🍿
Compose Multiplatformでexpect/actialでContextをうまく扱う方法
こんにちは!sugitaniと申します。ブラックキャット・カーニバル(略称ブラキャニ)というCompose Multiplatformで作られたSNSアプリを開発しています。
本稿はブラキャニの開発で得られた"あれどうやるんだっけ" を備忘録も兼ねて共有していくシリーズの4作目です。
expect/actialでContextをうまく扱う方法
以下の2つを使い分けています
-
@Composable
なビルダー関数を作ってどうにかする - Coil3同梱の
PlatformContext
でどうにかする
原則として1を使いますが、1では面倒な場面では2を使います。
@Composable
なビルダー関数を作ってどうにかする
①Contextを使う例題としてGoogleを開く処理を行ってみます。
commonMain
に以下を作成します
interface GoogleOpenerType1 {
fun openGoogle()
}
@Composable
expect fun rememberGoogleOpenerType1(): GoogleOpenerType1
Android以下に実装を作成します
@Composable
actual fun rememberGoogleOpenerType1(): GoogleOpenerType1 {
val context = LocalContext.current
val opener =
remember(context) {
object : GoogleOpenerType1 {
override fun openGoogle() {
val intent =
android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
data = "https://www.google.com".toUri()
}
context.startActivity(intent)
}
}
}
return opener
}
iOS以下に実装を作成します
@Composable
actual fun rememberGoogleOpenerType1(): GoogleOpenerType1 =
remember {
object : GoogleOpenerType1 {
override fun openGoogle() {
val url = NSURL(string = "https://www.google.com")
UIApplication.sharedApplication.openURL(url, emptyMap<Any?, Any>()) {}
}
}
}
PlatformContext
でどうにかする
②Coil3同梱の画像表示ライブラリCoil3同梱のPlatformContext
はAndroidから利用する時はただのContextに、iOSから利用するときは空の実装になります。
- commonMain/kotlin/coil3/PlatformContext.kt
- androidMain/kotlin/coil3/PlatformContext.kt
- nonAndroidMain/kotlin/coil3/PlatformContext.kt
どのみちCoilは大体の場合で使うと思うので①のアプローチだと面倒な場合は活用しましょう
commonMain
に以下を作成します
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
expect class GoogleOpenerType2(
context: PlatformContext,
) {
fun openGoogle()
}
Android以下に実装を作成します
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class GoogleOpenerType2 actual constructor(
private val context: PlatformContext,
) {
actual fun openGoogle() {
val intent =
android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
data = "https://www.google.com".toUri()
}
context.startActivity(intent)
}
}
iOS以下に実装を作成します
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class GoogleOpenerType2 actual constructor(
context: PlatformContext,
) {
actual fun openGoogle() {
val url = NSURL(string = "https://www.google.com")
UIApplication.sharedApplication.openURL(url, emptyMap<Any?, Any>()) {}
}
}
作成したType1,Type2は以下のように使います
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
internal fun App() {
val type1 = rememberGoogleOpenerType1()
val context = LocalPlatformContext.current
val type2 = remember(context) { GoogleOpenerType2(context) }
MaterialTheme {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = { Text("Use Context Example") },
)
},
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding).fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Button(onClick = { type1.openGoogle() }) {
Text("Open Google(type1)")
}
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { type2.openGoogle() }) {
Text("Open Google(type2)")
}
}
}
}
}
サンプルプロジェクト
本稿のソースコード、および動作するコードは
にあります。免責事項
このコードはあくまで"自分はこう実装した"という例ですので、よりよい方法がある可能性があります。見つけたら教えてください!
以下宣伝
- コメントのしやすさともらいやすさに全力を注いだショートSNS ブラキャニ をお試しください
- ブラックキャット・カーニバル株式会社のPublicationではCompose Multiplatformに関する記事を沢山投稿しています
- Compose Multiplatformでアプリのバックグラウンド切り替えを検知するには
- Compose MultiplatformでコンポーネントをImageBitmapにするには
- Compose MultiplatformでKoinを使って環境別のDIを行う方法
- Compose Multiplatformでexpect/actialでContextをうまく扱う方法
- Compose Multiplatformで後から重ねられる&一部をくり抜いたコンポーネントを作るには
- Compose MultiplatformでImageBitmapとBitmap/UIImageを相互変換するには
- Compose MultiplatformでLiquid glass対応のタブ表示を行うには
Discussion