Android單項綁定MVVM項目模板的方法
0.前言
事情還要從上周和同事的小聚說起,同事說他們公司現(xiàn)在app的架構模式用的是MVP模式,但是并沒有通過泛型和繼承等一些列手段強制使用,全靠開發(fā)者在Activity或者Fragment里new一個presenter來做處理,說白了,全靠開發(fā)者自覺。這引發(fā)了我的一個思考,程序的架構或者設計模式的作用,除了傳統(tǒng)的做到低耦合高內聚,業(yè)務分離,我覺得還有一個更重要的一點就是用來約束開發(fā)者,雖然使用某種模式或者架構可能并不會節(jié)省代碼量,有的甚至會增加編碼工作,但是讓開發(fā)者在一定規(guī)則內進行開發(fā),保證一個一致性,尤其是在當一個項目比較大而且需要團隊合作的前提情況下,就顯得極為重要。前段時間google公布了jetpack,旨在幫助開發(fā)者更快的構建一款app,以此為基礎我寫了這個項目模板做了一些封裝,來為以后自己寫app的時候提供一個支持。
1.什么是MVVM
MVVM這種設計模式和MVP極為相似,只不過Presenter換成了ViewModel,而ViewModel是和View相互綁定的。

MVP

MVVM
我在項目中并沒有使用這種標準的雙向綁定的MVVM,而是使用了單項綁定的MVVM,通過監(jiān)聽數(shù)據(jù)的變化,來更新UI,當UI需要改變是,也是通過改變數(shù)據(jù)后再來改變UI。具體的App架構參考了google的官方文檔

