天天看點

LiveData與ViewModel基礎使用篇

一、基本概念

1、LiveData

1.1 LiveData 簡介

LiveData 是一種可觀察的資料存儲器類。與正常的可觀察類不同,LiveData 具有生命周期感覺能力,意指它遵循其他應用元件(如 Activity、Fragment 或 Service)的生命周期。

2、LiveData 優勢

  • 確定界面符合資料狀态LiveData 遵循觀察者模式。
當底層資料發生變化時,LiveData 會通知 Observer 對象。可以整合代碼以在這些 Observer 對象來更新界面。這樣一來,無需在每次應用資料發生變化時更新界面,因為觀察者會替您完成更新。
  • 不會發生記憶體洩漏
觀察者會綁定到 Lifecycle 對象,并在其關聯的生命周期遭到銷毀後進行自我清理。
  • 不會因 Activity 停止而導緻崩潰
如果觀察者的生命周期處于非活躍狀态(如傳回棧中的 Activity),它不會接收任何 LiveData 事件。
  • 不再需要手動處理生命周期
界面元件隻是觀察相關資料,不會停止或恢複觀察。LiveData 将自動管理所有這些操作,因為它在觀察時可以感覺相關的生命周期狀态變化。
  • 資料始終保持最新狀态
如果生命周期變為非活躍狀态,它會在再次變為活躍狀态時接收最新的資料。例如,曾經在背景的 Activity 會在傳回前台後立即接收最新的資料。
  • 适當的配置更改
如果由于配置更改(如裝置旋轉)而重新建立了 Activity 或 Fragment,它會立即接收最新的可用資料。
  • 共享資源
可以使用單例模式擴充 LiveData 對象以封裝系統服務,以便在應用中共享它們。LiveData 對象連接配接到系統服務一次,然後需要相應資源的任何觀察者隻需觀察 LiveData 對象。
2、ViewModel

2.1 ViewModel簡介

Android Jetpack的一部分,用于為界面準備資料

2.1 ViewModel 優勢

  • 讓應用以注重生命周期的方式存儲和管理界面相關的資料
  • 讓資料可在發生螢幕旋轉等配置更改後繼續留存
  • 從界面控制器邏輯中分離出視圖資料所有權,使資料和試圖操作更容易且更高效

二、基礎使用

1、添加依賴
  • 項目build.gradle中
allprojects {
    repositories {
        google()
        jcenter()
    }
}
           
  • app子產品下build.gradle中
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
           
2、代碼實戰

2.1 建立ViewModel類

class NameTestViewModel:ViewModel() {
    val userName:MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}
           

2.2 Activity中使用

  1. 擷取ViewModel執行個體
private val mViewModel: NameTestViewModel by viewModels()
           
  1. 建立觀察者并為viewModel添加觀察者
// 建立observer
        val contentObserver = Observer<String> { content ->
            // 資料變更更新ui
            tvLiveDataContent.text = content
        }

        // 添加觀察者
        mViewModel.userName.observe(this, contentObserver)
           
  1. LiveData 設定、更新、轉換資料
// 設定資料
        btnSetData.setOnClickListener {
           //  mViewModel.userName.value = "張三"
             mViewModel.userName.postValue("張三")
        }

        // 更新資料
        btnChangeData.setOnClickListener {
            mViewModel.userName.value = "李四"
        }

        // 轉換資料
        mapDataFunction()
    }


           
  1. LiveData 轉換資料Map、SwitchMap
  • Map 無需傳回值
private fun mapDataFunction() {
        val mapString=MutableLiveData<String>()
        mapString.postValue("資料")

        // 轉換資料
        btnMapData.setOnClickListener {
            val mapObserver = Transformations.map(mapString) { userName ->
                "我是轉換後的 $userName"
            }
            mapObserver.observe(this){
                tvLiveDataContent.text = it  // text:我是轉換後的資料
            }
        }
    }
           
  • switchMap 經過switchMap轉換後必須有傳回值,傳回值為liveData類型資料
private fun switchMapData() {
        val switchMapString=MutableLiveData<String>()
            switchMapString.value="switchMapData"

        val switchMapStringTwo=MutableLiveData<String>()
        switchMapStringTwo.postValue("switchMapDataTwo")

        btnSwitchMapData.setOnClickListener {
            // 經過switchMap轉換後必須有傳回值,傳回值為liveData類型資料
            val switchMapObserver=Transformations.switchMap(switchMapString){
                // 修改資料
                switchMapStringTwo.postValue(it)
                // 通過switchMap将switchMapStringTwo作為傳回值
                switchMapStringTwo
            }

            switchMapObserver.observe(this){
                tvLiveDataContent.text = it // text:switchMapData
            }

        }
    }
           
  1. LiveData mediatorLiveData 合并多個資料源
