###### tags: `Android` `Jetpack` # View Model Jetpack 架構系列之一,View Model 是輔助 MVVM 架構中 ViewModel 的框架 因為它有 Lifecycle-aware 的特性,因此能有效的解決記憶體洩漏,及 Activity 生命週期問題 以往我們會把取得的資料存在 Activity 裡,但螢幕旋轉後,會發現上面的資料不見了 因為旋轉時 Activity 會先銷毀再重新產生,所以先前放在 Activity 裡的資料就會因此消失 即使不讓使用者旋轉,Activity、Fragment 和 View 還是可能在任何時候被銷毀 例如離開 App 到其他的 App 後,隔了一天再回來 App 時,Activity 可能已經被回收了 這時,再次開 App 即會重新產生 Activity,此時資料就會不見或閃退,這種錯誤通常不容易測試 因為你不會把 App 放一天再開起來測試,但對使用者來說,這可能是經常會發生的錯誤 所以需要一個比 Activity 生命週期更長的東西來存資料,View Model 正可以為我們解決這個問題 <details> <summary>畫面旋轉時 Activity 與 View Model 的生命週期圖</summary>  </details> ## 官方介紹 ViewModel 用于提供和管理 UI 介面的數據。通過使用 ViewModel,開發者可以方便的剝離 UI 介面和數據邏輯,從而達到 UI 介面 Activity 和 Fragment 負責顯示數據和處理使用者操作;ViewModel 負責提供和管理 UI 介面的數據,並且負責和數據層通訊。與此同時,也讓您在開發過程中更好地遵循單一職責的設計原則。此外,ViewModel 的另一大特點是它不会因為配置變更而銷毀。 請注意 ViewModel 不能代替 onSavedInstanceState(),因為當系統資源緊缺時應用程式會被系統關閉,ViewModel 也會隨之消失,我們應該盡量把 UI 介面所需的資料儲存在 ViewModel,而 onSavedInstanceState() 用於保存小量級的資訊,例如使用者ID而不是使用者全部的資料,當應用程式會被系統關閉,則用 onSavedInstanceState() 恢復 UI 介面。 另外,不應該將 Context 傳入 ViewModel,換言之 ViewModel 內不應該有 Activity、Fragment、View 的引用,因為這會造成記憶體洩漏。 [架构组件之 ViewModel 介绍](https://www.youtube.com/watch?v=mpO-aEXhX78) ## 基本使用 <font color=Tomato>以下將使用 Data Binding 篇的基本使用中的範例繼續實作</font> ### 匯入 ViewModel 因為不斷改版的關係,有些教學會匯入 lifecycle-extensions,但在新版本已經撤銷 官方建議依使用到的 lifecycle 元件來添加就好,如何添加可以參考[官方說明](https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies) 在 ==build.gradle(Module:app)== 加入以下程式碼,加入後 Sync ``` def lifecycle_version = "2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" //If your app uses Java 8, we recommend using this library instead of lifecycle-compiler implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" ``` ### 應用於 MVVM 架構 先參考此篇文章的 [基本使用](https://hackmd.io/USJfeNs2R1qLGp6rkEJQiw?view#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8),完成後再來加入 View Model 將 ==MainViewModel== 繼承 ViewModel ```kotlin= class MainViewModel : ViewModel() { private val dataModel = DataModel() val mData = ObservableField<String>() val isLoading = ObservableField(false) fun refresh() { isLoading.set(true) dataModel.retrieveData(object : DataModel.onDataReadyCallback { override fun onDataReady(data: String) { mData.set(data) isLoading.set(false) } }) } } ``` 修改 ==MainActivity== 的 ==viewModel== 變數的初始化方式 ```kotlin= class MainActivity : AppCompatActivity() { private lateinit var viewModel: MainViewModel private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this).get(MainViewModel::class.java) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = viewModel } } ``` :::success ViewModelProvider 函式中的傳入參數是 ViewModel 的生命範圍,這裡給予 this 所以 ViewModel 的生命週期會持續到 MainActivity 不再活動為止 只要 MainActivity 還在活動,ViewModel 就不會被清除,每次 create 都能取得相同實體 ::: 完成後,旋轉螢幕時,TextView 的文字都還是 New Data 因為 MainViewModel 的實體還存活,所以 mData 還保有原本的 New Data 文字  ## Context 獲取 使用 ViewModel 需要注意不要儲存 Activity/Fragment 的內容或 context 在 ViewModel 中 因為 configuration changes 時 Activity 及其內容會被銷毀,ViewModel 就會產生 memory leak 若要在 ViewModel 中使用 Context,可以改繼承 AndroidViewModel 其建構式帶有 application 可提供我們獲取 context ```kotlin= class MainViewModel(application: Application) : AndroidViewModel(application) { private val dataModel = DataModel() private val mContext = getApplication<Application>().applicationContext val mData = ObservableField<String>() val isLoading = ObservableField(false) fun refresh() { isLoading.set(true) dataModel.retrieveData(object : DataModel.onDataReadyCallback { override fun onDataReady(data: String) { mData.set(data) isLoading.set(false) } }) } } ``` ## 建構式傳參 ViewModel 是用 ViewModelProvider 建構,如果要傳入參數,也要使用 ViewModelProvider ViewModelProvider 有兩種建構式,除了原本的 owner 外,還能傳入 factory 所以我們要建立一個 ViewModelFactory 來作參數傳遞 新增建構式參數於 ==MainViewModel== ```kotlin= class MainViewModel(val mode: Int) : ViewModel() { private val dataModel = DataModel() val mData = ObservableField<String>() val isLoading = ObservableField(false) fun refresh() { isLoading.set(true) dataModel.retrieveData(object : DataModel.onDataReadyCallback { override fun onDataReady(data: String) { mData.set(data) isLoading.set(false) } }) } } ``` 新增 ==MainViewModelFactory== ```kotlin= class MainViewModelFactory(private val mode: Int) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(mode) as T } throw IllegalArgumentException("Unknown ViewModel class") } } ``` 修改 ==MainActivity== 的 ==ViewModelProvider== 建構式 ```kotlin= class MainActivity : AppCompatActivity() { private lateinit var viewModel: MainViewModel private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModelFactory = MainViewModelFactory(1) viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = viewModel } } ``` ## 參考文章 [Architecture Components - ViewModel](https://ithelp.ithome.com.tw/articles/10193118) [Day20 - Android MVVM 架構:ViewModel & LiveData](https://ithelp.ithome.com.tw/articles/10224442)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up