該系列文章為自學過程中的產(chǎn)出,若有錯誤,希望熱心路人不吝賜教
之前的文章Android學習之MVVM簡談過MVVM,這章著重講一下如何在Android的代碼中表現(xiàn)ViewModel
Responsibility(職責)
既然View只管視圖相關的邏輯,Model只管數(shù)據(jù)與規(guī)范,那ViewModel就注定要做最臟最累的活了(名字長的代價)通孽。
網(wǎng)絡調(diào)用誰做?ViewModel做睁壁!
IO誰做背苦?ViewModel做!
保存分頁參數(shù)誰做潘明?ViewModel做糠惫!
1+1=?誰做?還是ViewModel做钉疫!
ViewModel code
貼一段Google的sample code:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.google.samples.apps.sunflower.data.UnsplashPhoto
import com.google.samples.apps.sunflower.data.UnsplashRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@HiltViewModel // hilt依賴,修改這個class巢价,告訴編譯器牲阁,這個class是一個ViewModel
class GalleryViewModel @Inject constructor( // inject固阁,告訴編譯器,這個class構造器里面的內(nèi)容被我注入了城菊,麻煩幫我初始化它
private val repository: UnsplashRepository // ViewModel是苦力备燃,需要去倉庫里面取一些需要的東西
) : ViewModel() {
private var currentQueryValue: String? = null // 把搜索的參數(shù)保存起來
private var currentSearchResult: Flow<PagingData<UnsplashPhoto>>? = null // 把搜索的結果保存起來
fun searchPictures(queryString: String): Flow<PagingData<UnsplashPhoto>> {
currentQueryValue = queryString
val newResult: Flow<PagingData<UnsplashPhoto>> =
repository.getSearchResultStream(queryString).cachedIn(viewModelScope)
currentSearchResult = newResult
return newResult
}
}
在代碼中,看到了構造器中注入了一個Repository凌唬,那這個Repository在我們的開發(fā)中又擔任了什么角色呢并齐?
Repository code
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.google.samples.apps.sunflower.api.UnsplashService
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class UnsplashRepository @Inject constructor(private val service: UnsplashService) {
fun getSearchResultStream(query: String): Flow<PagingData<UnsplashPhoto>> {
return Pager(
config = PagingConfig(enablePlaceholders = false, pageSize = NETWORK_PAGE_SIZE),
pagingSourceFactory = { UnsplashPagingSource(service, query) }
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 25
}
}
這時候我們看到repository里面又注入了一個service,這么復雜客税,又不知道是用來做什么的况褪,為什么不能直接在ViewModel里面直接實現(xiàn)searchPictures的邏輯,何必又搞個Repository多此一舉呢更耻?不急测垛,再看另一個類型的Repository
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository module for handling data operations.
*
* Collecting from the Flows in [PlantDao] is main-safe. Room supports Coroutines and moves the
* query execution off of the main thread.
*/
@Singleton
class PlantRepository @Inject constructor(private val plantDao: PlantDao) {
fun getPlants() = plantDao.getPlants()
fun getPlant(plantId: String) = plantDao.getPlant(plantId)
fun getPlantsWithGrowZoneNumber(growZoneNumber: Int) =
plantDao.getPlantsWithGrowZoneNumber(growZoneNumber)
}
這個Repository結構也類似,看到注釋里面給我們解釋了一下:
Repository模塊是用來處理數(shù)據(jù)操作的
所以這時候我們可以假設有這么一個場景:有個用戶登錄界面秧均,用戶登錄成功后食侮,需要查詢到最近未讀的消息,然后在主頁面顯示出來目胡。
那么按照正常的MVVM的App Architecture來分析的話就是:
首先View(Activity / Fragment)填寫用戶名密碼后通知ViewModel更改了Model锯七,然后觸發(fā)登錄按鈕,這時候應該發(fā)出第一個api請求了誉己。我們說過翎蹈,網(wǎng)絡調(diào)用也同樣是ViewModel先處理的箱季,這時候ViewModel來到跟用戶有關的Repository,這個Repository有登錄、注冊蛤高、重置密碼等函數(shù),ViewModel把Model給了Repository复颈,并點名需要調(diào)用登錄有關的函數(shù)傅瞻。Repository表示他只負責處理數(shù)據(jù),并不發(fā)送請求疼阔,所以又把請求和Model繼續(xù)交給了他的小弟戒劫,Service / DAO,Service在發(fā)出請求之后婆廊,把結果反饋給了他的直屬上級Repository迅细,Repository轉交給了ViewModel,ViewModel把結果填充到了合適的Model淘邻,然后通知View進行了場景的跳轉(navigate)茵典。
跟著流程圖應該會更容易理解一些,差不多就是這樣了宾舅。