private fun mediatorLiveData() {
        val sourceString = MutableLiveData<String>()
        sourceString.value = "sourceLiveDataOne"

        val sourceStringTwo = MutableLiveData<String>()
        sourceStringTwo.postValue("sourceLiveDataTwo")

        val mediatorLiveData = MediatorLiveData<String>()

        // 添加資料源
        mediatorLiveData.addSource(sourceString) {
            mediatorLiveData.value = it
        }
        mediatorLiveData.addSource(sourceStringTwo) {
            mediatorLiveData.value = it
        }

        // 更新資料
        btnMediatorLiveData.setOnClickListener {
            mediatorLiveData.observe(this) {
                tvLiveDataContent.text = it  // text: sourceLiveDataTwo
            }

        }
    }
           

使用mediatorLiveData的addSource添加多個資料源,按照執行先後順序執行,後執行的會覆寫先執行的資料

3、ViewModel 補充

3.1 viewModel生命周期

  • ViewModel 對象存在的時間範圍是擷取 ViewModel 時傳遞給 ViewModelProvider 的 Lifecycle。
  • ViewModel 将一直留在記憶體中,直到限定其存在時間範圍的 Lifecycle 永久消失
  • 對于 activity,是在 activity 完成時;而對于 fragment,是在 fragment 分離時
  • activity中ViewModel生命周期
    LiveData與ViewModel基礎使用篇

3.2 Fragment間使用ViewModel共享資料

  1. 建立兩個Fragment
  2. 擷取viewModel執行個體
  3. 設定監聽
  4. 更新ui
class FirstFragment : Fragment() {
// 擷取viewModel
private val model:DataTestViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return layoutInflater.inflate(R.layout.layout_fragment_first, container, false) }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 設定觀察者
        model.userName.observe(viewLifecycleOwner, Observer {
            // 更新ui
            tvContent.text=it
        })
    }
}

class SecondFragment:Fragment() {
    private val model: DataTestViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return layoutInflater.inflate(R.layout.layout_fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        model.userName.observe(viewLifecycleOwner, Observer {
            tvContent.text=it
        })
    }
}
           
  1. 将fragment添加到activity中
private fun addFragment() {
        val firstFragment=FirstFragment()
        val secondFragment=SecondFragment()
        supportFragmentManager.beginTransaction()
            .add(R.id.flOne,firstFragment)
            .add(R.id.flTwo,secondFragment)
            .commitAllowingStateLoss()
    }
           

因為兩個fragment共享ViewModel,同時實作了監聽,所有當viewmodel中資料發生變化,兩個fragment都能收到響應,實作ui更新

3.3 協程與ViewModel一起使用

  1. 添加KTX依賴
// ktx
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
           
  1. 結合ViewModelScope使用
// ViewModelScope
    init {
        viewModelScope.launch {
            userName.value="defaultValue"
        }
    }
           
  1. Fragment中結合LifecycleScope使用
// lifecycleScope 執行耗時操作
        btnLifecycleScope.setOnClickListener {
            viewLifecycleOwner.lifecycleScope.launch {
                delay(100)
                // 修改資料
                model.userName.postValue("lifecycleScope")
            }
        }
           
  1. 可重新開機生命周期感覺型協程

    在Fragment的onViewCreated()中監聽生命周期變化并列印日志

// 可重新開機生命周期感覺型攜程
        viewLifecycleOwner.lifecycleScope.launch {

            viewLifecycleOwner.whenCreated {
                Log.d(TAG,"lifeCycle onCreated")
            }
            viewLifecycleOwner.whenStarted {
                Log.d(TAG,"lifeCycle onStarted")
            }
            viewLifecycleOwner.whenResumed {
                Log.d(TAG,"lifeCycle onResume")

            }

        }
           
  • 日志列印
D/FirstFragment: lifeCycle onCreated
D/FirstFragment: lifeCycle onStarted
D/FirstFragment: lifeCycle onResume
           

可見,我們通過viewLifecycleOwner觀察到了fragment生命周期的變化。

  1. 挂起生命周期感覺型協程

    即使 CoroutineScope 提供了适當的方法來自動取消長時間運作的操作,在某些情況下,可能需要暫停執行代碼塊(除非 Lifecycle 處于特定狀态),手動在finally釋放資源

private fun hangUpScope() {
        viewLifecycleOwner.lifecycleScope.launch {
            try {
                whenStarted {
                    var success = false
                    // 模拟攜程執行1s耗時操作
                    withContext(Dispatchers.IO) {
                        delay(1000)
                        success = true
                    }
                    if (success) {
                        model.userName.postValue("success")
                    }
                }
            }finally {
                // finally 中釋放資源
                if (lifecycle.currentState>=Lifecycle.State.STARTED){
                    releaseSource()
                }
            }
        }
    }
           

