在Android开发中,结合Kotlin Flow进行网络请求是一种现代化且高效的方式。下面我将介绍如何使用Flow与Retrofit等网络库结合进行网络请求。
基本设置
1. 添加依赖
首先确保你的项目已经添加了必要的依赖:
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Coroutines & Flow
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
2. 创建Retrofit服务接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@GET("posts")
suspend fun getPosts(): List<Post>
}
使用Flow进行网络请求
基本用法
class UserRepository(private val apiService: ApiService) {
fun getUser(userId: Int): Flow<User> = flow {
val user = apiService.getUser(userId)
emit(user)
}.flowOn(Dispatchers.IO)
}
在ViewModel中使用
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user
private val _error = MutableSharedFlow<String>()
val error: SharedFlow<String> = _error
fun fetchUser(userId: Int) {
viewModelScope.launch {
repository.getUser(userId)
.catch { e ->
_error.emit(e.message ?: "Unknown error")
}
.collect { user ->
_user.value = user
}
}
}
}
结合Room数据库的缓存策略
class UserRepository(
private val apiService: ApiService,
private val userDao: UserDao
) {
fun getUser(userId: Int): Flow<User> = flow {
// 先发射本地数据
val localUser = userDao.getUser(userId)
emit(localUser)
// 然后获取网络数据
val remoteUser = apiService.getUser(userId)
userDao.insert(remoteUser)
// 发射更新后的数据
emit(remoteUser)
}.catch { e ->
// 处理错误
if (e is IOException) {
// 网络错误,可以继续使用本地数据
} else {
throw e
}
}.flowOn(Dispatchers.IO)
}
高级用法
多个请求组合
fun getUserWithPosts(userId: Int): Flow<Pair<User, List<Post>>> = flow {
val userDeferred = async { apiService.getUser(userId) }
val postsDeferred = async { apiService.getPostsByUser(userId) }
val user = userDeferred.await()
val posts = postsDeferred.await()
emit(user to posts)
}.flowOn(Dispatchers.IO)
分页请求
fun getPagedPosts(page: Int, pageSize: Int = 10): Flow<PagingData<Post>> {
return Pager(
config = PagingConfig(pageSize = pageSize),
remoteMediator = PostRemoteMediator(apiService, db)
) {
db.postDao().getPosts()
}.flow
}
错误处理
fun fetchData() {
viewModelScope.launch {
repository.getData()
.onStart {
// 显示加载状态
_loading.value = true
}
.catch { e ->
// 处理错误
_error.emit(e.message ?: "Unknown error")
_loading.value = false
}
.onCompletion {
// 隐藏加载状态
_loading.value = false
}
.collect { data ->
_data.value = data
}
}
}
测试网络请求
@Test
fun `getUser should emit user from network`() = runTest {
// 准备
val mockUser = User(id = 1, name = "Test")
val mockApi = mockk<ApiService> {
coEvery { getUser(1) } returns mockUser
}
val repository = UserRepository(mockApi)
// 执行
val flow = repository.getUser(1)
// 验证
flow.test {
assertEquals(mockUser, expectMostRecentItem())
expectNoEvents()
}
}
最佳实践
- 分离关注点:将网络请求逻辑放在Repository层
- 使用StateFlow/SharedFlow:在ViewModel中暴露状态
- 适当的线程调度:使用flowOn确保网络请求在IO线程执行
- 错误处理:使用catch操作符妥善处理错误
- 取消支持:确保Flow在ViewModelScope内收集,以便自动取消
- 缓存策略:结合本地数据库实现离线支持
通过结合Kotlin Flow和协程,你可以构建响应式、可取消且易于测试的网络请求层。