此次的實戰篇,是這個系列的最後一篇。本文綜合前幾篇的內容,以僞代碼爲主,幫你們理解Google所推崇的MVVM。java
一點點入坑JetPack:實戰前戲NetworkBoundResource篇緩存
相信有耐心看到這的小夥伴,徹底足以經過僞代碼,感覺出來如下代碼的設計思路。Go~app
上代碼以前,咱們思考一個小問題。咱們平時的業務,很重的一個部分是從一個地方獲取數據,而後在UI上展現出來。所以,本節實戰部分的背景:從網絡獲取一批數據,若是網絡請求成功,便更新到RecycleView上;若是網絡請求不成功,加載本地已有緩存,而後更新到RecycleView上。ide
是否是很簡單的需求,不少小夥伴可能順手就能寫出來:函數式編程
// 網絡請求
loadNetwork(參數, Callback(){
// 請求成功,更新UI
success(data){
recyclerview.setData
}
// 請求失敗,讀取緩存
error(){
loadDB(參數,Callback(){
// 緩存讀取成功,更新UI
success(data){
recyclerview.setData
}
})
}
})
複製代碼
很是直觀且易閱讀。咱們在深刻想一下,若是其餘頁面也有這樣的需求,是否是也要寫一份這個內容?函數
這裏確定有小夥伴會指出,應該進行封裝!沒錯,還記得上一篇文章提到的NetworkBoundResource
嗎?接下來,就讓咱們經過NetworkBoundResource
,使用MVVM的思想去封裝這個業務。
針對MVVM官方提供的一張比較清晰的流程圖:
按照官方的推薦,咱們須要一個Repository做爲整個數據層的管理者。
例如,咱們設計一個加載歌曲信息,而後更新到RecycleView上的需求。這個Repository我們就叫,MusicRepository,表示音樂相關的數據獲取交由這個類去管理。
那麼這個Repository是什麼樣子的呢?
// 這裏的三個參數,分別是:線程池,緩存模塊,網絡模塊
class MusicRepository(
val appExecutors: AppExecutors,
val musicDao: MusicDao, // 後文會展開這個類
val service: MusicApiService // 後文會展開這個類(具體的請求模塊)
) {
companion object {
val inst: MusicRepository by lazy {
// 這裏傳入的內容,固然是業務方本身去實現,好比這前業務已經存在的DB/內存緩存模塊;封裝好的網絡請求模塊,好比OkHttp/Retrofit等等
MusicRepository(xxx,xxx,xxx)
}
}
// Parameter會在後續中展開
fun querySongs(parameter : Parameter): LiveData<Resource<MusicResp>> {
return object :
NetworkBoundResource<MusicResp, MusicResp>(
appExecutors
) {
override fun saveCallResult(item: MusicResp) {
// 網絡請求成功,先存入緩存模塊
musicDao.saveDB(item)
}
override fun shouldFetch(data: MusicResp?): Boolean {
return 本身的是否請求網絡策略
}
override fun loadFromDb(): LiveData<MusicResp> {
return musicDao.getCacheMusicResp(parameter.categoryId)
}
override fun createCall(): LiveData<ApiResponse<MusicResp>> {
// 調用網絡模塊的請求實現
return service.querySongs(parameter)
}
}.asLiveData()
}
}
複製代碼
接下來我們挨個展開上述代碼中用到的類,MusicDao一個負責咱們的Cache的實現類:
object MusicDao {
private val musicStoreSongs: MutableMap<Long, MusicResp> by lazy {
mutableMapOf<Long, MusicResp>()
}
fun updateSongsCache(categoryId: Long, data: MusicResp) {
musicStoreSongs[categoryId] = data
}
fun querySongsCache(categoryId: Long): LiveData<MusicResp> {
val cacheSongLiveData = MutableLiveData<MusicResp>()
cacheSongLiveData.value = musicStoreSongs[categoryId]
return cacheSongLiveData
}
}
複製代碼
這裏僅僅是實現了一套內存緩存。基於此咱們還能夠實現本身的數據庫緩存,或者內存+數據庫的二級緩存。而這一切的實現並不會對外邊的邏輯產生影響,作到了實現的隔離。
接下來,我們來看看網絡請求的實現類:MusicApiService
這裏涉及了協程的內容,建議沒有相關基礎的小夥伴,能夠看一看我以前寫過的文章。
object MusicApiService {
override fun querySongs(parameter : Parameter): LiveData<ApiResponse<MusicResp>> {
val liveData = MutableLiveData<ApiResponse<MusicResp>>()
CoroutineScope(FastMain).launch {
val resp = resp = withContext(BuzzApiPool) {
// 這裏對應的是業務方本身的網絡實現封裝
val np = NetWorkManager.getInstance().networkProvider
val builder = Uri.parse("服務端的請求接口")
.buildUpon()
builder.appendQueryParameter("category_id", parameter.categoryId)
try {
// 本身封裝的get請求
val json = np.networkClient.get(builder.toString())
// 這裏封裝的是Gson把String轉成JavaBean的方法
val data: MusicResp = fromServerResp(json)
data
} catch (e: Exception) {
MusicResp(e)
}
if (resp.isSuccess)) {
liveData.postValue(ApiSuccessResponse(resp))
} else {
liveData.postValue(
ApiErrorResponse(resp.exception ?: RuntimeException("unknown_error"))
)
}
}
return liveData
}
}
複製代碼
有了Repository以後,咱們則須要考慮一下ViewModel了。就叫MusicViewModel
class MusicViewModel :ViewModel(){
// Parameter 僞碼
var parameter = MutableLiveData<Parameter>()
val data : LiveData<Resource<MusicResp>> = Transformations.switchMap(parameter) { parameter->
MusicRepository.inst.querySongs(parameter)
}
}
複製代碼
ViewModel這樣就夠了,接下來就是咱們的UI,這裏就叫MusicActivity
吧。
class MusicActivity : AppCompatActivity(){
private lateinit var musicViewModel: MusicViewModel
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.xxx)
musicViewModel = ViewModelProviders.of(this).get(MusicViewModel::class.java)
musicViewModel.data.observe(this, Observer { musicResp->
// 這裏監聽的數據就是MusicRepository返回的MusicResp
adapter.setData(musicResp)
}
// 經過LiveData通知MusicRepository進行網絡請求
musicViewModel.parameter.value=Parameter(categoryId = xx) //本次請求的參數
}
}
複製代碼
到這裏,咱們最基本的使用,就完成了。
對於UI層來講:
Transformations.switchMap(參數)
會收到變換而後執行MusicRepository.inst.querySongs(請求參數)
,以後的全部邏輯所有交由MusicRepository
去處理。musicViewModel.data
的結果,更新UI便可。這樣你會發現,對於Activity/Fragment來講,它就只是View層了,一點邏輯操做都沒有。
固然這是理想狀態,畢竟PM擁有無窮的想象力,什麼樣的需求都會存在。
我猜理解清楚這套設計的小夥伴,必定會之處問題所在。那就是:
adapter.setData(musicResp)
,這就意味着,每次數據回調回來RecycleView都會更新,這樣就產生了不少無用的刷新。 並且這裏是監聽這個數據對象,若是想進行局部刷新,那麼Activity/Fragment中勢必要作不少額外的邏輯操做...
沒錯!這是一個嚴重的問題,但實際上Google早在好久以前就提供了一個類DiffUtil
,這個類能夠說完美的幫咱們在這套設計裏,搞定了RecycleView空刷的性能消耗。
若是有必要,下篇文章能夠聊一聊
DiffUtil
和Immutable、Mutable的理念
畢竟有些時候,咱們沒辦法這麼直來直去的加載數據。更多的時候,咱們須要在業務回來時進行一系列的額外代碼:好比數據的變換、邏輯的判斷...
數據變換:這類操做,可使用函數式編程的思想,很方便的在ViewModel中完成並經過LiveData通知給observe方。
邏輯的判斷:這部份內容,並不屬於MVVM(數據驅動)的部分。因此至於它還須要仁者見仁智者見智的封裝...
想了好久,仍是以爲在此就停下實戰篇的內容。由於我覺得這已經夠了,若是能消化這整個系列的內容,我相信該怎麼使用JetPack,小夥伴們心中已經有了本身的想法~
固然,小夥伴們若是有什麼更騷的操做,歡迎留言交流呦~
JetPack系列的文章,到此便告一段落了。不知道一路追過來的朋友們是否有收穫。
下一個長篇系列會是什麼內容,暫時尚未想好。你們有啥感興趣的,能夠留言給點建議~