範例簡介

我們以使用 SharedPreferences 為例來示範在 ViewModel 中的依賴注入。

範例程式碼

未使用 DI 的程式碼

第一階段:在 MainActivity 中,直接取得 SharedPreferences 的實例

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", "") ?: ""

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

第二階段:抽出邏輯,放到 ViewModel ,並把 SharePreferences 做為建構子傳入

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" )
    }
}

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(
                sharedPref
            )
        }

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

在這個階段,其實我們就將 SharedPreferencs 的實例 (也就是依賴,因為 MainViewModel 的運作依賴 SharedPreferences 實例) 注入到 MainViewModel 裏。

第三階段:抽出邏輯至 App 層級

為了提高程式碼的可重用性,我們再將 SharedPreferences 的實例拉高到 App 層級,讓其他會用到這個實例的程式,可以直接取用。

AppModule.kt (新增檔案)

interface AppModule {
		val shreadPref: SharedPreferences
}

class AppModuleImpl(
		private val appContext: Context
) {
    override val sharedPref: SharedPreferences = appContext.getSharedPreferences("mypref", Context.MODE_PRIVATE)

}

MyApp.kt (新增檔案)

class MyApp: Application() {
		companion object {
				lateinit var appModule: AppModule
		}
		
		override fun onCreate() {
				super.onCreate()
				appModule = AppModuleImple(this)
		}
}

AndroidMainfest.xml (編輯檔案)

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

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 {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = viewModel.username,
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

...

這樣就完成了手動將 SharedPreference 的依賴實例注入 MainViewModel 中的動作。

接下來,我們就可以在測試時,或是在不同狀況下,更換不同的 SharedPreference 實例,把測式的重心放在 MainViewModel 中,而不受 MainActivity 的影響。

而像 Koin 或 Dagger-Hilt 這一類的函式庫,只是提供了更便利的功能,讓我們在比較複雜的依賴注入時更加方便一些。

Last modified: 2025 年 6 月 30 日

Author

Comments

Write a Reply or Comment

Your email address will not be published.