在Android中使用Flow进行网络请求

在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()
    }
}

最佳实践

  1. 分离关注点:将网络请求逻辑放在Repository层
  2. 使用StateFlow/SharedFlow:在ViewModel中暴露状态
  3. 适当的线程调度:使用flowOn确保网络请求在IO线程执行
  4. 错误处理:使用catch操作符妥善处理错误
  5. 取消支持:确保Flow在ViewModelScope内收集,以便自动取消
  6. 缓存策略:结合本地数据库实现离线支持

通过结合Kotlin Flow和协程,你可以构建响应式、可取消且易于测试的网络请求层。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值