Kevin Worth
02/28/2023, 3:08 PMStateFlow via stateIn(…) how would one “restart” the subscription after an exception is caught?
val searchQuery = MutableStateFlow("")
val scope = viewModelScope
val uiState: StateFlow<ItemUiState> = searchQuery
.flatMapLatest { query ->
when {
query.isBlank() -> itemRepository.items
else -> itemRepository.search(query, scope)
}
}
.map<List<String>, ItemUiState> {
Success(it)
}
// This "works" but it loops indefinitely
// .retryWhen { cause: Throwable, attempt: Long ->
// emit(Error(cause))
// true
// }
.catch {
emit(Error(it))
}
.stateIn(
scope,
SharingStarted.WhileSubscribed(5000),
Loading)
This comes from slightly altering this project https://github.com/android/architecture-templates/tree/base where uiState starts out simply defined as (see MyModelViewModel.kt):
val uiState: StateFlow<MyModelUiState> = myModelRepository
.myModels.map(::Success)
.catch { Error(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), Loading)Kevin Worth
02/28/2023, 3:10 PMflatMapLatest to continually react to updates on the searchQuery (after user types in a new query and hits the search button).Kevin Worth
02/28/2023, 3:11 PMcatch, it stops listening to changes to the searchQuery because, the catch cancelled everything, right? So, how to clear it out and start over?Kevin Worth
03/07/2023, 2:46 PM.stateIn so that I can keep hold of a Job from launchIn. Then on certain events I check to see if the Job is cancelled, and restart it when necessary. Shouldn’t there be a way to get it to work with stateIn?wasyl
03/07/2023, 2:51 PMResultKevin Worth
03/07/2023, 2:54 PMSuccess and Error classes are extended classes of the sealed ItemUiState class. So, my ItemUiState is analogous to your Result, yes?wasyl
03/07/2023, 2:55 PMKevin Worth
03/07/2023, 2:57 PMitemRepository.items
• itemRepository.search(query, scope)wasyl
03/07/2023, 2:58 PMKevin Worth
03/07/2023, 3:02 PMKevin Worth
03/07/2023, 3:04 PMwasyl
03/07/2023, 3:16 PMcatch inside the flatMapLatest — so the map below is .map<Result<List<String>>, ItemUiState>wasyl
03/07/2023, 3:16 PMResult.failure and nothing else, but if searchQuery receives another emission then code inside flatMapLatest will be called againKevin Worth
03/07/2023, 3:20 PMKevin Worth
03/07/2023, 3:44 PM...
query.isBlank() -> itemRepository.items
.catch {
println("boom: $it")
emit(emptyList())
}
else -> itemRepository.searchByTitle(query, viewModelScope)
.catch {
println("pow: $it")
emit(emptyList())
}
...
And sure enough, that did keep things listening. Now I just need to decide how to get this actually functional (since emitting an empty list was only for debugging).Kevin Worth
03/07/2023, 4:00 PMwasyl
03/07/2023, 4:01 PMwasyl
03/07/2023, 4:02 PMflatMap so it propagated to the outer flow and canceled it toowasyl
03/07/2023, 4:03 PMKevin Worth
03/07/2023, 4:03 PMKevin Worth
03/07/2023, 4:47 PMval uiState: StateFlow<ItemUiState> = searchQuery
.flatMapLatest { query ->
when {
query.isBlank() -> itemRepository.items
.map<List<String>, ItemUiState> { Success(it) }
.catch { emit(Error(it)) }
else -> itemRepository.search(query, scope)
.map<List<String>, ItemUiState> { Success(it) }
.catch { emit(Error(it)) }
}
}
.catch {
emit(Error(RuntimeException("Uncaught exception in ViewModel", it)))
}
.stateIn(
scope,
SharingStarted.WhileSubscribed(5000),
Loading)
Now, for the outer catch which we really don’t expect to hit, there will need to be some extra handling to recover, since that will still cancel. But for the typical, expected network errors, etc. they are handled in the inner `catch`es and things keep trucking nicely.