Android单项绑定MVVM项目模板

作者: a49f87ef5d4f | 来源:发表于2019-04-03 10:11 被阅读9次

    0.前言

    事情还要从上周和同事的小聚说起,同事说他们公司现在app的架构模式用的是MVP模式,但是并没有通过泛型和继承等一些列手段强制使用,全靠开发者在Activity或者Fragment里new一个presenter来做处理,说白了,全靠开发者自觉。这引发了我的一个思考,程序的架构或者设计模式的作用,除了传统的做到低耦合高内聚,业务分离,我觉得还有一个更重要的一点就是用来约束开发者,虽然使用某种模式或者架构可能并不会节省代码量,有的甚至会增加编码工作,但是让开发者在一定规则内进行开发,保证一个一致性,尤其是在当一个项目比较大而且需要团队合作的前提情况下,就显得极为重要。前段时间google公布了jetpack,旨在帮助开发者更快的构建一款app,以此为基础我写了这个项目模板做了一些封装,来为以后自己写app的时候提供一个支持。

    1.什么是MVVM

    MVVM这种设计模式和MVP极为相似,只不过Presenter换成了ViewModel,而ViewModel是和View相互绑定的。

    image

    MVP

    image

    MVVM

    我在项目中并没有使用这种标准的双向绑定的MVVM,而是使用了单项绑定的MVVM,通过监听数据的变化,来更新UI,当UI需要改变是,也是通过改变数据后再来改变UI。具体的App架构参考了google的官方文档

    image

    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<R>>,ApiResponse是对返回结果的一层封装,为什么要封这一层,因为我们可能会对网络返回的错误或者一些特殊情况进行特殊处理,这些是可以再ApiResponse里做的,然后看LiveDataCallAdapterFactory,返回一个LiveDataCallAdapter,同时强制你的接口定义的网络请求返回的结果必需是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对返回的结果进行处理,并且将数据转换为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,还有很多不足,后续在应用过程中会逐渐完善。

    image

    关注我的公众号

    5.参考

    https://github.com/googlesamples/android-architecture-components

    相关文章

      网友评论

        本文标题:Android单项绑定MVVM项目模板

        本文链接:https://www.haomeiwen.com/subject/uzhabqtx.html