// 釋放資源

private fun releaseSource() {
        Log.d(TAG, "release source")
    }
           
  • 日志列印
D/FirstFragment: release source
           
  1. 結合liveData與協程并通過emit将資料發送出去
  • ViewModel中
// 延遲1s 協程中模拟網絡請求并通過emit将資料發送出去
    val liveDataItem= liveData {
        delay(1000)
        val data="John"
        emit(data)
    }
           
  • Fragment中監聽資料變化
model.liveDataItem.observe(viewLifecycleOwner, Observer {
            Log.d(TAG,it)
        })
           
  • 日志列印
D/FirstFragment: John
           
  1. 使用 liveData 建構器函數調用 suspend 函數,并将結果作為 LiveData 對象傳送
  • 使用liveData在協程中處理資料,并通過emit将結果發出
  • viewModel中代碼
// 延遲1s 協程中模拟網絡請求并通過emit将資料發送出去
    val liveDataItem= liveData {
        delay(1000)
        val data="John"
        emit(data)
    }

    val resultBean= liveData {
        emit(ResultBean(ResultBean.STATUS_LOADING))
        try {
            delay(2000)
            emit(ResultBean(ResultBean.STATUS_SUCCESS))
        }catch (e:Exception){
            emit(ResultBean(ResultBean.STATUS_FAIL))
        }
    }
           
  • Fragment中代碼
private fun liveDataWithRoutineCombination() {
        model.liveDataItem.observe(viewLifecycleOwner, Observer {
            Log.d(TAG,it)
        })

        model.resultBean.observe(viewLifecycleOwner, Observer {
            when(it.status){
                ResultBean.STATUS_LOADING->{
                    Log.d(TAG, "resultBean loading")
                }
                ResultBean.STATUS_SUCCESS->{
                    Log.d(TAG, "resultBean success")
                }
                ResultBean.STATUS_FAIL->{
                    Log.d(TAG, "resultBean fail")
                }
            }
        })
    }
           
  • 日志列印
D/FirstFragment: resultBean loading
D/FirstFragment: John
D/FirstFragment: resultBean success
           
  1. ViewModel 中使用SavedStateHandle儲存狀态

    在onSaveInstance中儲存資料,并在下次重新開機時,通過savedStateHandle讀取資料,類似我們在onSaveInstance()中儲存資料,在onCreate()中通過讀取bundle恢複資料

  • 支援的資料類型

    保留在 SavedStateHandle 中的資料将作為 Bundle 與 Activity 或 Fragment 的 savedInstanceState 的其餘部分一起儲存和恢複

    LiveData與ViewModel基礎使用篇

如果該類沒有擴充上述清單中的任何一項,則應考慮通過添加 @Parcelize Kotlin 注解或直接實作 Parcelable 來使該類變為 Parcelable 類型或者實作Serializable實作序列化

  • 代碼執行個體

    ViewModel注意構造方法中添加SavedStateHandle

class DataViewModelTwo(private val savedStateHandle: SavedStateHandle):ViewModel(){

    private val TAG="tag"

    // 設定query資料
    fun setQuery(query: String) {
        if (savedStateHandle.contains("query")) {
            Log.d(TAG, "contains query==== ${savedStateHandle.get<UserBean>("query")}")
        }else{
            Log.d(TAG,"儲存資料")
            savedStateHandle["query"] = UserBean(userName = query,userAge = 10)
        }
    }

    // 擷取資料
    fun getSavedValue(): UserBean? {
        return savedStateHandle.get<UserBean>("query")
    }
}
           
  • 擷取ViewModel對象
private val model2: DataViewModelTwo by activityViewModels()
           
  • Fragment的onSaveInstance中儲存資料
override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        model2.setQuery("啦啦啦啦")
    }
           
  • 在Fragment中onResume()方法中模拟擷取資料
override fun onResume() {
        super.onResume()
        // saveData
        saveDataFunction()
    }

    private fun saveDataFunction() {
      Log.d(TAG, "query==== ${model2.getSavedValue()}")
    }
           
  • 日志列印
D/tag: query==== null
D/tag: 儲存資料
 D/tag: query==== UserBean(userName=啦啦啦啦, userAge=10)
           

通過上面的例子我們就實作了viewModel儲存資料的效果

  • 參考:

    https://developer.android.google.cn/topic/libraries/architecture/viewmodel?hl=zh_cn

    https://developer.android.google.cn/topic/libraries/architecture/livedata?hl=zh_cn

    https://developer.android.google.cn/topic/libraries/architecture/viewmodel-savedstate?hl=zh_cn