Jetpack Compose で AppSearch に対応する

# AppSearch とは

テキストをオンデバイスで検索する機能の1つで、オフラインでも利用可能なライブラリです。
![appsearch_overview_diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223159/50db82bf-0a78-1b03-56cf-135a3909cb76.png)

Low I/Oな為、SQLiteに比べ大規模なデータセットのインデックス作成と検索レイテンシが低くなります。


ユースケース例(以下リファレンスからの引用)
>  let’s take the example of a music application that manages users’ favorite songs and allows users to easily search for them. Users enjoy music from around the world with song titles in different languages, which AppSearch natively supports indexing and querying for. When the user searches for a song by title or artist name, the application simply passes the request to AppSearch to quickly and efficiently retrieve matching songs. The application surfaces the results, allowing its users to quickly start playing their favorite songs.

かなり具体的でイメージしやすいかと思います。

https://developer.android.com/develop/ui/views/search/appsearch

また、`LocalStorage`と`PlatformStorage`の2種類が用意されていて違いとしては以下のような機能差があります。

![スクリーンショット 2024-12-05 22.28.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223159/696c99cf-b616-037f-1b73-ada20fa8ae86.png)

利用用途にもよりますが、今回は主に使用されるであろう`LocalStorage`に焦点を当ててご紹介します。

# 導入方法

### 下準備
利用するgradle(module)にAppSearchを追加します。

```toml:libs.versions.toml
[versions]
appSearch = "1.1.0-alpha06"
...

[libraries]
androidx-appsrarch = { group = "androidx.appsearch", name = "appsearch", version.ref = "appSearch" }
androidx-appsrarch-compiler = { group = "androidx.appsearch", name = "appsearch-compiler", version.ref = "appSearch" }
androidx-appsrarch-local-storage = { group = "androidx.appsearch", name = "appsearch-local-storage", version.ref = "appSearch" }
androidx-appsrarch-platform-storage = { group = "androidx.appsearch", name = "appsearch-platform-storage", version.ref = "appSearch" }
...
```

```gradle:build.gradle.kts(app)
dependencies {
    ...
    implementation(libs.androidx.appsrarch)
    kapt(libs.androidx.appsrarch.compiler)
    implementation(libs.androidx.appsrarch.local.storage)
    implementation(libs.androidx.appsrarch.platform.storage)
    ...
}
```

### 利用方法

AppSearchのデータ単位であるドキュメントクラスを作成します。

```kotlin:Video.kt
@Document
public data class Video(
    @Document.Namespace
    val namespace: String,
    @Document.Id
    val id: String,
    @Document.Score
    val score: Int,
    @Document.StringProperty(indexingType = AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
    val text: String
)
```
indexingTypeはPREFIXの他にも
- `INDEXING_TYPE_NONE`
- `INDEXING_TYPE_EXACT_TERMS`
の3つから選ぶことができます。
今回は部分一致での検索を行いたいので、`PREFIX`を指定しています。


次にDBの作成とSchemeの設定を行います
```SampleActivity.kt

@Composable
fun MainScreen(applicationContext: Context) {
    val scope = rememberCoroutineScope()
    scope.launch(Dispatchers.IO) {
        // Database の作成(if it's not creared)
        val sessionFuture = LocalStorage.createSearchSessionAsync(
            LocalStorage.SearchContext.Builder(
                applicationContext, DATABASE_NAME
            ).build()
        )
        // Scheme の設定
        val setSchemaRequest = SetSchemaRequest.Builder()
            .addDocumentClasses(Video::class.java)
            .build()
        sessionFuture.get().setSchemaAsync(setSchemaRequest)
    }
}
```

ここまでできたら、データのインサートを行います。
※Sampleデータは複数件用意すると検索速度を感じやすいかと思います。

```kotlin:SampleActivity.kt
// Dataの追加
val video = Video(
    namespace = "HaSuzuki",
    id = "videoId",
    score = 20,
    text = "Sample Sports Highlights Video."
)
sessionFuture.get().putAsync(PutDocumentsRequest.Builder().addDocuments(
    video
).build()).get().let {
    if (it.isSuccess) {
        // something...
    }
}
```

検索も試してみます。

```kotlin:SampleActivity.kt
val searchSpec = SearchSpec.Builder()
    .setResultCountPerPage(1)
    .setSnippetCount(10)
    .build()
val result = sessionFuture.get().search("Video", searchSpec)
result.nextPageAsync.get().mapNotNull {
    if (it.genericDocument.schemaType == Video::class.java.simpleName) {
       val doc = it.getDocument(Video::class.java)
       // text = Sample Sports Highlights Video.
       Log.d("Hit Documents", doc.text)
    }
}
```
※ [setSnippetCount](https://developer.android.com/reference/android/app/appsearch/SearchSpec.Builder#setSnippetCount(int))のdefault = 0かつMAX_RANGEは10,000で設定されているのでご注意ください。

### 補足
[SearchSpec](https://developer.android.com/reference/androidx/appsearch/app/SearchSpec) は他にも`ORDER_BY`や`RANKING_STRATEGY`、`PREFIXのTERM`など様々な機能があります。


# 最後に
いかがだったでしょうか。比較的シンプルな実装で、規模に関わらず利用しやすいライブラリだったかと思います。
また、Sample数は少ないですが`1,000件`の場合でも取得にかかる時間が`約30ms`という結果でした。速度の観点でも悪くない数字だと思います。

ぜひ、みなさんもオンデバイス検索を検討される際には `AppSearch` をお試しいただければと思います。

※現時点(2024/12/5)で`appsearch-compiler`は`ksp`に対応していなさそうなので、その点だけご注意ください。 

[TVer Advent Calender](https://qiita.com/advent-calendar/2024/tver)
← @yabk さんによる 「CharlesのAndroid設定について」
→ @k0bya4 さんによる 「DBスキーママイグレーションの自動化について書きます」
になります。