Lottie Composeでキャッシュを活用する方法

# Lottie Composeとは

LottieはJSONを元にしたリッチなアニメーションを、軽量かつクロスプラットフォームで表示できるライブラリです。Webやネイティブなど、プラットフォームを問わず同一のJSONファイルで表現でき、**Lottie Compose**はそのJetpack Compose対応版となります。

詳しくは以下を参照してください。

- [Lottie](https://lottiefiles.com/jp/what-is-lottie)
- [Lottie Compose](https://github.com/airbnb/lottie/blob/master/android-compose.md)

# なぜキャッシュを気にする必要があるのか

一部のシーンでは、画面が表示されてから即座にアニメーションが再生されることが求められる場合があります。このとき、事前にキャッシュを作成しておかないと、アニメーションの再生開始までわずかなラグが発生し、ユーザー体験に影響を及ぼします。

# 具体的にどうするか

画面表示後、アニメーション再生までにある程度の猶予があり、Composable起動後にアニメーションを準備しても間に合う場合は、ドキュメント通りに `rememberLottieComposition` を使用する実装で十分です。ユーザーの操作（例：ボタン押下）でアニメーション開始のトリガーが発生する頃には、Lottie側で準備が整えられています。

```kotlin
val composition by rememberLottieComposition(
    LottieCompositionSpec.Url(stringResource(R.string.animationUrl))
)

LottieAnimation(
    modifier = modifier,
    composition = composition,
    progress = 1f,
)
```

一方、画面表示からアニメーション開始までにほとんど猶予がなく、Composable起動後の準備では遅い場合には、`LottieCompositionFactory` の `fromUrl` や `fromAsset` を利用するのが有効です。これにより画面が表示される前にキャッシュの準備ができ、一度の処理で完了します。

LottieCompositionFactory はAndroid Viewでの利用時から存在する共通の仕組みであり、Jetpack Composeの世界に入る前からキャッシュを準備できます。

fromUrl や fromAsset は、アニメーションを再生するために必要な LottieComposition を返す関数です。これらを呼び出す過程で LottieCompositionCache に対するキャッシュ登録も行われます。そのため、これらを適切なタイミングで呼び出してキャッシュを温めておくことができます。

```java
public static LottieTask<LottieComposition> fromUrl(final Context context, final String url, @Nullable final String cacheKey) {
    return cache(cacheKey, () -> {
      LottieResult<LottieComposition> result = L.networkFetcher(context).fetchSync(url, cacheKey);
      if (cacheKey != null && result.getValue() != null) {
        LottieCompositionCache.getInstance().put(cacheKey, result.getValue());
      }
      return result;
    });
  }
```

もしアプリ起動時点でURLやアセットパスなどのアニメーションリソースが判明しているなら、[App Startup]((https://developer.android.com/topic/libraries/app-startup?hl=ja))を利用してアプリ起動時に LottieCompositionFactory.fromUrl などを実行しておくとよいでしょう。

```Kotlin

class AnimationInitializer : Initializer<Unit> {

    val animationUrl = R.string.animationUrl

    override fun create(context: Context) {
        LottieCompositionFactory.fromUrl(context, animationUrl)
    }
}


```




# 補足
Lottie Composeでも、従来のAndroidViewでのComposeでもLottieが使用する情報のソースは同じである事が重要です。共通のソースを使用しており、そのソースに対するアクセスも共通のLottieCompositionFactoryを用いて行えます。アウトプット先がAndroid ViewかJetpack Composeかで分かれるだけ、ということですね。


実際にrememberLottieCompositionのコードを追っていくと、最終的にAndroid Viewと同様のfrom~~シリーズが叩かれていることがわかります。

```Kotlin
private fun lottieTask(
    context: Context,
    spec: LottieCompositionSpec,
    cacheKey: String?,
    isWarmingCache: Boolean,
): LottieTask<LottieComposition>? {
    return when (spec) {
        is LottieCompositionSpec.RawRes -> {
            if (cacheKey == DefaultCacheKey) {
                LottieCompositionFactory.fromRawRes(context, spec.resId)
            } else {
                LottieCompositionFactory.fromRawRes(context, spec.resId, cacheKey)
            }
        }
        is LottieCompositionSpec.Url -> {
            if (cacheKey == DefaultCacheKey) {
                LottieCompositionFactory.fromUrl(context, spec.url)
            } else {
                LottieCompositionFactory.fromUrl(context, spec.url, cacheKey)
            }
        }
        is LottieCompositionSpec.File -> {
            if (isWarmingCache) {
                // Warming the cache is done from the main thread so we can't
                // create the FileInputStream needed in this path.
                null
            } else {
                val fis = FileInputStream(spec.fileName)
                when {
                    spec.fileName.endsWith("zip") -> LottieCompositionFactory.fromZipStream(
                        ZipInputStream(fis),
                        if (cacheKey == DefaultCacheKey) spec.fileName else cacheKey,
                    )
                    else -> LottieCompositionFactory.fromJsonInputStream(
                        fis,
                        if (cacheKey == DefaultCacheKey) spec.fileName else cacheKey,
                    )
                }
            }
        }
        is LottieCompositionSpec.Asset -> {
            if (cacheKey == DefaultCacheKey) {
                LottieCompositionFactory.fromAsset(context, spec.assetName)
            } else {
                LottieCompositionFactory.fromAsset(context, spec.assetName, cacheKey)
            }
        }
        is LottieCompositionSpec.JsonString -> {
            val jsonStringCacheKey = if (cacheKey == DefaultCacheKey) spec.jsonString.hashCode().toString() else cacheKey
            LottieCompositionFactory.fromJsonString(spec.jsonString, jsonStringCacheKey)
        }
        is LottieCompositionSpec.ContentProvider -> {
            val inputStream = context.contentResolver.openInputStream(spec.uri)
            LottieCompositionFactory.fromJsonInputStream(inputStream, if (cacheKey == DefaultCacheKey) spec.uri.toString() else cacheKey)
        }
    }
}
```


また、共通のソースを用いているため clearCacheといったキャッシュ解放も同様に行えます。関連して、clearChacheを用いたLottie Composeでのパフォーマンス改善の記事がありますので添付して終わりにいたします。

https://medium.com/@mesooryanarayananb/lottiecompositioncache-memory-usage-and-clearing-strategy-in-android-jetpack-compose-1fb6cd621333