写点什么

LiveData+Retrofit 网络请求实战

用户头像
Android架构
关注
发布于: 5 小时前

bannerList.observe(this, Observer {


Log.e("main", "res:$it")


})


}


调试结果如下:


[外链图片转存失败(img-iq85bzWA-1568877294854)(https://upload-images.jianshu.io/upload_images/15679108-2dc184537790611f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 “banner 请求结果”)]


banner 请求结果

LiveData 的 map 与 switchMap 操作

LiveData 可以通过 Transformations 的 map 和 switchMap 操作,将一个 LiveData 转成另一种类型的 LiveData,效果与 RxJava 的 map/switchMap 操作符类似。可以看看两个函数的声明


public static <X, Y> LiveData<Y> map(


@NonNull LiveData<X> source,


@NonNull final Function<X, Y> mapFunction)


public static <X, Y> LiveData<Y> switchMap(


@NonNull LiveData<X> source,


@NonNull final Function<X, LiveData<Y>> switchMapFunction)


根据以上代码,我们可以知道,对应的变换函数返回的类型是不一样的:map 是基于泛型类型的变换,而 switchMap 则返回一个新的 LiveData。


还是以 banner 请求为例,我们将 map 和 switchMap 应用到实际场景中:


1: 为了能够手动控制请求,我们需要一个 refreshTrigger 触发变量,当这个变量被设置为 true 时,通过 switchMap 生成一个新的 LiveData 用作请求 banner


private val refreshTrigger = MutableLiveData<Boolean>()


private val api = WanApi.get()


private val bannerLis:LiveData<ApiResponse<List<BannerVO>>> = Transformations.switchMap(refreshTrigger) {


//当 refreshTrigger 的值被设置时,bannerList


api.bannerList()


}


2: 为了展示 banner,我们通过 map 将 ApiResponse 转换成最终关心的数据是 List


val banners: LiveData = Transformations.map(bannerList) {


it.data ?: ArrayList()


}</list

LiveData 与 ViewModel 结合

为了将 LiveData 与 Activity 解耦,我们通过 ViewModel 来管理这些 LiveData。


class HomeVM : ViewModel() {


private val refreshTrigger = MutableLiveData<Boolean>()


private val api = WanApi.get()


private val bannerList: LiveData<ApiResponse<List<BannerVO>>> = Transformations.switchMap(refreshTrigger) {


//当 refreshTrigger 的值被设置时,bannerList


api.bannerList()


}


val banners: LiveData<List<BannerVO>> = Transformations.map(bannerList) {


it.data ?: ArrayList()


}


fun loadData() {


refreshTrigger.value = true


}


}


在 activity_main.xml 中加入 banner 布局,这里使用 BGABanner-Android 来显示图片


<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"


xmlns:tools="http://schemas.android.com/tools">


<data>


<variable


name="vm"


type="io.github.iamyours.wandroid.ui.home.HomeVM"/>


</data>


<LinearLayout


android:layout_width="match_parent"


android:layout_height="match_parent"


android:orientation="vertical">


<cn.bingoogolapple.bgabanner.BGABanner


android:id="@+id/banner"


android:layout_width="match_parent"


android:layout_height="120dp"


android:paddingLeft="16dp"


android:paddingRight="16dp"


app:banner_indicatorGravity="bottom|right"


app:banner_isNumberIndicator="true"


app:banner_pointContainerBackground="#0000"


app:banner_transitionEffect="zoom"/>


<TextView


android:layout_width="match_parent"


android:layout_height="44dp"


android:background="#ccc"


android:gravity="center"


android:onClick="@{()->vm.loadData()}"


android:text="加载 Banner"/>


</LinearLayout>


</layout>


然后在 MainActivity 完成 Banner 初始化,通过监听 ViewModel 中的 banners 实现轮播图片的展示。


class MainActivity : AppCompatActivity() {


lateinit var binding: ActivityMainBinding


override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)


val vm = ViewModelProviders.of(this).get(HomeVM::class.java)


binding.lifecycleOwner = this


binding.vm = vm


initBanner()


}


private fun initBanner() {


binding.run {


val bannerAdapter = BGABanner.Adapter<ImageView, BannerVO> { _, image, model, _ ->


image.displayWithUrl(model?.imagePath)


}


banner.setAdapter(bannerAdapter)


vm?.banners?.observe(this@MainActivity, Observer {


banner.setData(it, null)


})


}


}


}


