服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android单项绑定MVVM项目模板的方法

Android单项绑定MVVM项目模板的方法

2022-10-11 15:51滑板上的老砒霜 Android

这篇文章主要给大家介绍了关于Android单项绑定MVVM项目模板的相关资料,文中通过示例代码介绍的非常详细,对各位Android开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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>>这种结构。使用的时候

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.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传出去。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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里直接返回一个匿名内部类,复写相应的抽象方法即可。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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的简单封装

?
1
2
3
4
5
6
7
8
9
10
11
12
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。

在使用时如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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,还有很多不足,后续在应用过程中会逐渐完善。

5.参考

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

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。

原文链接:https://www.jianshu.com/p/e112d874b94c

延伸 · 阅读

精彩推荐
  • AndroidAndroid编程之软件的安装和卸载方法

    Android编程之软件的安装和卸载方法

    这篇文章主要介绍了Android编程之软件的安装和卸载方法,涉及Android编程实现软件的安装、权限修改及卸载的相关技巧,具有一定参考借鉴价值,需要的朋友可...

    wiseideal3992021-04-18
  • AndroidAndroid中自定义标题栏样式的两种方法

    Android中自定义标题栏样式的两种方法

    这篇文章主要介绍了Android中自定义标题栏样式的两种方法,同时讲解了自定义标题栏布局的实现,需要的朋友可以参考下...

    Android开发网10452021-03-03
  • AndroidAndroid实现换肤的两种思路分析

    Android实现换肤的两种思路分析

    这篇文章主要介绍了Android实现换肤的两种思路分析,较为详细的分析了Android实现换肤的具体方法,需要的朋友可以参考下...

    mer12345678732021-04-20
  • Android解决PhoneGap不支持viewport的几种方法

    解决PhoneGap不支持viewport的几种方法

    今天小编就为大家分享一篇关于解决PhoneGap不支持viewport的几种方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随...

    李先静6582022-09-02
  • AndroidAndroid编程获取通知栏高度的方法

    Android编程获取通知栏高度的方法

    这篇文章主要介绍了Android编程获取通知栏高度的方法,涉及Android针对通知栏属性相关操作技巧,需要的朋友可以参考下...

    java2009cgh6422021-05-06
  • AndroidAndroid实现静态广播监听器的方法

    Android实现静态广播监听器的方法

    这篇文章主要介绍了Android实现静态广播监听器的方法,涉及Android的广播机制与记录监听广播信息的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    鉴客5522021-03-27
  • AndroidExpandableListView实现简单二级列表

    ExpandableListView实现简单二级列表

    这篇文章主要为大家详细介绍了ExpandableListView实现简单二级列表,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    W丶先森4502022-08-20
  • AndroidAndroid手机屏幕px与dp互转的工具类

    Android手机屏幕px与dp互转的工具类

    今天小编就为大家分享一篇关于Android手机屏幕px与dp互转的工具类,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随...

    徐刘根6402022-10-10