2023年5月27日土曜日

Compose Multiplatform で Image loading ってどうやるの?

技術書典14用に書いたんだけど mhidaka 多忙により寝かされてしまったため、ここで供養します。
夏コミ(C102)は別の記事を書く予定です。

==========================


今年(2023年)のKotlinConf(https://kotlinconf.com/)でCompose Multiplatformが発表されました。これまではCompose Desktopを使うことでAndroidとDesktopでUIコードを共通化することができました。Compose Multiplatformではこれに加えiOSとWebもサポートしています。2023年5月時点ではiOSはAlpha版、WebはExperimental版です。

この章ではAndroidとiOSのUIをCompose Multiplatformで共通化したときに、画像をネットワークから非同期に読み込む方法を紹介します。

ここで紹介する方法は2023年5月時点のものであり、今後APIが変わる可能性が大いにあります。注意してください。



Resources

Compose MultiplatformにはResourceというinterfaceが用意されています。 Resourceにはsuspend関数のreadBytes()が定義されており、戻り値はByteArrayです。 @ExperimentalResourceApi interface Resource { suspend fun readBytes(): ByteArray } このResourceを実装したLoadImageResourceを用意します。 LoadImageResourceのreadBytes()では指定されたURLからByteArrayを取得する処理を実装します。LoadImageResourceではKtor(https://ktor.io/)を使っています。 @OptIn(ExperimentalResourceApi::class) class LoadImageResource(private val url: String) : Resource { override suspend fun readBytes(): ByteArray { return HttpClient().use { it.get(url).readBytes() } } }

rememberImageBitmap

Resourceの拡張関数としてrememberImageBitmap()が用意されています。 rememberImageBitmap()はComposable関数で、戻り値はLoadState<ImageBitmap>です。 LaunchedEffectのなかでResourceのreadBytes()を呼び出し、返ってきたByteArrayをtoImageBitmap()でImageBitmapに変換しています。 @ExperimentalResourceApi @Composable fun Resource.rememberImageBitmap(): LoadState<ImageBitmap> { val state: MutableState<LoadState<ImageBitmap>> = remember(this) { mutableStateOf(LoadState.Loading()) } LaunchedEffect(this) { state.value = try { LoadState.Success(readBytes().toImageBitmap()) } catch (e: Exception) { LoadState.Error(e) } } return state.value } LoadStateはsealed classでLoading、Success、Errorの3つのサブクラスがあります。 sealed class LoadState<T> { class Loading<T> : LoadState<T>() data class Success<T>(val value: T) : LoadState<T>() data class Error<T>(val exception: Exception) : LoadState<T>() }

AsyncImage

画像を読み込んで表示したいURLに対してLoadImageResourceを作り、rememberImageBitmap()の返す LoadStateに応じてComposableを表示します。 LoadState.SuccessのvalueプロパティからImageBitmapを取得し、BitmapPainterでPainterを作ります。 最後にBitmapPainterをImage composableにセットすることで画像が表示されます。 @OptIn(ExperimentalResourceApi::class) @Composable fun AsyncImage( url: String, contentDescription: String?, modifier: Modifier = Modifier, contentScale: ContentScale = ContentScale.Fit, ) { val resource = remember(url) { LoadImageResource(url) } when (val loadState = resource.rememberImageBitmap()) { is LoadState.Loading, is LoadState.Error -> { Spacer(modifier = modifier.background(Color.LightGray)) } is LoadState.Success -> { Image( painter = BitmapPainter(loadState.value), contentDescription = contentDescription, contentScale = contentScale, modifier = modifier ) } } }

まとめ

これでCompose MultiplatformのAndroid、iOS両方でネットワークから読み込んだ画像が表示されます。 実際のアプリで使うにはキャッシュ機能などが必要になりますし、そのうちライブラリもでてくるでしょう。 Compose Multiplatformぜひ試してみてください。



0 件のコメント:

コメントを投稿