Compose Multiplatform 実践:expect と actual を使用したクロスプラットフォームコードの実装
Compose Multiplatform (略称CMP)
コードを共有することで
重複作業を減らすだけでなく
開発効率とコードの一貫性を向上させることができます
CMP
は開発者にクロスプラットフォームソリューションを提供し
同じビジネスロジックを異なるプラットフォーム上で実行できるようにします
この記事では
CMP
がexpect
とactual
キーワードを使用してクロスプラットフォーム
コードを実装する方法を探り
実践的な経験を共有します
目錄
- Compose Multiplatform 實戰:放輕鬆點,初探CMP
- Compose Multiplatform 實戰:初戰,安裝CMP環境吧
- Compose Multiplatform 實戰:續戰,用Wizard創建CMP專案
- Compose Multiplatform 實戰:在Android、iOS模擬器上跑CMP專案
- Compose Multiplatform 實戰:CMP的專案結構理解與編譯配置
- Compose Multiplatform 實戰:CMP中跨平台Android、iOS程式碼的進入點
- Compose Multiplatform 實戰:在CMP的Compose中用Material Design3 Theme
- Compose Multiplatform 實戰:CMP用Compose實作跨平台畫面
- Compose Multiplatform 實戰:使用 expect 和 actual 實現跨平台程式碼
- Compose Multiplatform 實戰:CMP中實作Compose Navigation頁面切換
- Compose Multiplatform 實戰:CMP中透過StateFlow來管理UI狀態
- Compose Multiplatform 實戰:CMP中實作NavigationBar底部欄
- Compose Multiplatform 實戰:CMP中使用koin來依賴注入Dependency Injection
- Compose Multiplatform 實戰:CMP實作跨平台資料庫SqlDelight
- Compose Multiplatform 實戰:CMP中使用ROOM開發跨平台資料庫 & 疑難雜症
まずは Compose Multiplatform と Kotlin Multiplatform について簡単に理解しましょう
expect
キーワードは共有コードでプラットフォーム固有のインターフェースやクラスを宣言するために使用され
actual
キーワードは特定のプラットフォームでそのインターフェースやクラスを実装するために使用されます
簡単な例を見てみましょう
以下のコードは各プラットフォームの名前を返す方法を示しています
commonMain
でexpect
キーワードを使用して関数を宣言し
CMPプロジェクトの他のプラットフォームでその関数を実装することを期待しています
// in ~/commonMain/.../xxx.kt
expect fun getPlatformName(): String
// in ~/androidMain/.../xxx.kt
actual fun getPlatformName(): String {
return "Android"
}
// in ~/iosMain/.../xxx.kt
actual fun getPlatformName(): String {
return "iOS"
}
また
expect
とactual
は関数だけでなく
クラスにも使用できます
これにより、共有コード内で
プラットフォーム固有のロジックを柔軟に定義し
特定のプラットフォームで実装を提供できます
// in ~/commonMain/.../FileSystem.kt
expect class FileSystem {
fun readFile(path: String): String
}
// in ~/androidMain/.../FileSystem.kt
actual class FileSystem {
actual fun readFile(path: String): String {
// Androidプラットフォーム固有のファイル読み込みロジック
}
}
// in ~/iosMain/.../FileSystem.kt
actual class FileSystem {
actual fun readFile(path: String): String {
// iOSプラットフォーム固有のファイル読み込みロジック
}
}
<div class=”c-border-main-title-2”“>実際の例</div>
- 一昨日のmaterial 3テーマの設定時に
expect
function として setStatusBarStyle を設定しました
これは主にクロスプラットフォーム関数を期待
し
特定のプラットフォームでステータスバーを設定できるようにするためです
// in ~/commonMain/.../xxxx.kt
@Composable
expect fun setStatusBarStyle(
backgroundColor: Color,
isDarkTheme: Boolean
)
// in ~/androidMain/.../xxx.kt
@Composable
actual fun setStatusBarStyle(backgroundColor: Color, isDarkTheme: Boolean) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = backgroundColor.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
}
}
}
// in ~/iosMain/.../xxx.kt
@Composable
actual fun setStatusBarStyle(backgroundColor: Color, isDarkTheme: Boolean) {
DisposableEffect(isDarkTheme) {
val statusBarStyle = if (isDarkTheme) {
UIStatusBarStyleLightContent
} else {
UIStatusBarStyleDarkContent
}
UIApplication.sharedApplication.setStatusBarStyle(statusBarStyle, animated = true)
onDispose { }
}
}
- 2つ目の例は、特定のプラットフォームにkoinを注入する場合です
そのプラットフォームが特定のinstance
を必要とする場合
そのプラットフォームで実装する必要があるかもしれません
この場合、commonMainにexpect
のkoin moduleを作成できます
以下は例です(後ほどkoinの使用方法についてさらに詳しく説明します)
// in ~/commonMain/.../xxxx.kt
expect val platformModule: Module
fun appModule() =
listOf(platformModule)
// in ~/androidMain/.../xxx.kt
actual val platformModule: Module = module {
/** Datastore*/
single { dataStore(get<Context>()) }
/** Database*/
single<RoomDatabase.Builder<AppDatabase>> {
getAppDatabase(get())
}
}
// in ~/iosMain/.../xxx.kt
actual val platformModule: Module = module {
/** DataStore*/
single { dataStore() }
/** Database*/
single { getAppDatabase() }
}
IDEには
実装しているclass
がどのプラットフォームにあるかを区別しやすくする方法があります
共有コードでexpect
キーワードを記述する際
他のプラットフォームでまだ対応するactual
が実装されていない場合
IDEは未実装であることを通知します
この時、自動生成をクリックするだけで
IDEは未実装のファイルを作成し
特定のプラットフォームのファイル名に対応する文字列
を追加します
例えば、xxx.android.ktやxxx.ios.ktなどです。
これにより
各classが属するプラットフォームをより直感的に理解できます
例えば:
command+f
で実装した関数を検索するとき
現在検索されているのがどのプラットフォームのclassなのかが明確にわかります。