-
Notifications
You must be signed in to change notification settings - Fork 213
Description
Is your feature request related to a problem? Please describe.
The documentation states "Another good use case for fresh()
is when a user wants to pull to refresh." While this is correct, there is a drawback to using fresh()
for pull to refresh events: We don't get the Loading or Error states from the StoreResponse. This may mean implementing additional logic around showing the user the loading state or error state in the UI when using both stream()
, for the primary flow of data, and fresh()
for the pull to refresh event.
Describe the solution you'd like
I created the following extensions to be used in a pull refresh scenario and have found they work well. Since pull to refresh is common, perhaps these would be good to integrate into Store somehow?
/**
* Use to trigger a network refresh for your Store. An example use-case is for pull to refresh functionality.
* This is best used in conjunction with `Flow.collectRefreshFlow` to prevent this returned Flow from living
* on longer than necessary and conflicting with your primary Store Flow.
*/
fun <Key : Any, Output : Any> Store<Key, Output>.refreshFlow(key: Key) = stream(StoreRequest.fresh(key))
/**
* Helper to observe the loading state of a Store refresh call triggered via `Store.refreshFlow`. This Flow will
* automatically be cancelled after the refresh is successful or has an error.
*
* @param checkLoadingState Lambda providing the current StoreResponse for the refresh (Loading, Error, or Data) allowing
* you to decide when to show/hide your loading indicator.
*/
suspend fun <T> Flow<StoreResponse<T>>.collectRefreshFlow(checkLoadingState: suspend (StoreResponse<T>) -> Unit) {
onEach { storeResponse ->
checkLoadingState(storeResponse)
}.first { storeResponse ->
storeResponse.isData() || storeResponse.isError()
}
}
Examples of the solution in use
Below is an example of how refreshFlow
is used alongside the main flow:
fun getUserDataFlow(): Flow<StoreResponse<List<UserData>>> {
return myStore.stream(StoreRequest.cached(Key("123"), refresh = true))
}
fun refreshUserDataFlow(): Flow<StoreResponse<List<UserData>>> {
return myStore.refreshFlow(Key("123"))
}
And finally an example of how collectRefreshFlow
is used in a ViewModel alongside the main flow:
class MyViewModel {
val userDataLiveData = userRepository.getUserDataFlow()
.onEach { storeResponse ->
setLoadingSpinnerVisibility(storeResponse)
setEmptyStateVisibility(storeResponse)
}
.filterIsInstance<StoreResponse.Data<List<UserData>>>()
.mapLatest { storeResponse ->
storeResponse.dataOrNull() ?: emptyList()
}
.flowOn(Dispatchers.IO)
.asLiveData().distinctUntilChanged()
}
fun onRetryLoadUserData() = viewModelScope.launch {
if (networkIsConnected()) {
userRepository.refreshUserDataFlow()
.collectRefreshFlow { storeResponse -> setLoadingSpinnerVisibility(storeResponse) }
}
}
}
Additional context
I feel these two extensions provide more power/flexibility in the pull to refresh scenario than simply calling the suspending fresh()
method. Does it seem like these or something similar could fit into the Store API?