Jetpack Compose 优雅的Retrofit+协程+Kotlin Flow+Paging实现网络数据分页

2,292 阅读2分钟

前言

最近在学习Jetpack Compose,在之前的Android开发中,我们经常使用 Retrofit 实现网络数据请求,那在Jetpack Compose我们应该怎样使用Retrofit实现网络数据请求呢,这一篇介绍下使用 Retrofit+协程+Kotlin Flow+Paging 实现网络数据分页查询。

篇外准备

为了更真实的网络请求,我使用 SpringBoot 创建了一个项目来写我们使用的接口,我们看下代码:

@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/users")
public class UserController {

    @GetMapping("/getUserList")
    public List<UserVo> getUserList(Integer pageNum, Integer pageSize) {
        System.out.println("pageNum==="+pageNum + "    ,pageSize===="+pageSize);
        List<UserVo> list = new ArrayList<>();
        for(int i = 0; i < 500; i++) {
            UserVo userVo = new UserVo();
            userVo.setUserId(i);
            userVo.setName("小明"+i);
            list.add(userVo);
        }
        int start = pageNum * pageSize - pageSize;
        int end = start + pageSize;
        return list.subList(start, end);
    }
}
@Data
public class UserVo {

    private int userId;
    private String name;
}

接口比较简单,只是根据pageNum和pageSize参数返回了包含name和userId的列表数据,接口的结构为:

image.png

(当然,这部分是可以不看的,正常开发中接口由后端负责...)

依赖引入

implementation "androidx.paging:paging-compose:1.0.0-alpha14"

implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'

实体类创建

我们看了接口的结构,创建一个和接口的字段对应上的User类,代码如下:

@Parcelize
class User(
    val userId: Long,
    val name: String,
): Parcelable

Retrofit单例创建

object HttpClient {

    private const val BASE_URL = "你的服务器IP或域名,端口"
    private val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun <T> create(clazz: Class<T>): T {
        return retrofit.create(clazz) as T
    }
}

网络请求接口 UserApi 创建

interface UserApi{

    @GET("/users/getUserList")
    suspend fun getUserList(
        @Query("pageNum") pageNum: Int,
        @Query("pageSize") pageSize: Int
    ): List<User>
}

UserSource分页数据源创建

class UserSource: PagingSource<Int, User>() {
    override fun getRefreshKey(state: PagingState<Int, User>): Int? {
        return null
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        val nextPage = params.key ?: 1
        /** 网络数据请求*/
        val data = HttpClient.create(UserApi::class.java).getUserList(nextPage, 10)
        return try {
            LoadResult.Page(
                data = data,
                prevKey = if (nextPage == 1) null else nextPage - 1,
                nextKey = nextPage + 1
            )
        } catch (e: Exception) {
           LoadResult.Error(e)
        }
    }
}

UserViewModel创建

class UserViewModel: ViewModel() {

    val userItems: Flow<PagingData<User>> =
        Pager(PagingConfig(pageSize = 10, prefetchDistance = 1)) {
            UserSource()
        }.flow.cachedIn(viewModelScope)
}

UserScreen组合函数页面创建

这个页面比较简单,只是一个显示user数据的列表

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun UserScreen(viewModel: UserViewModel = UserViewModel()) {
    val lazyUserItems = viewModel.userItems.collectAsLazyPagingItems()
    val scrollState = rememberLazyListState()
    Surface(
        Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background)
            .autoCloseKeyboard()
    ) {
        Scaffold(
            content = { innerPadding ->
                Box(modifier = Modifier.padding(innerPadding)) {
                    LazyColumn(
                        contentPadding = innerPadding,
                        state = scrollState
                    ) {
                        items(lazyUserItems) {
                            it?.let {
                                Column {
                                    Box(modifier = Modifier
                                        .fillMaxWidth()
                                        .height(60.dp),
                                        contentAlignment = Alignment.Center
                                    ) {
                                        Text(
                                            text = "name: ${it.name},  userId: ${it.userId}",
                                            fontSize = 18.sp
                                        )
                                    }
                                    CQDivider()
                                }
                            }
                        }
                        lazyUserItems.apply {
                            when (loadState.append) {
                                is LoadState.Loading -> {
                                    item {
                                        Loading()
                                    }
                                } else -> {
                            }
                            }
                        }
                        item { 
                            Spacer(modifier = Modifier.height(60.dp))
                        }
                    }
                }
            }
        )
    }
}

到这里,使用 Retrofit+协程+Kotlin Flow+Paging 实现网络数据分页查询的功能就实现了,我们来看下效果:

gt_20231222_32361_v_gif.gif

总结

(1)Retrofit 是一个Android开发常用的网络请求框架,使用起来高效简洁。

(2)协程 是一个Kotlin提供了一种名为协程的轻量级线程,可以简化异步编程,性能更高。

(3)Kotlin Flow 是基于 Kotlin 协程的库,专门用于处理异步数据流,它的设计灵感来自于响应式编程,类似RxJava。

(4)Paging 是一个Jetpack库里的分页组件。