How to manually observe SnapshotStateList or MutableState? It said that mutableStateListOf returns list that can be observed, but I can't find such method. Is it true that only Compose can observe changes?
The easiest way to observe of a state object, such as the result of mutableStateOf or mutableStateListOf(), is by using snapshotFlow.
snapshotFlow's block parameter will be called when any state object it uses has been changed and the result of block will be emitted to the flow such as snapshotFlow { snapshotList.toList() }. Note the toList() is required here otherwise the snapshotList itself is used which the snapshotFlow doesn't consider changed (it is always the same instance) so it will only emit once. In compose runtime version 1.4, toList() was changed so that calling toList() on a SnapshotStateList does not perform a copy.
Alternately, you can use SnapshotStateObserver which enables multiplexing a single apply observer to observe a multitude of lambdas. This is what Compose UI layout and draw use, for example, to track when to recalculate the layout and when to replay drawing commands.
State objects do not have an observe or subscribe equivalent because Snapshots was designed and optimized to observe an open, dynamic set of objects read by a function and any function it calls (such as Compose needs); and no fast path for a single object (which Compose does not need) was implemented and is therefore expensive. Having an observe method of a state object would imply it is fast and cheap, which it is not.
Related
How can I get the latest value of a Flow? I don't have a StateFlow where I need that latest value. This is the condensed scenario:
There is a repository exposing a StateFlow
val repositoryExposedStateFlow: StateFlow<SomeType> = MutableStateFlow(...)
Additionally there are mappers transforming that StateFlow like
val mappedFlow: Flow<SomeOtherType> = repositoryExposedStateFlow.flatMapLatest { ... }
mappedFlow is no StateFlow anymore, but just a Flow. Thus, I cannot get the latest/current value as I can when there's StateFlow.
Anyhow, I need the latest value in that Flow at some point. Since this point is not in a ViewModel, but some Use Case implementation, I cannot simply perform a stateIn and hold the latest value in the ViewModel all the time the ViewModel is alive -- otherwise I had to pass on the value to all Use Cases. Actually, within a Use Case I trigger a network refresh which leads to emitting of new values on the StateFlow and thus on the mappedFlow, too.
In the Use Cases I have CoroutineScopes though. So I came up with
suspend fun <T> Flow<T>.getState(): T {
return coroutineScope {
val result = stateIn(
scope = this
).value
coroutineContext.cancelChildren()
result
}
}
Without using coroutineContext.cancelChildren() the method will never return, because coroutineScope blocks the caller until all child coroutines have finished. As stateIn never finishes, I manually cancel all children.
Apparently this is a bad thing to do.
But how can I solve this problem in a better way? In my perception the problem arises from StateFlow mapping resulting in regular Flow instances.
Yes, all you need is to call first() on the flow. Since it is backed by a StateFlow upstream, the first() call will get the current value of that backing StateFlow, run it through whatever transformations happen from the downstream operators, and return that value.
This effectively gets you the same result as your attempt above.
The downside is that all the downstream operators must be run, so it is potentially expensive.
This is only possible if there is an upstream StateFlow. Otherwise, there is no concept of a latest value for you to be able to retrieve.
I would challenge your need to get the latest value, though. Typically, you collect flows, so you're already working with a current value. Flows are intended for reactive programming.
Android, Kotlin
I have the following livedata in my datasource class, I cannot change this to StateFlow, so need to convert it to StateFlow in my viewModel
val trackingCatalogInitialLoadLiveData: LiveData<Pair<CatalogTracking, Int>> by lazy {
instantSearchDataSourceLiveData.switchMap { instantSearchDataSource ->
instantSearchDataSource.initialLoadLiveData
}
}
In My ViewModel I have the following, and this is the part I am not sure about if this is the correct way to convert LiveData to StateFlow:
val trackingCatalogInitialLoadStateFlow: StateFlow<Pair<CatalogTracking, Int>> by lazy {
instantSearchDataSourceFactory.trackingCatalogInitialLoadLiveData.asFlow()
.stateIn(viewModelScope, SharingStarted.Lazily, Pair(CatalogTracking(), 0))
}
Then in my fragment I just collect the results
coroutineScope.launch {
mInstantSearchViewModel.trackingCatalogInitialLoadStateFlow.collect { trackingPair ->
// code here
}
Is this the best practice to convert LiveData to StateFlow? Anything I should be looking out for?
You don't need to use by lazy. asFlow() and stateIn() both create simple wrappers, so they are trivial to call directly in the property initializer.
As #Joffrey said, if you use SharingStarted.Lazily, inspecting the flow's value before it has any collectors will incorrectly show your provided initial value. Since LiveData is hot, starting your StateFlow lazily doesn't buy you a lot. The underlying coroutine that transfers LiveData values to the StateFlow is doing a trivial amount of work.
If you don't need to inspect the value (in most cases you probably don't), then it should be fine to leave it as a cold Flow. Even though the Flow from asFlow() is cold, the underlying LiveData is still hot, so when collectors of the flow collect it, they'll always get the latest value. The main behavior difference would be if your data source does not provide a guaranteed initial value for the LiveData, then a StateFlow gives you the opportunity to emit your provided default initially without waiting for the LiveData to publish its first value.
In a ViewModel, remember isn't used, but mutable...is used:
class CustomViewModel : ViewModel() {
// ...
var myDeckList = mutableStateListOf<Deck>()
// ...
}
Does ViewModel have a delegated responsibility similar to what remember provides?
If so, why is mutable... not delegated?
remember is used to preserve state across recompositions. If we are storing state inside ViewModel, it will automatically survive recompositions because it's outside the composition tree.
mutableStateOf serves a different purpose. It creates a MutableState which is:
A mutable value holder where reads to the [value] property during the execution of a [Composable] function, the current [RecomposeScope] will be subscribed to changes of that value. When the [value] property is written to and changed, a recomposition of any subscribed [RecomposeScope]s will be scheduled.
It sets up an observer pattern (like a LiveData, StateFlow, etc.) where writes to the value inform the readers about the value change. So ViewModel has nothing to do with this observer pattern and that's why you still need to use mutable... functions in your ViewModel.
The mutableStateListOf that you have used in you question works along the same lines. It creates a SnapshotStateList which is a type of MutableList that is observable and can be snapshot.
Try using a remember{} function inside a viewModel.
It's just simply not possible since remember{} functions can be only called inside composable contexts
I am learning pagination using paging3 from the jetpack library. I am making a call to an api to receive a list of articles. I've noticed that the result received after making the call in the repository using the Pager is a flow of PagingData containing the desired result like so:
Flow<PagingData<Articles>>
When I receive this flow in my ViewModel I would like to convert it into a Stateflow. I have tried using the stateIn operator but it requires a default value which I think would be a StateFlow , this is where I am stuck. How can I convert the flow of PagingData into a Stateflow and is it advisable to do so?
I also have a question: why do you want to convert the Flow of PagingData into a Stateflow when you get the Article and show them on UI?
In my practice, no need to touch Stateflow. If I want to get Articles with Paging lib 3. Pay attention to Flow, ArticleRemoteMediator: RemoteMediator<Int, ArticleEntity> ...
I think this article can help you to archive your goal. (getting article with Paging 3) https://medium.com/nerd-for-tech/pagination-in-android-with-paging-3-retrofit-and-kotlin-flow-2c2454ff776e
I would say it depends on the usecase, while using regular flow every new subscriber triggers a new flow emission this can lead to resource wastage (unnecessary network requests) on configuration change eg screen ration, if you would desire such a behaviour its okey to use regular if not consider using StateFlow.
You can convert a regular flow emissions into StateFlow by using stateIn extension and set the initial state as empty as follows.
someFlow.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), PagingData.empty())
According to the documentation PagingData.empty() immediately displays an empty list of items with dispatching any load state update to a presenter
I am using a lot of LiveData in my projects and it's great in those cases where I need to pass something to views since it's intention is to be observed by lifecycle owners (i.e. views).
But I wonder what should I use in those cases when I need to apply some logic in my view models every time when some data from DB changes?
I am familiar with Transformations (map and switch) but (if I am right) they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic.
If I understand correctly, observing LiveData in viewModels is bad practice.
What is an alternative? Some of the RxJava observable types? Or something else?
"they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic."
Yes you're right. It's because:
Functions in both map() and switchMap() run on the Main Thread,
so no long running operations should be done there!
But I don't think observing LiveData in ViewModel is bad practice, because in Android we have MediatorLiveData for this purpose. If you take a look at source code of map and switchMap function, you'll see they use MediatorLiveData in there.
So the only problem here is that if some logic you want to execute is a long running task, you must run it in background thread when observe changes from the source LiveData. You can use Rx or something to run it in background thread like below:
private val _downloadState = MutableLiveData<DownloadDataState>()
val downloadState: LiveData<DownloadDataState> = _downloadState
// version observe changes in downloadState
val version = MediatorLiveData<String>().apply {
addSource(downloadState) {
// Whenever value of downloadState changes, this block will run your logic
Single.just(dataRepository.fetchDataVersion())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
// Then set value to the observer
value = result
},
{ e: Throwable ->
e.printStackTrace()
}
)
}
}