abstract class BaseFragment<T : ViewDataBinding, VM : BaseViewModel> : Fragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel = getViewModel()
mViewModel?.errorLiveData?.observe(viewLifecycleOwner, Observer {
Log.d(TAG, "onViewCreated: error ")
showError()
throwableHandler(it)
})
}
}
每个子Fragment只需要继承BaseFragment即可,具体的异常监听就不用开发者管理。
2、 [Loading状态]
除了异常状态外,请求必不可少的就是Loading,这里Loading分为两种,一种是整个页面替换为Loading,例如Recyclerview列表时,就可以直接整个页面先Loading,而后显示数据;还有一种是数据界面不替换,只是个Loading Dialog显示在上层,例如点击登录时,需要一个loading。
Loading和异常处理的思路一致,可以在BaseViewModel中添加一个LoadingLiveData,数据类型为Boolean,在每个请求一开始LoadingLiveData.postValue(true)
,结束请求或者请求异常时,就LoadingLiveData.postValue(false)
。UI层BaseFragment中,则可以监听LoadingLiveData发出的是true还是false,以便对Loading的显示和隐藏进行控制。
ViewModel层:
open class BaseViewModel : ViewModel() {
//加载中
val loadingLiveData = SingleLiveData()
//异常
val errorLiveData = SingleLiveData()
fun launch(
block: suspend () -> Unit,
error: suspend (Throwable) -> Unit,
complete: suspend () -> Unit
) {
loadingLiveData.postValue(true)
viewModelScope.launch(Dispatchers.IO) {
try {
block()
} catch (e: Exception) {
Log.d(TAG, "launch: error ")
error(e)
} finally {
complete()
}
}
}
}
在BaseViewModel 中launch一开始就通知Loading显示,在try-catch-finally
代码块的finally
中将请求结束的通知分发出去。
UI层:
abstract class BaseFragment<T : ViewDataBinding, VM : BaseViewModel> : Fragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel = getViewModel()
//Loading 显示隐藏的监听
mViewModel?.loadingLiveData?.observe(viewLifecycleOwner, Observer {
if (it) {
//show loading
showLoading()
} else {
dismissLoading()
}
})
//请求异常的监听
mViewModel?.errorLiveData?.observe(viewLifecycleOwner, Observer {
Log.d(TAG, "onViewCreated: error ")
showError()
throwableHandler(it)
})
}
}
注册一个loading的观察者,当通知为true时,显示loading,false则隐藏。
3、 [Empty状态]
数据为空的状态发生在请求成功后,对于这种情况,可以直接在UI层中,请求成功的监听中对数据是否为null进行判断。
到这里,网络请求的基本封装已经完成,但是在运行测试的过程中,存在几个问题需要去解决,例如网络不通的情况下try-catch却不会抛出异常。接下来就开始进行二次封装。
暴露问题二次封装
问题一:网络请求异常,try-catch却不会将异常抛出
因为业务场景比较复杂,只依赖try-catch来获取异常,明显也会有所遗漏,那这种情况下我们可以直接以服务器返回的code,作为请求状态的依据。以上面Wanandroid的api为例,当errorCode=0时,则表示请求成功,其他的值都表示失败,那这就好办了。
我们新建一个密封类ResState,存放Success和Error状态,
sealed class ResState {
data class Success(val data: T) : ResState()
data class Error(val exception: Exception) : ResState()
}
对Repository层请求返回的数据进行code判断处理,新建一个BaseRepository类,
open class BaseRepository() {
suspend fun executeResp(
resp: BaseResp, successBlock: (suspend CoroutineScope.() -> Unit)? = null,
errorBlock: (suspend CoroutineScope.() -> Unit)? = null
): ResState {
return coroutineScope {
if (resp.errorCode == 0) {
successBlock?.let { it() }
ResState.Success(resp.data)
} else {
Log.d(TAG, “executeResp: error”)
errorBlock?.let { it() }
ResState.Error(IOException(resp.errorMsg))
}
}
}
}
当errorCode == 0
时,将ResState置为Success
并将数据返回,errorCode !=0
时,则将状态置为Error
并将Exception返回。而子Repository则只需要继承BaseRepository即可,
class ProjectRepo : BaseRepository() {
suspend fun loadProjectTree(): ResState<List> {
return executeResp(mService.loadProjectTree())
}
修改后返回值用ResState<>包裹,并直接将请求的结果传给executeResp()方法,而ViewModel中也做出相应的修改,
class ProjectViewModel : BaseViewModel() {
val mProjectTreeLiveData = MutableLiveData<List>()
fun loadProjectTree() {
launch(
{
val state = mRepo.loadProjectTree()
//添加ResState判断
if (state is ResState.Success) {
mProjectTreeLiveData.postValue(state.data)
} else if (state is ResState.Error) {
Log.d(TAG, “loadProjectTree: ResState.Error”)
errorLiveData.postValue(state.exception)
}
},
{
errorLiveData.postValue(it)
},
{
loadingLiveData.postValue(false)
}
)
}
}
ViewModel层新增了一个ResState判断,通过请求的返回值ResState,如果是ResState.Success则将数据通知给UI,如果是ResState.Error,则将异常通知给UI。
以服务器返回的code值进行判断,无疑是最准确的。
问题二:errorLiveData注册观察者一次后,不管请求失败还是成功,它还是会收到通知。
这是MutableLiveData的一个特性,只要当注册的观察者处于前台时,都会收到通知。那这个特性又影响了什么呢? 我在errorLiveData的监听中,对不同的异常进行了Toast的弹出提醒,如果每次进入一个页面,虽然请求成功了,但是因为errorLiveData还是能接收到通知,就会弹出一个Toast提醒框。现象如下:
那我们针对MutableLiveData将其修改为单事件响应的liveData,只有一个接收者能接收到信息,可以避免不必要的业务的场景中的事件消费通知。
class SingleLiveData : MutableLiveData() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer) {
if (hasActiveObservers()) {
Log.w(TAG, “多个观察者存在的时候,只会有一个被通知到数据更新”)
}
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(value: T?) {
mPending.set(true)
super.setValue(value)
}
@MainThread
fun call() {
value = null
}
companion object {
private const val TAG = “SingleLiveData”
}
}
将BaseViewModel中的MutableLiveData替换为SingleLiveData就可以了。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!
-
阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升
-
**全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记
有任何问题,欢迎广大网友一起来交流
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
24)]
- **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记
[外链图片转存中…(img-TvjkTZBC-1712790160324)]
有任何问题,欢迎广大网友一起来交流
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-xVCFkkf3-1712790160325)]