
你可能遇到過這種場景:
用戶打開一個頁面,應用發(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