Android ViewModel與Lifecycles和LiveData組件用法詳細(xì)講解
一、ViewModel
ViewModel 類旨在以注重生命周期的方式存儲和管理界面相關(guān)的數(shù)據(jù)。
ViewModel 類讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存。
簡單的說就是,在android中,當(dāng)Activity重建或銷毀時,頁面上的數(shù)據(jù)會丟失。為了保存頁面的數(shù)據(jù),我們以前通常的做法是在 onSaveInstanceState 中,將數(shù)據(jù)保存到 bundle 中,再在 onCreate 中將 bundle 中的數(shù)據(jù)取出來。
而使用 ViewModel,我們就無需再用這種方法保存,因為 ViewModel 會自動感知生命周期,處理數(shù)據(jù)的保存與恢復(fù)。即數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置(其它例如分辨率調(diào)整、權(quán)限變更、系統(tǒng)字體樣式、語言變更等)更改后繼續(xù)留存。
對于橫豎屏生命周期的總結(jié)是:先銷毀掉原來的生命周期,然后再重新跑一次。
但是,這樣子是不是會有問題呢?有些場景下: 比如說,做游戲開發(fā) 。橫豎屏的切換,生命周期重新加載,那么當(dāng)前頁面的數(shù)據(jù)也會重新開始了。但是ViewModel會保存里面的數(shù)據(jù)。
在切換語言的時候ViewModel也會保存數(shù)據(jù)
Activity等視圖文件中不保存數(shù)據(jù),在ViewModel里面保存數(shù)據(jù)
當(dāng)Activity或fragment被Destory或onCreate時ViewModel數(shù)據(jù)不會丟失
ViewModel基本用法
想要使用ViewModel組件,還需要添加如下依賴:
dependencies {
//ViewModel是LifeCycle的一個組件庫,所以只需要添加LifeCycle庫的依賴即可
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}
通常來講,我們需要給每個Activity和Fragment都創(chuàng)建一個對應(yīng)的ViewModel,因此為MainActivity創(chuàng)建一個對應(yīng)的MainViewModel類,并讓他繼承自ViewModel,代碼如下所示:
class MainViewModel :ViewModel(){ var counter=0 }
現(xiàn)在我們在界面上添加一個按鈕,每點擊一次按鈕就讓計數(shù)器加1,并且把最新的計數(shù)顯示到界面上。修改布局代碼:
package com.example.JetPackTest import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import com.example.kotlintext.R import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { lateinit var viewModel: MainViewModel private val TAG:String="MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d(TAG, "onCreate: ") //viewModel= ViewModelProviders.of(this).get(MainViewModel::class.java) viewModel=ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java) plusOneBtn.setOnClickListener { viewModel.counter++; refreshCounter() } refreshCounter() } private fun refreshCounter() { infoText.text=viewModel.counter.toString() } override fun onDestroy() { super.onDestroy() Log.d(TAG, "onDestroy: ") } override fun onStart() { super.onStart() Log.d(TAG, "onStart: ") } override fun onStop() { super.onStop() Log.d(TAG, "onStop: ") } override fun onRestart() { super.onRestart() Log.d(TAG, "onRestart: ") } override fun onPause() { super.onPause() Log.d(TAG, "onPause: ") } }
首先我們要通過ViewModelProvider來創(chuàng)建ViewModel的實例,之所以這么寫是ViewModel有獨立的生命周期,并且其生命周期要長于Activity。如果我們在onCreate()方法中創(chuàng)建ViewModel的實例,那么每次onCreate()方法執(zhí)行時候,ViewModel都會創(chuàng)建一個新的實例,這樣當(dāng)手機屏幕發(fā)生變化時候,就無法保留其中的數(shù)據(jù)了。
當(dāng)我們旋轉(zhuǎn)一下屏幕,你會發(fā)現(xiàn)Activity雖然重新被創(chuàng)建了,但計數(shù)器的數(shù)據(jù)沒有丟失
向ViewModel傳遞參數(shù)
如果我們需要通過構(gòu)造函數(shù)來傳遞一些參數(shù),需要借助ViewModelProvider.Factory就可以實現(xiàn)。雖然計數(shù)器在屏幕旋轉(zhuǎn)的時候不會丟失數(shù)據(jù),但是如果退出程序之后又重新打開,那么之前的計數(shù)就會被清零。這個時候我們就需要在退出程序的時候?qū)Ξ?dāng)前的計數(shù)進行保存,然后在重新打開程序的時候讀取之前保存的計數(shù),并傳遞給MainViewModel。修改MainViewModel中的代碼,如下所示
class MainViewModel(countReserved:Int) :ViewModel(){ var counter=countReserved }
我們通過給MainViewModel的構(gòu)造函數(shù)添加了一個countReserved參數(shù),這個參數(shù)用于記錄之前保存的計數(shù)值,并在初始化的時候賦值給counter變量。
新建一個MainViewModelFactory類,并讓它實現(xiàn)ViewModelProvider.Factory接口,代碼如下:
class MainViewModelFactory(private val countReserved:Int):ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return MainViewModel(countReserved)as T } }
可以看到MainViewModel.Factory的構(gòu)造函數(shù)中也接收了一個countReserved參數(shù),另外ViewModelProvider.Factory接口要求我們必須實現(xiàn)create()方法,因此這里在create方法中我們創(chuàng)建了MainViewModel的實例,并將countReserved參數(shù)傳了進去。為什么這里就可以創(chuàng)建MainViewModel的實例了呢?因為create()方法的執(zhí)行時機和Activity的生命周期無關(guān),所以不會產(chǎn)生之前提到的問題。
另外,我們在界面上添加一個清零按鈕,方便用戶手動將計數(shù)器清零。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/infoText" android:layout_gravity="center_horizontal" android:textSize="32sp" /> <Button android:id="@+id/plusOneBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Plus One" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/clearBtn" android:layout_gravity="center_horizontal" android:text="clear" /> </LinearLayout>
最后修改MainActivity中的代碼
package com.example.JetPackTest import android.content.Context import android.content.SharedPreferences import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import androidx.lifecycle.ViewModelProvider import com.example.kotlintext.R import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { lateinit var viewModel: MainViewModel lateinit var sp:SharedPreferences private val TAG:String="MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d(TAG, "onCreate: ") sp=getPreferences(Context.MODE_PRIVATE) val countReserved = sp.getInt("count_reserved", 0) viewModel=ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java) plusOneBtn.setOnClickListener { viewModel.counter++; refreshCounter() } clearBtn.setOnClickListener { viewModel.counter=0 refreshCounter() } refreshCounter() } private fun refreshCounter() { infoText.text=viewModel.counter.toString() } override fun onDestroy() { super.onDestroy() Log.d(TAG, "onDestroy: ") } override fun onStart() { super.onStart() Log.d(TAG, "onStart: ") } override fun onStop() { super.onStop() Log.d(TAG, "onStop: ") } override fun onRestart() { super.onRestart() Log.d(TAG, "onRestart: ") } override fun onPause() { super.onPause() Log.d(TAG, "onPause: ") sp.edit { putInt("count_reserved",viewModel.counter) } } }
在onCreate()方法中,我們首先獲取了SharedPreferences的實例,然后讀取之前保存的計數(shù)值,如果沒有讀到的話,就使用0作為默認(rèn)值。接下來在ViewModelProvider方法傳入MainViewModelFactory(countReserved)作為參數(shù),將讀取到的計數(shù)值傳給了MainViewModelFactory的構(gòu)造函數(shù)。
并在onPause()方法中對當(dāng)前的計數(shù)進行保存,這樣可以保證不管程序是退出還是進入后臺,計數(shù)都不會丟失。
二、Lifecycles
在編寫Android應(yīng)用程序的時候,可能經(jīng)常遇到需要感知Activity生命周期的情況。比如,某個頁面中發(fā)起了一條網(wǎng)絡(luò)請求,但是當(dāng)請求得到響應(yīng)的時候,界面或許已經(jīng)關(guān)閉了,這個時候就不應(yīng)該繼續(xù)對響應(yīng)的結(jié)果進行處理。因此我們需要能夠時刻感知到Activity的生命周期,以便在適當(dāng)?shù)臅r候進行相應(yīng)的邏輯控制。
比如有個問題,如果要在一個非Activity的類中去感知Activity的聲明周期,應(yīng)該怎么辦?
可以通過在Activity中嵌入一個隱藏的Fragment來進行感知,或者通過手寫監(jiān)聽器的方式來進行感知。
下面通過監(jiān)聽器的方式來對Activity的生命周期進行感知
class MyObserver{ fun activityStart(){ } fun activityStop(){ } }
class MainActivity:AppCompatActivity(){ lateinit var observer:MyObserver override fun onCreate(savedInstanceState:Bundle?){ observer=MyObserver() } override fun onStart(){ super.onStart() observer.activityStart() } override fun onStop(){ super.onStop() observer.activityStop() } }
這里我們?yōu)榱俗孧yObserver能夠感知到Activity的生命周期,需要專門在MainActivity中重寫相應(yīng)的生命周期方法,然后再通知給MyObserver。這種實現(xiàn)方式需要在Activity中編寫太多額外的邏輯。
而Lifecycles組件就可以在任何一個類中都能輕松感知到Activity的生命周期,同時又不需要在Activity中編寫大量的邏輯處理。
新建一個MyObserver類,并讓它實現(xiàn)LifecycleObserver接口,代碼如下所示:
class MyObserver:LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun activityStart(){ Log.d("MyObserver", "activityStart") } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun activityStop(){ Log.d("MyObserver", "activityStop") } }
可以看到,我們在方法上使用了@OnLifecycleEvent注解,并傳入了一種生命周期事件。生命周期事件的類型一共有7種:ON_CREATE、ON_START、ON_STOP、ON_RESUEM、ON_DESTORY分別匹配Activity中相應(yīng)的聲明周期回調(diào)。另外還有一種ON_ANY類型,表示可以匹配Activity的任何生命周期回調(diào)。
因此,上述代碼中的activityStart()和activityStop()方法就應(yīng)該分別在Activity的onStart()和onStop()觸發(fā)的時候執(zhí)行。
接下來借助LifecycleOwner,可以使用如下的語法結(jié)構(gòu)讓MyObserver得到通知:
lifecycleOwner.lifecycle.addObserver(MyObserver)
首先調(diào)用lifecycleOwner的getLifecycle()方法,得到一個Lifecycle對象,然后調(diào)用它的addObserver()方法來觀察LifecyclerOwner的生命周期,再把MyObserver的實例傳進去就可以了。
又因為我們的Activity是繼承自AppCompatActivity的,或者Fragment繼承自androidx.fragment.app.Fragment,他們本身就是一個LifecycleOwner的實例,這部分工作AndroidX庫自動幫我們完成。所以可以這么寫
class MainActivity : AppCompatActivity() { private val TAG:String="MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycle.addObserver(MyObserver()) } }
加上這一行,MyObserver就能自動感知到Activity的生命周期了。不僅在Activity適用,F(xiàn)ragment也適用。
運行程序,然后切到后臺,再回來的打印輸出
當(dāng)然MyObserver除了感知Activity的生命周期發(fā)生變化,也能夠獲知當(dāng)前的生命周期狀態(tài)。只需要在MyObserver的構(gòu)造函數(shù)中將Lifecycle對象傳進來,如下所示:
class MyObserver(val lifecycle:Lifecycle):LifecycleObserver{ }
有了lifecycle對象之后,我們就可以在任何地方調(diào)用lifecycle.currentState來主動獲知當(dāng)前的生命周期狀態(tài)。lifecycle.currentState返回的生命周期狀態(tài)是一個枚舉類型,一共有DESTROYED,INITIALIZED,CREATED,STARTED,RESUMED這五種狀態(tài)。
也就是說,當(dāng)獲取的生命周期狀態(tài)是CREATED的時候,說明onCreate()方法已經(jīng)執(zhí)行了,但是onStart()方法還沒有執(zhí)行。當(dāng)獲取的生命周期狀態(tài)是STARTED的時候,說明onStart()方法已經(jīng)執(zhí)行了,但是onResume()方法還沒有執(zhí)行。
三、LiveData
LiveData是Jetpack提供的一種響應(yīng)式編程組件,它可以包含任何類型的數(shù)據(jù),并在數(shù)據(jù)發(fā)生變化的時候通知給觀察者。
LiveData的基本用法
之前編寫的計數(shù)器雖然功能簡單,但還是有問題。當(dāng)點擊Plus One按鈕時,都會先給ViewModel中的計數(shù)加1,然后立即獲取最新的計數(shù)。這種方式雖然可以在單線程中正常工作,但如果ViewModel的內(nèi)部開啟了線程去執(zhí)行一些耗時邏輯,那么在點擊按鈕后就立即去獲取最新的數(shù)據(jù),得到的肯定還是之前的數(shù)據(jù)。
之前我們使用都是在Activity中手動獲取ViewModel中的數(shù)據(jù)這種交互方式,但是ViewModel卻無法將數(shù)據(jù)的變化主動通知給Activity。
或許你會把Activity的實例傳給ViewModel,這樣ViewModel不就能主動對Activity進行通知了嗎?但是要知道ViewModel的生命周期是長于Activity的,如果把Activity的實例傳給ViewModel,就很有可能就因為Activity無法釋放而造成內(nèi)存泄露。
如果我們將計數(shù)器的計數(shù)使用LiveData來包裝,然后在Activity中去觀察它,就可以主動將數(shù)據(jù)變化通知給Activity了。
修改MainViewModel中的代碼,如下所示:
class MainViewModel(countReserved:Int) :ViewModel(){ var counter=MutableLiveData<Int>() init { counter.value=countReserved } fun plusOne(){ val count=counter.value?:0 counter.value=count+1 } fun clear(){ counter.value=0 } }
這里我們將counter變量修改成了一個MutableLiveData對象,并指定它的泛型為Int,表示它包含的是整型數(shù)據(jù)。MutableLiveData是一種可變的LiveData,用法很簡單,主要有3種讀寫數(shù)據(jù)的方法,分別是getValue()、setValue()和postValue()方法。getValue()方法用于獲取LiveData中包含的數(shù)據(jù);setValue()方法用于給LiveData設(shè)置數(shù)據(jù),但是只能在主線程中調(diào)用;postValue()方法用于在非主線程中給LiveData設(shè)置數(shù)據(jù)。
這里在init結(jié)構(gòu)體中給counter設(shè)置數(shù)據(jù),這樣之前保存的計數(shù)值劇可以在初始化的時候得到恢復(fù)。接下來新增了plusOne()和clear()這兩個方法,分別用于給計數(shù)加1以及將計數(shù)清零。plusOne()方法中的邏輯是先獲取counter中包含的數(shù)據(jù),然后給它加1,再重新設(shè)置到counter中。調(diào)用LiveData的getValue()方法獲得的數(shù)據(jù)是可能為空的,因此這里使用了一個?:操作符,當(dāng)獲取到的數(shù)據(jù)為空時,就用0來作為默認(rèn)計數(shù)。
修改MainActivity
class MainActivity : AppCompatActivity() { //對變量進行延遲初始化,這樣在就不用先給全局變量賦值,而且在賦值的時候賦值為null,后面還要進行判空 // 如果變量多會比較麻煩 lateinit var viewModel: MainViewModel lateinit var sp: SharedPreferences private val TAG:String="MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) sp=getPreferences(Context.MODE_PRIVATE) val countReserved = sp.getInt("count_reserved", 0) viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java) plusOneBtn.setOnClickListener { viewModel.plusOne() } clearBtn.setOnClickListener { viewModel.clear() } viewModel.counter.observe(this, Observer { count-> infoText.text=count.toString() }) } override fun onPause() { super.onPause() Log.d(TAG, "onPause: ") sp.edit { putInt("count_reserved",viewModel.counter.value?:0) } } }
這里調(diào)用了ViewModel.counter的observe()方法來觀察數(shù)據(jù)的變化。經(jīng)過對MainViewModel的改造,現(xiàn)在counter變量已經(jīng)變成了一個LiveData對象,任何LiveData對象都可以調(diào)用它的observe()方法來觀察數(shù)據(jù)的變化。observe()方法接口接收兩個參數(shù),第一個是一個LifecycleOwner對象,Activity本身就是一個LifecycleOwner對象,因此直接傳this就好;第二個參數(shù)是一個Observer接口,當(dāng)counter中包含的數(shù)據(jù)發(fā)生變化時,就會回調(diào)這里,因此這里將最新的數(shù)據(jù)更新到界面。
為了在非ViewModel中就只能觀察LiveData的數(shù)據(jù)變化,而不能給LiveData設(shè)置數(shù)據(jù),下面改造MainViewModel:
class MainViewModel(countReserved:Int) :ViewModel(){ val counter:LiveData<Int> get() = _counter private val _counter=MutableLiveData<Int>() init { _counter.value=countReserved } fun plusOne(){ val count=_counter.value?:0 _counter.value=count+1 } fun clear(){ _counter.value=0 } }
將原來的counter變量改名為_counter變量,并給它加上private修飾符,這樣_counter變量就對外部不可見了。然后又定義了一個counter變量,將它的類型聲明為不可變的LiveData,并在它的get()屬性方法中返回_counter變量。
這樣,當(dāng)外部調(diào)用counter變量時,實際上獲得的就是_counter的實例,但是無法給counter設(shè)置數(shù)據(jù),從而保證了ViewModel的數(shù)據(jù)封裝性。
map和switchMap
LiveData為了能夠應(yīng)對各種不同的需求場景,提供了兩種轉(zhuǎn)換方法:map()和switchMap()方法。
map()方法,這個方法的作用是將實際包含數(shù)據(jù)的LiveData和僅用于觀察數(shù)據(jù)的LiveData進行轉(zhuǎn)換。
那么什么情況下,會用到這個方法呢?
比如說有個User類,User類包含用戶的姓名和年齡,定義如下:
data class User(var firstName:String,var lastName:String,var age:Int)
我們可以在ViewModel中創(chuàng)建一個相應(yīng)的LiveData來包含User類型的數(shù)據(jù),如下所示:
class MainViewModel(countReserved:Int):ViewModel(){ val userLiveData=MutableLiveData<User>() }
如果MainActivity中明確只會顯示用戶的姓名,而完全不關(guān)心用戶的年齡,這個時候還將User類型的LiveData暴露給外部就不合適了。
而map()方法就是專門解決這個問題,它可以將User類型的LiveData自由地轉(zhuǎn)型成任意其他類型的LiveData
class MainViewModel(countReserved:Int):ViewModel(){ private val userLiveData=MutableLiveData<User>() val userName:LiveData<String> =Transformations.map(userLiveData){user-> "${user.firstName}${user.lastName}" } ... }
這里我們調(diào)用了Transformations的map()方法來對LiveData的數(shù)據(jù)類型進行轉(zhuǎn)換。map()方法接收兩個參數(shù):第一個參數(shù)是原始的LiveData對象;第二個參數(shù)是一個轉(zhuǎn)換函數(shù),我們在轉(zhuǎn)換函數(shù)中編寫具體的轉(zhuǎn)換邏輯即可。這里的邏輯就是將user對象轉(zhuǎn)換為一個只包含用戶姓名的字符串。
另外還將userLiveData聲明成了private,以保證數(shù)據(jù)的封裝性。外部使用的時候只要觀察userName這個LiveData就可以了。當(dāng)userLiveData的數(shù)據(jù)發(fā)生變化時,map()方法會監(jiān)聽到變化并執(zhí)行轉(zhuǎn)換函數(shù)的邏輯,然后再將轉(zhuǎn)換之后的數(shù)據(jù)通知給userName的觀察者。
switchMap()方法的使用場景比較固定:如果ViewModel中的某個LiveData對象是調(diào)用另外的方法獲取的,那么我們就可以借助switchMap()方法,將這個LiveData對象轉(zhuǎn)換成另一個可觀察的LiveData對象。
比如:LiveData對象的實例都是在ViewModel中創(chuàng)建的,然而在實際的項目中,不可能一直都是這種理想情況,很有可能ViewModel中的某個LiveData對象是調(diào)用另外的方法獲取的。
新建一個Repository單例類,代碼如下所示:
object Repository { fun getUser(userId:String):LiveData<User>{ val liveData=MutableLiveData<User>() liveData.value=User(userId,userId,0) return liveData } }
這里在Repository類中添加了一個getUser()方法,這個方法接收一個userId參數(shù)。每次將傳入的userId當(dāng)做用戶姓名來創(chuàng)建一個新的User對象。
getUser()方法返回的是一個包含User數(shù)據(jù)的LiveData對象,而且每次調(diào)用getUser()方法都會返回一個新的LiveData實例。
然后再MainViewModel中也定義一個getUser()方法,并且讓它調(diào)用Repository的getUser()方法來獲取LiveData對象:
class MainViewModel(countReserved:Int) :ViewModel(){ fun getUser(userId:String):LiveData<User>{ return Repository.getUser(userId) } }
接下來的問題是,在Activity中如何觀察LiveData的數(shù)據(jù)變化呢?既然getUser()方法返回的是一個LiveData對象,那么我們可不可以直接在Activity中使用如下寫法呢?
viewModel.getUser(userId).observe(this) {user-> }
因為每次調(diào)用getUser()返回的都是一個新的LiveData實例,而上述寫法會一直觀察老的LiveData實例,從而根本無法觀察到數(shù)據(jù)的變化,會發(fā)現(xiàn)這種情況下LiveData是不可觀察的。
這個時候switchMap()方法就可以派上用場了。
修改MainViewModel中的代碼:
class MainViewModel(countReserved:Int) :ViewModel(){ private val userIdLiveData=MutableLiveData<String>() val user:LiveData<User> =Transformations.switchMap(userIdLiveData){ userId -> Repository.getUser(userId)//此時的userId就是userIdLiveData的類型對象String } fun getUser(userId:String){ userIdLiveData.value=userId } }
定義了一個新的userIdLiveDat對象,用來觀察userId的數(shù)據(jù)變化,然后調(diào)用了Transformations的switchMap()方法,用來對另一個可觀察的LiveData對象進行轉(zhuǎn)換。
switchMap()方法同樣接收兩個參數(shù):第一個參數(shù)傳入我們新增的userIdLiveData,switchMap()方法會對它進行觀察;第二個參數(shù)是一個轉(zhuǎn)換函數(shù),注意:我們必須在這個轉(zhuǎn)換函數(shù)中返回一個LiveData對象,因為switchMap()方法的工作原理就是將轉(zhuǎn)換函數(shù)中返回LiveData對象轉(zhuǎn)換為另一個可觀察的LiveData對象。我們只需要在轉(zhuǎn)換函數(shù)中調(diào)用Respository的getUser()方法來得到LiveData對象,將其返回。
首先,當(dāng)外部調(diào)用MainViewModel的getUser()方法來獲取用戶數(shù)據(jù)時,并不會發(fā)起任何請求或者函數(shù)調(diào)用,只會傳入userId的值設(shè)置到userIdLiveData中。一旦userIdLiveData的數(shù)據(jù)發(fā)生變化,那么觀察userIdLiveData的switchMap()方法就會執(zhí)行,并且調(diào)用我們編寫的轉(zhuǎn)換函數(shù)。然后在轉(zhuǎn)換函數(shù)中調(diào)用Repository.getUser()方法獲取真正的用戶數(shù)據(jù)。同時,switchMap()方法會將Repository.getUser()方法返回的LiveData對象轉(zhuǎn)換成一個可觀察的LiveData對象。對于Activity只需要觀察這個LiveData對象就可以了。
修改activity_main.xml文件,新增一個Get User按鈕
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/getUserBtn" android:layout_gravity="center_horizontal" android:text="Get User" /> </LinearLayout>
修改MainActivity的代碼:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java) getUserBtn.setOnClickListener { val userId=(0..10000).random().toString() viewModel.getUser(userId) } viewModel.user.observe(this, Observer { user -> infoText.text=user.firstName }) } }
通過Get User按鈕的點擊事件中使用隨機函數(shù)生成一個userId,然后調(diào)用MainViewModel的getUser()方法來獲取用戶數(shù)據(jù),但是這個方法不會有返回值。等數(shù)據(jù)獲取完后,可觀察LiveData對象的observe()方法將會得到通知,我們在這里將獲取的用戶名顯示到界面上。
LiveData內(nèi)部不會判斷即將設(shè)置的數(shù)據(jù)和原有數(shù)據(jù)是否相同,只是調(diào)用了setValue()或postValue()方法,就一定會觸發(fā)數(shù)據(jù)變化事件。
如果Activity處于不可見狀態(tài)的時候(手機息屏,或者被其他的Activity遮擋),LiveData發(fā)生了多次數(shù)據(jù)變化,當(dāng)Activity恢復(fù)可見狀態(tài)時,只有最新的那份數(shù)據(jù)才會通知給觀察者,前面的數(shù)據(jù)在這種情況下相當(dāng)于已經(jīng)過期了,會被直接丟棄。
到此這篇關(guān)于Android ViewModel與Lifecycles和LiveData組件用法詳細(xì)講解的文章就介紹到這了,更多相關(guān)Android ViewModel Lifecycles LiveData內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android 基于google Zxing實現(xiàn)二維碼、條形碼掃描,仿微信二維碼掃描效果(推薦)
這篇文章主要介紹了 Android 基于google Zxing實現(xiàn)二維碼、條形碼掃描,仿微信二維碼掃描效果,非常不錯,具有參考借鑒價值,需要的朋友參考下2017-01-01使用Android的OkHttp包實現(xiàn)基于HTTP協(xié)議的文件上傳下載
OkHttp(GitHub主頁https://github.com/square/okhttp)是近來人氣攀升的一款安卓第三方HTTP包,這里我們來講解一下如何使用Android的OkHttp包實現(xiàn)基于HTTP協(xié)議的文件上傳下載:2016-07-07Android studio設(shè)置指定的簽名文件教程
這篇文章主要介紹了Android studio設(shè)置指定的簽名文件教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android如何使用Bmob后端云實現(xiàn)失物招領(lǐng)功能
這篇文章主要介紹了Android如何使用Bmob后端云實現(xiàn)失物招領(lǐng)功能,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03Android定時器實現(xiàn)的幾種方式整理及removeCallbacks失效問題解決
本文為大家詳細(xì)介紹下Android 定時器實現(xiàn)的幾種方式:Handler + Runnable、Timer的方式、Handle與線程的sleep(long )方法和removeCallbacks失效問題如何解決2013-06-06Android RadioGroup 設(shè)置某一個選中或者不可選中的方法
下面小編就為大家?guī)硪黄狝ndroid RadioGroup 設(shè)置某一個選中或者不可選中的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04