2.框架組合
整個模板采用了Retrofit+ViewModel+LiveData的這樣組合,Retrofit用來進行網(wǎng)絡請求,ViewModel用來進行數(shù)據(jù)存儲于復用,LiveData用來通知UI數(shù)據(jù)的變化。本篇文章假設您已經(jīng)熟悉了ViewModel和LiveData。
3.關鍵代碼分析
3.1Retrofit的處理
首先,網(wǎng)絡請求我們使用的是Retrofit,Retrofit默認返回的是Call,但是因為我們希望數(shù)據(jù)的變化是可觀察和被UI感知的,為此需要使用LiveData進行對數(shù)據(jù)的包裹,這里不對LiveData進行詳細解釋了,只要記住他是一個可以在Activity或者Fragment生命周期可以被觀察變化的數(shù)據(jù)結構即可。大家都知道,Retrofit是通過適配器來決定網(wǎng)絡請求返回的結果是Call還是什么別的的,為此我們就需要先寫返回結果的適配器,來返回一個LiveData
class LiveDataCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (CallAdapter.Factory.getRawType(returnType) != LiveData::class.java) {
return null
}
val observableType = CallAdapter.Factory.getParameterUpperBound(0, returnType as ParameterizedType)
val rawObservableType = CallAdapter.Factory.getRawType(observableType)
if (rawObservableType != ApiResponse::class.java) {
throw IllegalArgumentException("type must be a resource")
}
if (observableType !is ParameterizedType) {
throw IllegalArgumentException("resource must be parameterized")
}
val bodyType = CallAdapter.Factory.getParameterUpperBound(0, observableType)
return LiveDataCallAdapter<Any>(bodyType)
}
}
class LiveDataCallAdapter<R>(private val responseType: Type) : CallAdapter<R, LiveData<ApiResponse<R>>> {
override fun responseType() = responseType
override fun adapt(call: Call<R>): LiveData<ApiResponse<R>> {
return object : LiveData<ApiResponse<R>>() {
private var started = AtomicBoolean(false)
override fun onActive() {
super.onActive()
if (started.compareAndSet(false, true)) {
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
postValue(ApiResponse.create(response))
}
override fun onFailure(call: Call<R>, throwable: Throwable) {
postValue(ApiResponse.create(throwable))
}
})
}
}
}
}
}
首先看LiveDataCallAdapter,這里在adat方法里我們返回了一個LiveData<ApiResponse<R>> ,ApiResponse是對返回結果的一層封裝,為什么要封這一層,因為我們可能會對網(wǎng)絡返回的錯誤或者一些特殊情況進行特殊處理,這些是可以再ApiResponse里做的,然后看LiveDataCallAdapterFactory,返回一個LiveDataCallAdapter,同時強制你的接口定義的網(wǎng)絡請求返回的結果必需是LiveData<ApiResponse<R>>這種結構。使用的時候
.object GitHubApi {
var gitHubService: GitHubService = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build().create(GitHubService::class.java)
}
interface GitHubService {
@GET("users/{login}")
fun getUser(@Path("login") login: String): LiveData<ApiResponse<User>>
}
3.2對ApiResponse的處理
這里用NetWorkResource對返回的結果進行處理,并且將數(shù)據(jù)轉換為Resource并包入LiveData傳出去。
abstract class NetWorkResource<ResultType, RequestType>(val executor: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
val dbData=loadFromDb()
if (shouldFetch(dbData)) {
fetchFromNetWork()
}
else{
setValue(Resource.success(dbData))
}
}
private fun setValue(resource: Resource<ResultType>) {
if (result.value != resource) {
result.value = resource
}
}
private fun fetchFromNetWork() {
val networkLiveData = createCall()
result.addSource(networkLiveData, Observer {
when (it) {
is ApiSuccessResponse -> {
executor.diskIO().execute {
val data = processResponse(it)
executor.mainThread().execute {
result.value = Resource.success(data)
}
}
}
is ApiEmptyResponse -> {
executor.diskIO().execute {
executor.mainThread().execute {
result.value = Resource.success(null)
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.value = Resource.error(it.errorMessage, null)
}
}
})
}
fun asLiveData() = result as LiveData<Resource<ResultType>>
abstract fun onFetchFailed()
abstract fun createCall(): LiveData<ApiResponse<RequestType>>
abstract fun processResponse(response: ApiSuccessResponse<RequestType>): ResultType
abstract fun shouldFetch(type: ResultType?): Boolean
abstract fun loadFromDb(): ResultType?
}
這是一個抽象類,關注一下它的幾個抽象方法,這些抽象方法決定了是使用緩存數(shù)據(jù)還是去網(wǎng)路請求以及對網(wǎng)絡請求返回結果的處理。其中的AppExecutor是用來處理在主線程更新LiveData,在子線程處理網(wǎng)絡請求結果的。
之后只需要在Repository里直接返回一個匿名內部類,復寫相應的抽象方法即可。
class UserRepository {
private val executor = AppExecutors()
fun getUser(userId: String): LiveData<Resource<User>> {
return object : NetWorkResource<User, User>(executor) {
override fun shouldFetch(type: User?): Boolean {
return true
}
override fun loadFromDb(): User? {
return null
}
override fun onFetchFailed() {
}
override fun createCall(): LiveData<ApiResponse<User>> = GitHubApi.gitHubService.getUser(userId)
override fun processResponse(response: ApiSuccessResponse<User>): User {
return response.body
}
}.asLiveData()
}
}
3.3對UI的簡單封裝
abstract class VMActivity<T : BaseViewModel> : BaseActivity() {
protected lateinit var mViewModel: T
abstract fun loadViewModel(): T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = loadViewModel()
lifecycle.addObserver(mViewModel)
}
}
這里通過使用集成和泛型,強制開發(fā)者在繼承這個類時返回一個ViewMode。
在使用時如下。
class MainActivity : VMActivity<MainViewModel>() {
override fun loadViewModel(): MainViewModel {
return MainViewModel()
}
override fun getLayoutId(): Int = R.layout.activity_main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel.loginResponseLiveData.observe(this, Observer {
when (it?.status) {
Status.SUCCESS -> {
contentTV.text = it.data?.reposUrl
}
Status.ERROR -> {
contentTV.text = "error"
}
Status.LOADING -> {
contentTV.text = "loading"
}
}
})
loginBtn.setOnClickListener {
mViewModel.login("skateboard1991")
}
}
}
4.github地址
整個項目就是一個Git的獲取用戶信息的一個簡易demo,還有很多不足,后續(xù)在應用過程中會逐漸完善。
5.參考
https://github.com/googlesamples/android-architecture-components
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。
相關文章
Android通過自定義控件實現(xiàn)360軟件詳情頁效果
這篇文章主要給大家介紹了Android通過自定義控件實現(xiàn)360軟件詳情頁效果的相關資料,實現(xiàn)后的效果非常不錯,文中也給出了詳細的示例代碼和介紹,需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05
android studio git 刪除已在遠程倉庫的文件或文件夾方式
這篇文章主要介紹了android studio git 刪除已在遠程倉庫的文件或文件夾方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
Android開發(fā)模仿qq視頻通話懸浮按鈕(實例代碼)
這篇文章主要介紹了Android開發(fā)模仿qq視頻通話懸浮按鈕功能的實例代碼,需要的的朋友參考下2017-02-02

