簡介

承續之前的程式碼 (Dependency Injection – 1 – 程式碼範例 – 自行撰寫將 SharedPreferences 實例依賴注入的程式碼),我們使用 Koin 來重構未使用依賴注入的程式碼。

依賴

libs.versions.toml (修改)

[versions]
...

koin = "3.5.3"

[libraries]
...

koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" }

...

[bundles]
...

koin-compose = ["koin-core", "koin-android", "koin-androidx-compose"]

build.gradle.kts (Module level :app) (修改)

...

dependencies {

    ...

    implementation(libs.bundles.koin.compose)

}

程式碼範例

第一階段:未使用 Koin 時的程式碼

MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        val sharedPref = getSharedPreferences("mypref", Context.MODE_PRIVATE)

        sharedPref.edit {
            putString("username", "Steve")
        }
        println(sharedPref.getString("username", "" ))
        val username = sharedPref.getString("username", "") ?: "No name"

        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            KoinKata1010Theme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = username,
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

第二階段:在 MainActivity 中直接使用 Koin

這種方式比較少用,只是做為解釋 Koin 機制的範例程式碼。

AppModule.kt (新增)

val appModule = module {
    // 完整寫法
//    single<SharedPreferences> {
//        androidApplication().getSharedPreferences("mypref", Context.MODE_PRIVATE)
//    }
    // 因為可以從產生的實例類別對應到 MainViewModel 的參數型別,所以可以省略類別
    
    single {
        androidApplication().getSharedPreferences("mypref", Context.MODE_PRIVATE)
    }
    // 但如果我們用的是子類別 (例如: androidx.security.crypto.EncryptedSharedPreferences),
    // 就要指定要注入的類別
//    single<SharedPreferences> {
//        EncryptedSharedPreferences(
//            androidApplication(),
//            "mypref",
//            MasterKey(androidApplication()),
//            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
//            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
//        )
//    }
}

MyApp.kt

class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        
        startKoin {
            androidLogger()  
            androidContext(this@MyApp)
            modules(appModule)
        }
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".MyApp"
        ...

MainActivity.kt

...
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        //無需自行建立 sharedPref
        // val sharedPref = getSharedPreferences("mypref", Context.MODE_PRIVATE)

        // 使用 Koin 的 inject() 函式來取得 SharedPrefrences 實例
        val sharedPref: SharedPreferences by inject()
        // 也可以使用 Koin 的 get() 函式來取得 SharedPrefrences 實例
        // val sharedPref = get<SharedPreferences>()
        
        // 其他的程式碼不變
        sharedPref.edit {
            putString("username", "Steve")
        }
        println(sharedPref.getString("username", "" ))
        val username = sharedPref.getString("username", "") ?: "No name"

        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            KoinKata1010Theme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = username,
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

第三階段:透過 ViewModel 來使用 Koin

這是比較常用的方式,屬於建構式注入。

MainViewModel.kt

class MainViewModel(
    private val sharedPref: SharedPreferences
): ViewModel() {
    init {
        sharedPref.edit {
            putString("username", "Steve")
        }
    }
    val username = sharedPref.getString("username", "") ?: ""
}

class MainViewModelFactory(private val sharedPref: SharedPreferences) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel> create( modelClass: Class<T> ): T {
        if( modelClass.isAssignableFrom( MainViewModel::class.java ) ) {
            @Suppress( "UNCHECKED_CAST" )
            return MainViewModel( sharedPref ) as T
        }
        throw IllegalArgumentException( "Unknown ViewModel Class" )
    }
}

AppModule.kt (修改)

val appModule = module {
    // ...

    viewModel {
        MainViewModel(get())
    }
    // 這種寫法也可以
    // viewModelOf(::MainViewModel)
}

MyApp.kt (不變)

class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        
        startKoin {
            androidLogger()  
            androidContext(this@MyApp)
            modules(appModule)
        }
    }
}

MainActivity.kt (修改)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

//        val sharedPref = getSharedPreferences("mypref" , Context.MODE_PRIVATE)
//        sharedPref.edit {
//            putString("username", "Mary")
//        }
//        println(sharedPref.getString("username", ""))
//        val username = sharedPref.getString("username", "") ?: ""

//        val viewModel by viewModels<MainViewModel> {
//            MainViewModelFactory(
//                MyApp.appModule.sharedPref
//            )
//        }


        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {

            DILabTheme {
                // 取得 ViewModel
                val viewModel = getViewModel<MainViewModel>()
                // 也可以這麼寫
                // val viewModel:MainViewModel = koinViewModel()

                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        // 使用 ViewModel 中的變數
                        name = viewModel.username,
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

這樣,就完成以 Koin 幫助我們完成依賴注入,執行後,結果如下:

問題

1、single() 與 singleOf() 有什麼不同

singleOf() 是 Koin 3.2 版後新增的功能,如果在一個依賴中,我們需要另一個之前建立過的依賴,我們會用 get()

val appModule = module {
    single<FirstDepClass> {
        FirstClass
    }
    
    // 如果 SecondDepClass 需要 FirstDepClass 的實例做為參數傳入,我們會用 get()
    single<SecondDepClass> {
        SecondDepClass(get())
    }
}

使用 singleOf() 的方式如下:

val appModule = module {
    single<FirstDepClass> {
        FirstClass
    }
    
    singleOf(::SecondDepClass)
}

得到的結果跟使用 single 相同。

參考資料

Koin Official Site

Koin 的那一兩件事情

Day22 – 依賴注入框架Koin

Koin: Lightweight Dependency Injection Framework

Last modified: 2025 年 10 月 14 日

Author

Comments

Write a Reply or Comment

Your email address will not be published.