Kotlin:用Flow实现带有手动重试功能的网络请求

image-20220410172409195.png

你可能遇到过这种场景:

用户打开一个页面,应用发起一个接口请求去获取数据并展示。但是因为某个异常的发生导致这个请求失败。在这个例子里,应用并没有崩溃它只是显示了一个漂亮的错误界面。你正在使用Kotlin Flow并且决定在错误界面上放置一个重试按钮,但是,怎样才能重启一个flow呢?

首先我们先来看一下不带重试的实现

你的viewmodel实现可能如下:

import androidx.lifecycle.ViewModel
import com.messiaslima.promogamer.domain.Store
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart

class StoreViewModel(private val storeOrchestrator: StoreOrchestrator) : ViewModel() {
  
    val uiState = storeOrchestrator.getStores()
        .map<List<Store>, UiState> { UiState.Success(it) }
        .catch { throwable -> emit(UiState.Error(throwable)) }
        .onStart { emit(UiState.Loading) }

    sealed class UiState {
        object Loading : UiState()
        class Success(stores: List<Store>) : UiState()
        class Error(throwable: Throwable) : UiState()
        object Idle : UiState()
    }
}

上边的代码做了两件事

  • 创建了一个密封类去表示各种UI状态
  • 提供了一个Flow去获取列表数据并将其包装在UiState类中

然而如果在第一次请求的时候发生异常,这个处理并没有提供给我们一个简单的方式去重启flow 。Flow框架有一个设置重试行为的方式但是这个是自动触发的而不是通过用户的操作。

赋予Flow重试的能力
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.onEach

@FlowPreview
fun <T> retryableFlow(retryTrigger: RetryTrigger, flowProvider: () -> Flow<T>) =
    retryTrigger.retryEvent.filter { it == RetryTrigger.State.RETRYING }
        .flatMapConcat { flowProvider() }
        .onEach { retryTrigger.retryEvent.value = RetryTrigger.State.IDLE }

class RetryTrigger {
    enum class State { RETRYING, IDLE }

    val retryEvent = MutableStateFlow(State.RETRYING)

    fun retry() {
        retryEvent.value = State.RETRYING
    }
}

这个实现的要点是包装网络请求flow到一个MutableStateFlow中,通过这个操作,我们可以再次使用状态事件去启动这个网络请求的flow。

为了让它使用更方便,这里创建了一个类叫做RetryTrigger去持有这个状态:RETRYING和IDLE,让它控制flow是否应该再次被启动。

RETRYING 状态意味着flow可以启动。初始的状态是RETRYING是因为我们第一次是普通的调用不涉及状态转换。在第一次调用之后,状态变更为IDLE。

IDLE状态意味着flow已经启动过了并且返回了第一次请求的值(这次请求可能是个成功或是失败的结果)。我们过滤这个MutableStateFlow去启动这个网络请求的flow仅当这个状态是RETRYING的时候。只有当flow是RETRYING状态的时候会去调用MutableStateFlow中我们网络请求的flow。

最后在RetryTrigger中有retry()方法。它再次设置状态到RETRYING来让这个网络请求的flow重新启动。

最终的实现如下
import androidx.lifecycle.ViewModel
import com.messiaslima.promogamer.domain.Store
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart

@FlowPreview
class StoreViewModel(private val storeOrchestrator: StoreOrchestrator) : ViewModel() {
    private val retryTrigger = RetryTrigger()

    val uiState = retryableFlow(retryTrigger) {
        storeOrchestrator.getStores()
            .map<List<Store>, UiState> { UiState.Success(it) }
            .catch { emit(UiState.Error(it))}
            .onStart { emit(UiState.Loading) }
    }

    fun tryAgain() {
        retryTrigger.retry()
    }

    sealed class UiState {
        object Loading : UiState()
        class Success(stores: List<Store>) : UiState()
        class Error(throwable: Throwable) : UiState()
        object Idle : UiState()
    }
}

对于这个最终的实现:

  • 创建了一个RetryTrigger实例,作为可重试flow的参数。
  • 网络请求的flow被包装到可重试的flow中。通过lambda表达式传递这个flow。更进一步还可以将这个方法设置成内联函数,但是这些具体的实现取决于是否适合你的项目。
  • 现在有了tryAgain()方法可以调用retryTrigger.retry()。这个方法应该在用户点击重试按钮的时候被调用。

原文:how-to-manually-retry-a-call-using-kotlin-flow

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容