Jamie Craane
07/02/2020, 6:46 AM@ExperimentalCoroutinesApi
class PersonsViewModel {
private val realApi = RealApi()
// cached list of persons
private var cachedPersons: List<Person>? = null
// indicates of the data should be retrieved from network or cache. This is a MutableStateFlow since its value
// can be updated by the UI (for example uses refreshes the page).
private val refreshTrigger: MutableStateFlow<RefreshTrigger> = MutableStateFlow(RefreshTrigger.FromCache())
// flatMapLatest on refreshTrigger because we are interested in the latest value only.
var persons: Flow<CommonDataContainer<List<Person>>> = refreshTrigger.flatMapLatest { trigger ->
flow<CommonDataContainer<List<Person>>> {
// Notify the UI we are loading data
emit(CommonDataContainer.Loading())
val refresh = trigger is RefreshTrigger.Refresh
val cached = cachedPersons
if (!refresh && cached != null) {
// Return the cached data if not explicitly refreshed and data is in cache.
emit(CommonDataContainer.Success(cached))
} else {
// Refresh or data is not in cache, retrieve from network.
retrievePersonsFromNetwork()
}
}
}
private suspend fun FlowCollector<CommonDataContainer<List<Person>>>.retrievePersonsFromNetwork() {
val response = realApi.retrievePersons()
when (response) {
is Success -> {
cachedPersons = response.data
// Emit the data.
emit(CommonDataContainer.Success(response.data))
}
// Something went wrong, emit a failure.
is Failure -> emit(CommonDataContainer.Failure())
}
}
suspend fun refresh() {
yield()
refreshTrigger.value = RefreshTrigger.Refresh()
}
}
// CommonDataContainer can represent various states which are interesting to collectors.
sealed class CommonDataContainer<out T> {
// Indicates the data is loading
class Loading<T> : CommonDataContainer<T>()
// Indicates an error occurred retrieving the data
class Failure<T> : CommonDataContainer<T>()
// Indicates success. Holds the data in the data property.
class Success<out T>(val data: T) : CommonDataContainer<T>()
}
// Indicates where the data should com from. Both classes have a unique id to make sure the Refresh trigger is always emitted by MutableStateFlow
// since MutableStateFLow does not emit the value if the new value is the same as the current value.
sealed class RefreshTrigger {
data class FromCache(private val id: String = Random.nextInt().toString()) : RefreshTrigger()
/**
* Every instance has a unique id so the refresh StateFlow sees it as a new value. StateFlow does not emit the same value twice if
* the current value is the same as the new value.
*/
data class Refresh(private val id: String = Random.nextInt().toString()) : RefreshTrigger()
}
ildar.i [Android]
07/02/2020, 8:44 AMJamie Craane
07/02/2020, 11:28 AMDALDEI
07/11/2020, 7:05 PM