最终效果如下:


[外链图片转存失败(img-jYdgEFqB-1568877294856)(https://upload-images.jianshu.io/upload_images/15679108-abbe27a111d6db78.gif?imageMogr2/auto-orient/strip)]


banner

加载进度显示

SwipeRefreshLayout

请求网络过程中,必不可少的是加载进度的展示。这里我们列举两种常用的的加载方式,一种在布局中的进度条(如 SwipeRefreshLayout),另一种是加载对话框。


为了控制加载进度条显示隐藏,我们在 HomeVM 中添加 loading 变量,在调用 loadData 时通过 loading.value=true 控制进度条的显示,在 map 中的转换函数中控制进度的隐藏


val loading = MutableLiveData<Boolean>()


val banners: LiveData<List<BannerVO>> = Transformations.map(bannerList) {


loading.value = false


it.data ?: ArrayList()


}


fun loadData() {


refreshTrigger.value = true


loading.value = true


}


我们在 activity_main.xml 的外层嵌套一个 SwipeRefreshLayout,通过 databinding 设置加载状态,添加刷新事件


<androidx.swiperefreshlayout.widget.SwipeRefreshLayout


android:layout_width="match_parent"


android:layout_height="match_parent"


app:onRefreshListener="@{() -> vm.loadData()}"


app:refreshing="@{vm.loading}">


...


</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>


然后我们再看下效果:


[外链图片转存失败(img-b8PJZvg6-1568877294857)(https://upload-images.jianshu.io/upload_images/15679108-c2745d7182c66f29?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 “SwipeRefreshLayout 进度控制”)]


SwipeRefreshLayout 进度控制

加载对话框 KProgressHUD

为了能和 ViewModel 解藕,我们将加载对话框封装到一个 Observer 中。


class LoadingObserver(context: Context) : Observer<Boolean> {


private val dialog = KProgressHUD(context)


.setStyle(KProgressHUD.Style.SPIN_INDETERMINATE)


.setCancellable(false)


.setAnimationSpeed(2)


.setDimAmount(0.5f)


override fun onChanged(show: Boolean?) {


if (show == null) return


if (show) {


dialog.show()


} else {


dialog.dismiss()


}


}


}


然后在 MainActivity 添加这个 Observer


vm.loading.observe(this,?LoadingObserver(this))


效果:


[外链图片转存失败(img-2JCQNDTS-1568877294858)(https://upload-images.jianshu.io/upload_images/15679108-66b1b55491ca6473?imageMogr2/auto-orient


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


/strip%7CimageView2/2/w/1240 “加载对话框显示”)]


加载对话框显示


我们还可以将 LoadingObserver 注册到 BaseActivity


class BaseActivity : AppCompatActivity() {


val loadingState = MutableLiveData<Boolean>()


override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)


loadingState.observe(this, LoadingObserver(this))


}


}


然后在 HomeVM 中添加一个 attachLoading 方法


class HomeVM:ViewModel{


fun attachLoading(otherLoadingState: MutableLiveData<Boolean>) {


loading.observeForever {


otherLoadingState.value = it


}


}


}


最终如果想要显示进度对话框,在 BaseActivity 到子类中,只需调用 vm.attachLoading(loadingState)即可。

分页请求

分页请求是另个一常用请求,它的请求状态就比刷新数据多了几种。以 wanandroid 首页文章列表 api 为例,我们在 HomeVM 中加入 page,refreshing,moreLoading,hasMore 变量控制分页请求


private val page = MutableLiveData<Int>() //分页数据


val refreshing = MutableLiveData<Boolean>()//下拉刷新状态


val moreLoading = MutableLiveData<Boolean>()//上拉加载更多状态


val hasMore = MutableLiveData<Boolean>()//是否还有更多数据


private val articleList = Transformations.switchMap(page) {


api.articleList(it)


}


val articlePage = Transformations.map(articleList) {


refreshing.value = false


moreLoading.value = false


hasMore.value = !(it?.data?.over ?: false)


it.data


}


fun loadMore() {


page.value = (page.value ?: 0) + 1


moreLoading.value = true


}


fun refresh() {


loadBanner()


page.value = 0


refreshing.value = true


}


用 SmartRefreshLayout 作为分页组件,来实现 WanAndroid 首页文章列表数据的展示。

绑定 SmartRefreshLayout 属性和事件

通过 @BindingAdapter 注解,将绑定 SmartRefreshLayout 属性和事件封装一样,便于我们在布局文件通过 databinding 控制它。


新建一个 CommonBinding.kt 文件,注意在 gradle 中引入 kotlin-kapt


@BindingAdapter(value = ["refreshing", "moreLoading", "hasMore"], requireAll = false)


fun bindSmartRefreshLayout(


smartLayout: SmartRefreshLayout,


refreshing: Boolean,


moreLoading: Boolean,


hasMore: Boolean


) {


if (!refreshing) smartLayout.finishRefresh()


if (!moreLoading) smartLayout.finishLoadMore()


smartLayout.setEnableLoadMore(hasMore)


}


@BindingAdapter(value = ["onRefreshListener", "onLoadMoreListener"], requireAll = false)


fun bindListener(


smartLayout: SmartRefreshLayout,


refreshListener: OnRefreshListener?,


loadMoreListener: OnLoadMoreListener?


) {


smartLayout.setOnRefreshListener(refreshListener)


smartLayout.setOnLoadMoreListener(loadMoreListener)


}


然后在布局中使用


<layout xmlns:android="http://schemas.android.com/apk/res/android"


xmlns:app="http://schemas.android.com/apk/res-auto"


xmlns:tools="http://schemas.android.com/tools">


<data>


<variable


name="vm"


type="io.github.iamyours.wandroid.ui.home.HomeVM"/>


</data>


<com.scwang.smartrefresh.layout.SmartRefreshLayout


android:id="@+id/refreshLayout"


android:layout_width="match_parent"


app:onRefreshListener="@{()->vm.refresh()}"


app:refreshing="@{vm.refreshing}"


app:moreLoading="@{vm.moreLoading}"


app:hasMore="@{vm.hasMore}"


app:onLoadMoreListener="@{()->vm.loadMore()}"


android:layout_height="match_parent">


<androidx.core.widget.NestedScrollView


android:layout_width="match_parent"


android:layout_height="match_parent">


<LinearLayout


android:layout_width="match_parent"


android:orientation="vertical"


android:layout_height="wrap_content">


<cn.bingoogolapple.bgabanner.BGABanner


android:id="@+id/banner"


android:layout_width="match_parent"


android:layout_height="140dp"


app:banner_indicatorGravity="bottom|right"


app:banner_isNumberIndicator="true"


app:banner_pointContainerBackground="#0000"


app:banner_transitionEffect="zoom"/>


<androidx.recyclerview.widget.RecyclerView


android:id="@+id/recyclerView"


android:layout_width="match_parent"


android:layout_marginTop="5dp"


tools:listitem="@layout/item_article"


android:layout_height="wrap_content"/>


</LinearLayout>


</androidx.core.widget.NestedScrollView>


</com.scwang.smartrefresh.layout.SmartRefreshLayout>


</layout>

分页实现

然后在 MainActivity 中完成 RecyclerView 的逻辑


class MainActivity : AppCompatActivity() {


lateinit var binding: ActivityMainBinding


private val adapter = ArticleAdapter()


override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)


binding = DataBindingUtil.setContentView(this, R.layout.activity_main)


val vm = ViewModelProviders.of(this).get(HomeVM::class.java)


binding.lifecycleOwner = this


binding.vm = vm


binding.executePendingBindings()


initBanner()


initRecyclerView()


binding.refreshLayout.autoRefresh()


}


private fun initRecyclerView() {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
LiveData+Retrofit 网络请求实战