簡介
承續之前的程式碼 (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 相同。
Comments