Skip to content

Kotlin Multiplatform - UI共有なし

このチュートリアルでは、Androidアプリケーションを作成し、Koinの依存性注入を使用してコンポーネントを取得する方法を説明します。 このチュートリアルを完了するには、約15分かかります。

NOTE

更新 - 2024-10-21

コードの取得

アプリケーションの概要

このアプリケーションのアイデアは、ユーザーのリストを管理し、共有Presenterを使ってネイティブUIに表示することです。

Users -> UserRepository -> Shared Presenter -> Native UI

「User」データ

すべての共通/共有コードは、shared Gradleプロジェクトにあります

Userのコレクションを管理します。以下にデータクラスを示します。

kotlin
data class User(val name : String)

ユーザーのリストを管理するための「Repository」コンポーネントを作成します(ユーザーを追加したり、名前でユーザーを検索したりします)。以下に、UserRepositoryインターフェースとその実装を示します。

kotlin
interface UserRepository {
    fun findUser(name : String): User?
    fun addUsers(users : List<User>)
}

class UserRepositoryImpl : UserRepository {

    private val _users = arrayListOf<User>()

    override fun findUser(name: String): User? {
        return _users.firstOrNull { it.name == name }
    }

    override fun addUsers(users : List<User>) {
        _users.addAll(users)
    }
}

共有Koinモジュール

Koinモジュールを宣言するには、module関数を使用します。Koinモジュールは、注入されるすべてのコンポーネントを定義する場所です。

最初のコンポーネントを宣言しましょう。UserRepositoryImplのインスタンスを作成することで、UserRepositoryのシングルトンを取得します。

kotlin
module {
    singleOf(::UserRepositoryImpl) { bind<UserRepository>() }
}

共有Presenter

ユーザーを表示するためのpresenterコンポーネントを記述しましょう。

kotlin
class UserPresenter(private val repository: UserRepository) {

    fun sayHello(name : String) : String{
        val foundUser = repository.findUser(name)
        val platform = getPlatform()
        return foundUser?.let { "Hello '$it' from ${platform.name}" } ?: "User '$name' not found!"
    }
}

UserRepositoryはUserPresenterのコンストラクタで参照されます

KoinモジュールにUserPresenterを宣言します。メモリにインスタンスを保持せず、ネイティブシステムにそれを保持させるため、factoryOf定義として宣言します。

kotlin
val appModule = module {
    singleOf(::UserRepositoryImpl) { bind<UserRepository>() }
    factoryOf(::UserPresenter)
}

NOTE

Koinモジュールは実行可能な関数(ここではappModule)として利用でき、initKoin()関数を使ってiOS側から簡単に実行できます。

ネイティブコンポーネント

以下のネイティブコンポーネントはAndroidとiOSで定義されています。

kotlin
interface Platform {
    val name: String
}

expect fun getPlatform(): Platform

どちらもローカルプラットフォームの実装を取得します

Androidでの注入

Androidアプリ全体はandroidApp Gradleプロジェクトにあります

UserPresenterコンポーネントは作成され、それとともにUserRepositoryインスタンスが解決されます。これをActivityで取得するには、koinInject Compose関数を使って注入しましょう。

kotlin
// in App()

val greeting = koinInject<UserPresenter>().sayHello("Koin")

Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
    Image(painterResource(Res.drawable.compose_multiplatform), null)
    Text("Compose: $greeting")
}

これで、アプリの準備ができました。

INFO

koinInject()関数を使用すると、Android ComposeランタイムでKoinインスタンスを取得できます

AndroidアプリケーションでKoinを開始する必要があります。Composeアプリケーション関数App内でKoinApplication()関数を呼び出すだけです。

kotlin
fun App() {
    
    KoinApplication(application = koinAndroidConfiguration(LocalContext.current)){
        // ...
    }
}

共有KMP設定からKoinのAndroid設定を収集します。

kotlin
// Android config
fun koinAndroidConfiguration(context: Context) : KoinAppDeclaration = {
    androidContext(context)
    androidLogger()
    koinSharedConfiguration()
}

NOTE

LocalContext.currentを使用して、Composeから現在のAndroidコンテキストを取得します

そして共有KMP設定です。

kotlin
// Common config
fun koinSharedConfiguration() : KoinAppDeclaration = {
    modules(appModule)
}

INFO

modules()関数は、指定されたモジュールのリストをロードします

iOSでの注入

iOSアプリ全体はiosAppフォルダーにあります

UserPresenterコンポーネントは作成され、それとともにUserRepositoryインスタンスが解決されます。これをContentViewで取得するには、iOS用のKoin依存関係を取得する関数を作成する必要があります。

kotlin
// Koin.kt

fun getUserPresenter() : UserPresenter = KoinPlatform.getKoin().get()

これで終わりです。iOS側からKoinKt.getUserPresenter().sayHello()関数を呼び出すだけで済みます。

swift
import Shared

struct ContentView: View {

    // ...
    let greet = KoinKt.getUserPresenter().sayHello(name: "Koin")
}

iOSアプリケーションでKoinを開始する必要があります。Kotlinの共有コードでは、initKoin()関数で共有設定を使用できます。最後に、iOSのメインエントリでは、上記のヘルパー関数を呼び出すKoinAppKt.doInitKoin()関数を呼び出すことができます。

swift
@main
struct iOSApp: App {
    
    init() {
        KoinAppKt.doInitKoin()
    }

    //...
}