Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save senseilearning/a0739eebd509e1c57f4c7ada0bd20ffc to your computer and use it in GitHub Desktop.
Save senseilearning/a0739eebd509e1c57f4c7ada0bd20ffc to your computer and use it in GitHub Desktop.
KMPプロジェクトでContextを扱うには、プラットフォーム固有のContext(Android)やそれに類する機能(iOS)を、commonMainで利用可能な形に抽象化する必要があります。主な方法として、Kotlin Multiplatformの**expect/actualメカニズム**を利用する方法と、依存性注入(DI)ライブラリ(Koinなど)を活用する方法の2つがあります。
一般的に、小規模な場合はexpect/actualが手軽ですが、多くの機能でContextが必要になる場合は、コードの疎結合を促進し、テストを容易にするDIの利用が推奨されます。
expect/actual を利用する方法
この方法は、commonMainで「期待される」(expect)宣言を行い、各プラットフォームのソースセット(androidMainやiosMain)でその「実際の」(actual)実装を提供するKMPの基本的な機能です。
1. commonMainでexpectを宣言
共有コードで必要となる機能をインターフェースやクラスとしてexpect宣言します。ここでは、アプリケーションのコンテキスト情報を持つAppContextというexpectクラスを定義する例を示します。
// in commonMain/kotlin
package com.example.project
expect class AppContext
AppContext自体に機能を持たせることも可能です。例えば、リソースへのアクセスなどです。
// in commonMain/kotlin
package com.example.project
expect class PlatformContext {
fun getGreeting(): String
}
2. androidMainでactualを実装
androidMainでは、expect宣言されたAppContextのactual実装として、AndroidのContextを保持するクラスを定義します。
// in androidMain/kotlin
package com.example.project
import android.content.Context
actual typealias AppContext = Context
もしPlatformContextのように機能を持つクラスを定義した場合は、以下のようになります。
// in androidMain/kotlin
package com.example.project
import android.content.Context
actual class PlatformContext(private val context: Context) {
actual fun getGreeting(): String {
// R.string.greetingのようなリソースへのアクセス
return "Hello from Android!"
}
}
3. iosMainでactualを実装
iOSにはAndroidのContextに直接対応するものがないため、iosMainでは必要な機能を提供するクラスを独自に定義します。
typealiasを使う場合は、特定のクラスに結びつけず、空のクラスなどを定義することが多いです。
// in iosMain/kotlin
package com.example.project
actual class AppContext
PlatformContextの例では、iOSのAPIを利用して実装します。
// in iosMain/kotlin
package com.example.project
import platform.UIKit.UIDevice
actual class PlatformContext {
actual fun getGreeting(): String {
return "Hello from ${UIDevice.currentDevice.systemName}!"
}
}
依存性注入 (DI) を利用する方法 (Koin)
KoinのようなDIライブラリを使うと、プラットフォーム固有の実装をより柔軟に注入でき、コードの再利用性とテスト性が向上します。
1. commonMainでインターフェースを定義
共有コードで利用したい機能をインターフェースとして定義します。
// in commonMain/kotlin
interface ContextProvider {
fun getAppContext(): Any // AndroidではContext、iOSでは他のものを返す
fun getDeviceName(): String
}
2. 各プラットフォームでインターフェースを実装
androidMainの実装
// in androidMain/kotlin
import android.content.Context
import android.os.Build
class AndroidContextProvider(private val context: Context) : ContextProvider {
override fun getAppContext(): Any = context
override fun getDeviceName(): String = "Android ${Build.VERSION.SDK_INT}"
}
iosMainの実装
// in iosMain/kotlin
import platform.UIKit.UIDevice
class IosContextProvider : ContextProvider {
// iOSでは特定のContextオブジェクトは不要な場合が多い
override fun getAppContext(): Any = this
override fun getDeviceName(): String = UIDevice.currentDevice.systemName()
}
3. Koinモジュールを設定
各プラットフォームで、上記の実装をKoinモジュールとして登録します。
androidMainのモジュール
// in androidMain/kotlin
import org.koin.dsl.module
actual val platformModule = module {
single<ContextProvider> { AndroidContextProvider(get()) } // get()でAndroidのContextを解決
}
iosMainのモジュール
// in iosMain/kotlin
import org.koin.dsl.module
actual val platformModule = module {
single<ContextProvider> { IosContextProvider() }
}
commonMainでこれらのモジュールをまとめるexpect/actualを定義しておくと便利です。
// in commonMain/kotlin
import org.koin.core.module.Module
expect val platformModule: Module
4. アプリケーション起動時にKoinを初期化
Android (Applicationクラス)
// in androidApp/src/main/java/.../MainApplication.kt
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MainApplication)
modules(appModule()) // platformModuleを含む共通モジュール
}
}
}
iOS (iOSAppなど)
iOSでは、MainViewControllerのinitやAppDelegateでKoinを初期化します。
// in iosApp/iosApp/iOSApp.swift
import SwiftUI
import shared
@main
struct iOSApp: App {
init() {
// Koinの初期化処理を呼び出す
KoinKt.doInitKoin()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
sharedモジュールにKoinを初期化するヘルパー関数を定義します。
// in commonMain/kotlin
fun initKoin() {
startKoin {
modules(commonModule, platformModule)
}
}
これで、commonMainのViewModelなどでContextProviderを注入して、プラットフォーム固有の機能を利用できます。
// in commonMain/kotlin
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class MyViewModel : KoinComponent {
private val contextProvider: ContextProvider by inject()
fun getDevice(): String {
return contextProvider.getDeviceName()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment