Kotlin:用Flow實現(xiàn)帶有手動重試功能的網(wǎng)絡請求

image-20220410172409195.png

你可能遇到過這種場景:

用戶打開一個頁面,應用發(fā)起一個接口請求去獲取數(shù)據(jù)并展示。但是因為某個異常的發(fā)生導致這個請求失敗。在這個例子里,應用并沒有崩潰它只是顯示了一個漂亮的錯誤界面。你正在使用Kotlin Flow并且決定在錯誤界面上放置一個重試按鈕,但是,怎樣才能重啟一個flow呢?

首先我們先來看一下不帶重試的實現(xiàn)

你的viewmodel實現(xiàn)可能如下:

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

上邊的代碼做了兩件事

  • 創(chuàng)建了一個密封類去表示各種UI狀態(tài)
  • 提供了一個Flow去獲取列表數(shù)據(jù)并將其包裝在UiState類中

然而如果在第一次請求的時候發(fā)生異常,這個處理并沒有提供給我們一個簡單的方式去重啟flow 。Flow框架有一個設置重試行為的方式但是這個是自動觸發(fā)的而不是通過用戶的操作。

賦予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
    }
}

這個實現(xiàn)的要點是包裝網(wǎng)絡請求flow到一個MutableStateFlow中,通過這個操作,我們可以再次使用狀態(tài)事件去啟動這個網(wǎng)絡請求的flow。

為了讓它使用更方便,這里創(chuàng)建了一個類叫做RetryTrigger去持有這個狀態(tài):RETRYING和IDLE,讓它控制flow是否應該再次被啟動。

RETRYING 狀態(tài)意味著flow可以啟動。初始的狀態(tài)是RETRYING是因為我們第一次是普通的調用不涉及狀態(tài)轉換。在第一次調用之后,狀態(tài)變更為IDLE。

IDLE狀態(tài)意味著flow已經(jīng)啟動過了并且返回了第一次請求的值(這次請求可能是個成功或是失敗的結果)。我們過濾這個MutableStateFlow去啟動這個網(wǎng)絡請求的flow僅當這個狀態(tài)是RETRYING的時候。只有當flow是RETRYING狀態(tài)的時候會去調用MutableStateFlow中我們網(wǎng)絡請求的flow。

最后在RetryTrigger中有retry()方法。它再次設置狀態(tài)到RETRYING來讓這個網(wǎng)絡請求的flow重新啟動。

最終的實現(xiàn)如下
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()
    }
}

對于這個最終的實現(xiàn):

  • 創(chuàng)建了一個RetryTrigger實例,作為可重試flow的參數(shù)。
  • 網(wǎng)絡請求的flow被包裝到可重試的flow中。通過lambda表達式傳遞這個flow。更進一步還可以將這個方法設置成內聯(lián)函數(shù),但是這些具體的實現(xiàn)取決于是否適合你的項目。
  • 現(xiàn)在有了tryAgain()方法可以調用retryTrigger.retry()。這個方法應該在用戶點擊重試按鈕的時候被調用。

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容