Android單項繫結MVVM專案模板

滑板上的老砒霜發表於2019-04-03

0.前言

事情還要從上週和同事的小聚說起,同事說他們公司現在app的架構模式用的是MVP模式,但是並沒有通過泛型和繼承等一些列手段強制使用,全靠開發者在Activity或者Fragment裡new一個presenter來做處理,說白了,全靠開發者自覺。這引發了我的一個思考,程式的架構或者設計模式的作用,除了傳統的做到低耦合高內聚,業務分離,我覺得還有一個更重要的一點就是用來約束開發者,雖然使用某種模式或者架構可能並不會節省程式碼量,有的甚至會增加編碼工作,但是讓開發者在一定規則內進行開發,保證一個一致性,尤其是在當一個專案比較大而且需要團隊合作的前提情況下,就顯得極為重要。前段時間google公佈了jetpack,旨在幫助開發者更快的構建一款app,以此為基礎我寫了這個專案模板做了一些封裝,來為以後自己寫app的時候提供一個支援。

1.什麼是MVVM

MVVM這種設計模式和MVP極為相似,只不過Presenter換成了ViewModel,而ViewModel是和View相互繫結的。

Android單項繫結MVVM專案模板

MVP

Android單項繫結MVVM專案模板

MVVM

我在專案中並沒有使用這種標準的雙向繫結的MVVM,而是使用了單項繫結的MVVM,通過監聽資料的變化,來更新UI,當UI需要改變是,也是通過改變資料後再來改變UI。具體的App架構參考了google的官方文件

Android單項繫結MVVM專案模板

2.框架組合

整個模板採用了Retrofit+ViewModel+LiveData的這樣組合,Retrofit用來進行網路請求,ViewModel用來進行資料儲存於複用,LiveData用來通知UI資料的變化。本篇文章假設您已經熟悉了ViewModel和LiveData。

3.關鍵程式碼分析

3.1Retrofit的處理

首先,網路請求我們使用的是Retrofit,Retrofit預設返回的是Call,但是因為我們希望資料的變化是可觀察和被UI感知的,為此需要使用LiveData進行對資料的包裹,這裡不對LiveData進行詳細解釋了,只要記住他是一個可以在Activity或者Fragment生命週期可以被觀察變化的資料結構即可。大家都知道,Retrofit是通過介面卡來決定網路請求返回的結果是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>,ApiResponse是對返回結果的一層封裝,為什麼要封這一層,因為我們可能會對網路返回的錯誤或者一些特殊情況進行特殊處理,這些是可以再ApiResponse裡做的,然後看LiveDataCallAdapterFactory,返回一個LiveDataCallAdapter,同時強制你的介面定義的網路請求返回的結果必需是LiveData<ApiResponse>這種結構。使用的時候

.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對返回的結果進行處理,並且將資料轉換為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?

}

複製程式碼

這是一個抽象類,關注一下它的幾個抽象方法,這些抽象方法決定了是使用快取資料還是去網路請求以及對網路請求返回結果的處理。其中的AppExecutor是用來處理在主執行緒更新LiveData,在子執行緒處理網路請求結果的。 之後只需要在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)
    }
}
複製程式碼

這裡通過使用整合和泛型,強制開發者在繼承這個類時返回一個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地址

Github 整個專案就是一個Git的獲取使用者資訊的一個簡易demo,還有很多不足,後續在應用過程中會逐漸完善。

Android單項繫結MVVM專案模板

關注我的公眾號

5.參考

github.com/googlesampl…

相關文章