Created
July 16, 2025 03:32
-
-
Save senseilearning/a0739eebd509e1c57f4c7ada0bd20ffc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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