In MyViewModel a MutableStateFlow is used to transmit events to the fragment.
When the value of the MutableStateFlow is changed the earlier values are being overwritten inside the coroutine. So never received by fragment.
internal class MyViewModel(application: Application) : AndroidViewModel(application) {
private val myMutableStateFlow = MutableStateFlow<MySealedClass>(MySealedClass.Dummy1())
private fun getData() {
viewModelScope.launch {
//yield()
myMutableStateFlow.value = MySealedClass.Dummy2()
myMutableStateFlow.value = MySealedClass.Dummy3()
}
}
}
internal class MyFragment : Fragment(){
private var uiStateJob: Job? = null
override fun onStart() {
super.onStart()
uiStateJob = lifecycleScope.launch {
myViewModel.getUiFlow().collect {
//do something
}
}
}
}
If yield() is commented Dummy2 event is never received by the Fragment. Dummy 3 is received though.
If yield() is uncommented Dummy2 & 3 are both received.
If the state values are changed outside the coroutine then both Dummy2 and Dummy3 are received.
I need to predictably receive all events in my fragment.
Is there a proper reasoning for this behaviour?
StateFlow is meant to represent a state. Each event is technically a new up-to-date state value, making the previous states obsolete. This type of flow is for situations when only the latest state matters, because its events are conflated. From the docs:
Updates to the value are always conflated. So a slow collector skips fast updates, but always collects the most recently emitted value.
Edit in response to your comment: yield() is a suspend function that forces the suspension of the current coroutine. Therefore it gives a chance to the other coroutine to progress until its next suspension point, this is why in that case the collect is "ready" before the first value is set (and emitted).
However you shouldn't rely on that because it's brittle: if the other coroutine gets modified and has extra suspension points by calling other suspend functions, it might not reach the collect call, and you would be back to the other behaviour.
If you consistently need all events, you have several options:
Switch to a cold flow, which will only start when you collect
Use a Channel (with or without buffer)
Use a SharedFlow and trigger the events start by using onSubscription
Related
I would like to ask you why does it work?
Normally when I used collectLatest with flow my data wasn't collected on time and the return value was empty. I have to use async-await coroutines, but I have read it blocks main thread, so it is not efficient. I've made my research and find the solution using sharedflow.
Previously:
suspend fun getList: List<Items> {
CoroutineScope(Dispatchers.Main).launch {
async {
flow.collectLatest {
myItems = it
}
}.await()
}
return myItems
}
or without await-async and it returns emptyList
now:
suspend fun getList: List<Items> {
val sharedFlow = flow.conflate().shareIn(
coroutineScopeIO,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
return sharedFlow.first()
}
conflate means:
Conflates flow emissions via conflated channel and runs collector in a separate coroutine. The effect of this is that emitter is never suspended due to a slow collector, but collector always gets the most recent value emitted.
I'm not sure I understand it clearly. When I conflate flow, I just create seperate coroutine to emit what will be inside my another function as in my example shareIn().first() and using this variablesharedFlow which is suspended so will give the same effect I made asnyc-await, but in that case I do not block main thread, but only my exact *parentCoroutine-or-suspendFunction?
SharingStarted.WhileSubscribed()
It just means to start emit when subcribed.
conflate() has nothing to do with why this is working. The separate coroutine it talks about is run under the hood and you don't need to think about it. It's just to make sure your flow never causes the upstream emitter to have to wait for a slow collector, and your collector skips values if they are coming faster than it can handle them. conflate() makes it safe to have a slow collector without a buffer.
In your first code block, you are launching a new coroutine in a new CoroutineScope, so it is not a child coroutine and will not be waited for before the function returns. (Incidentally, this new coroutine will only finish when the Flow completes, and most types of Flows never complete.)
In the second code block, you are calling first() on the Flow, which suspends and gets the next value emitted by the flow and then returns that value without waiting for the Flow to complete.
Some other notes:
You should never use async { /*...*/ }.await() where await() is called immediately on the Deferred, because it is just a more convoluted version of withContext(/*...*/) { /*...*/ }.
It's a code smell to create a CoroutineScope that you never assign to a property, because the point of creating a scope is so you can manage the scope, and you obviously aren't managing it if you have no reference to it to work with.
You said you are worried about blocking the main thread, but nothing in the code you showed looks suspicious of blocking the main thread. But it's possible your flow that you are basing this on has blocking code in it. By convention it shouldn't. If that flow blocks, you should use the flowOn(Dispatchers.IO) operator on it at the source so downstream users don't have to worry about it.
Although your code worked, it doesn't make sense to create a SharedFlow in a function and immediately collect from it. It's not being shared with anything! Your code could be simplified to this equivalent code:
suspend fun getList: List<Items> {
return flow.first()
}
Let's say that we have a simple fragment with a view based on the UI state held in StateFlow in the view model.
On onCreate() we collect state as usually:
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launchWhenStarted {
viewModel.uiState.collect {
// UI update according to state
}
}
}
Now we navigate to the next fragment - the previous is kept by the fragment manager, but the view is destroyed. On the new fragment, we pop back stack and now is surprising:
the previous fragment is recreating the view on the initial state and even if we try to update state flow nothing will happen since it doesn't emit equal data twice one by one.
So, how to restore the view state after return to the fragment?
Maybe a bit later but what you are looking for is repeatOnLifeCycle
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { ... }
}
}
repeatOnLifecycle together with lifecycleScope.launch allows you to collect flows from a Fragment or Activity.
The main difference with your approach is that launchWhenStarted will run you collection function ONCE the UI State is started, but will not repeat the collection when a fragment is restored, while repeatOnLifeCycle will create a new coroutine starting the collection everytime the lifecycle scope reaches the specified state (hence the name) and will cancel it when the lifecycle reaches the opposite state (for example, if you specified STARTED, then the coroutine will be cancelled on onStop).
In addition to that, the launchWhenX documentation nowadays indicates the following
Caution: This API is not recommended to use as it can lead to wasted
resources in some cases. Please, use the Lifecycle.repeatOnLifecycle
API instead. This API will be removed in a future release.
PD, lifecycleScope.launch { ... } creates a coroutine, and is required as repeatOnLifecycle is a suspend function (suspend functions run on a coroutine or another suspend function)
I have an activity that uploads pictures. Inside this activity I have following observer which is working perfectly:
pictureViewModel.customCreateResult.observeForever { result -> onResponsePostPicture(result!!)}
I need to use observeForever because the user sometimes navigates to other activities. This is working fine and is not the problem. When the user decides to leave this activity with the observer. So when finishing the activity I'm calling:
override fun onDestroy() {
super.onDestroy()
pictureViewModel.customCreateResult.removeObserver{ result -> onResponsePostPicture(result!!)}
}
When for example 2 out of 4 pictures are uploaded and then the user finishes the activity but decides to reopen the activity. I'm getting a respond of the last 2 pictures from the observer. So my removeObserver is not working. What am I doing wrong?
You are not adding and removing same observer each time you are creating anew one . You are passing a lambda which is a new observer every time . Below is an example .
private var observer:Observer<String> = Observer {
onResponsePostPicture(it!!)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
pictureViewModel.customCreateResult.observeForever(observer)
}
override fun onDestroy() {
super.onDestroy()
pictureViewModel.customCreateResult.removeObserver(observer)
}
Alternatively you can use #removeObservers(this) Which will remove all the observers corresponds to Lifecycle Owner.
override fun onDestroy() {
customCreateResult.removeObservers(this)
super.onDestroy()
}
To add to what #ADM said, the issue is specifically that passing a lambda to observeForever/removeObserver creates a new Observer object that the system holds onto. Even if you pass the same lambda instance by holding it as a val, or anything like that, internally it's a new and completely different object.
So by registering an observer in this way, you cannot remove it with removeObserver and it will continue to receive events, and it can create large memory leaks if your lambda has references to things which lead back to something like an Activity.
The documentation doesn't warn you about this, and code completion for observeForever even suggests the lambda version, which is the way the rest of the Kotlin LiveData observer examples are written. It's a huge problem waiting to silently happen, and I wish they'd at least make people aware
I'm working on small android app using MVVM pattern.
My issue is that my ViewModel observer in MyActivity not called from the background. I need it to be called even if the app is in background to show system Notification to the user that app calculation process is done and the result is ready.
This is the current implementation located in onCreate in MyActivity:
mainActivityViewModel.getTestResult().observe(MainActivity.this, new Observer<String>() {
#Override
public void onChanged(#Nullable String blogList) {
Toast.makeText(getApplicationContext(), "test...", Toast.LENGTH_SHORT).show();
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)){
//The app is in foreground - showDialog
}else{
//The app is in background - showNotification
}
}
For now, this observer will be called only if the app is in foreground - if the process done while app was in foreground - 'showDialog' will trigger, if the app was in background - the showNotification will trigger - but only after I will open the app again. It's not the behaviour that I try to achieve. Please help! Thanks.
onChanged will only be called if the Activity's current Lifecycle state is at least STARTED. onPause gets called when you leave the Activity, which means it's not at least STARTED.
LiveData is simply not suitable for the behavior you're trying to achieve.
I would recommend you to use a foreground Service instead. Especially if the mentioned "calculation process" is something that the user should be aware of.
edit:
Let's say you're performing some potentially long running task in the background and you want to continue this task even if the user would leave or even close your Activity. Then using a Service is a good option, and especially a foreground Service if the task is the result of a user action. For example, the user clicks an "upload" button, a foreground Service performs the task and the associated Notification says "Upload in progress".
You have the option to either
Always show a new Notification when the task is complete, regardless of if the Activity is shown or not. This is pretty common.
Only show the Notification if the Activity is not currently started, and if it is started, show something in the Activity view instead.
In order to do the latter option, you need to know the current status of the Activity's Lifecycle. You want to be able to do the following check from your service somehow: getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
The best way to communicate between an Activity and Service is binding to the Service and extending the Binder class in the Service.
After binding, you may store the Activity Lifecycle status in a variable in the Service, or even provide the Activity itself to the Service.
I guess your getTestResult() in ViewModel returning some live data.
So first of all, you are assigning your real data with LiveData using .setValue(some_data) method. And it is working fine while app is open. Btu when your app is in background. You need to use .postValue(some_data) method to assign data with that LiveData.
Check difference below:
setValue()
Sets the value. If there are active observers, the value will be dispatched to them. This method must be called from the main thread.
postValue()
Posts a task to a main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
Conclusion, the key difference would be:
setValue() method must be called from the main thread. But if you need set a value from a background thread, postValue() should be used.
I saw this question researching for the same issue and even though it was asked 2 years ago I was able to let LiveData notify the observer even though the Fragment (or in question's case, an Activity) is either paused or stopped, so I am posting my solution here.
The solution is for a fragment, but can be adapted to activities as well.
On the fragment:
class MyFragment: Fragment() {
private var _lifecycleWrapper: LifecycleOwnerWrapper? = null
val activeLifecycleOwner: LifecycleOwner
get() {
if (_lifecycleWrapper == null)
_lifecycleWrapper = LifecycleOwnerWrapper(viewLifecycleOwner)
return _lifecycleWrapper!!
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
// On the livedata, use "activeLifecycleOwner"
// instead of "viewLifecycleOwner"
myLiveData.observe(activeLifecycleOwner) { value ->
// do processing even when in background
}
}
override fun onDestroyView() {
super.onDestroyView()
_lifecycleWrapper = null
}
}
LifecycleOwnerWrapper:
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
/**
* A special lifecycle owner that lets the livedata
* post values even though the source lifecycle owner is in paused or stopped
* state. It gets destroyed when the source lifecycle owner gets destroyed.
*/
class LifecycleOwnerWrapper(sourceOwner: LifecycleOwner):
LifecycleOwner, LifecycleEventObserver
{
private val lifecycle = LifecycleRegistry(this)
init
{
sourceOwner.lifecycle.addObserver(this)
when (sourceOwner.lifecycle.currentState)
{
Lifecycle.State.DESTROYED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
Lifecycle.State.CREATED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
Lifecycle.State.STARTED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
Lifecycle.State.RESUMED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
else ->
{
// do nothing, the observer will catch up
}
}
}
override fun getLifecycle(): Lifecycle
{
return lifecycle
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)
{
if (event != Lifecycle.Event.ON_PAUSE && event != Lifecycle.Event.ON_STOP)
lifecycle.handleLifecycleEvent(event)
}
}
The only thing you need to do is to not call this after onDestroy (or for viewLifecycleOwner, after onDestroyView) otherwise the lifecycle owner will be stale.
What you are trying to do is possible but not in the way you are doing it.
The whole purpose of the LiveData API is to link the data layer with the UI in a life cycle aware manner, so when the app is not in foreground then the observer knows that and stop updating the UI.
The first argument on the observer is the lifecycle.
This is a great improvement because without it the crashes because UI was not available were too often or it was too complex to control manually (boilerplate, edge cases, etc).
Service is not a good idea because the services can be killed by the DALVIK or ANT machine if the memory is needed for the foreground app. Services are not in the foreground but that doesn't mean that are bound to background neither that are guaranteed to be working for a undeterminated span of time.
For doing what you wish use the WorkManager. The WorkManager allows you to schedule jobs with or without conditions and from there you are gonna be able to send a Notification to the user.
You can try for a combination of Workmanager and Viewmodel to achieve an foreground/background app functionality.
For this use the Activity life cycle:
Use the onResume method to remove any WorkManager and star using the ViewModel
Use the onPause method to star the WorkManager
To handle the declaration, you can edit or dismiss the declaration from inside the function in your ViewModel class where the data was successfully retrieved.
private fun dataShow(list: List<String>) {
//Notification cancel
NotificationManagerCompat.from(getApplication()).cancel(30)
if (list.isNotEmpty()) {
data.value = list
progressHome.value = false
} else {
progressHome.value = true
}
}
Mutable LiveData in a ViewModel:
val viewStateLiveData: MutableLiveData<SomeViewState> = MutableLiveData()
When a network call fails, this function (in the ViewModel) is called from the main thread to update the view state object (to display a Snackbar, but then reset the state so it isn't re-displayed):
#UiThread
private fun onFailure() {
viewStateLiveData.value = viewStateLiveData.value?.copy(retrievalSuccess = false, showProgress = false)
viewStateLiveData.value = viewStateLiveData.value?.copy(retrievalSuccess = null)
}
The Fragment observes the MutableLiveData:
viewModel.viewStateLiveData.observe(this, Observer { viewState ->
Timber.i("Update ViewState: $viewState")
})
But the output is this single observer call (representing the second mutation):
Update ViewState: ViewState(retrievalSuccess=null, showProgress=false)
Rather than what I would expect, which would be two calls to the observer, reflecting both mutations:
Update ViewState: ViewState(retrievalSuccess=false, showProgress=false)
Update ViewState: ViewState(retrievalSuccess=null, showProgress=false)
Why is MutableLiveData.setValue() not triggering the observer after both mutations? The docs say "Sets the value. If there are active observers, the value will be dispatched to them." but it's hard not to conclude that this is not the case as setValue() is being called, but the observer (which is active) is not being triggered. The observer is definitely set up in advance of the MutableLiveData mutations.
I experimented with a series of mutations, and only ever see one call to the observer, which receives a view state object that combines all the prior mutations. So the calls to setValue() are mutating the view state, but only the last call in a series is triggering the observer.
I tried using postValue() in vain. Oddly, this behavior I'm describing was not present until recently, but it's not clear what has changed that has caused these issues.
This was a Fragment lifecycle/timing issue, and ultimately caused by the ViewState mutations happening before the Fragment was in the active state, and so the LiveData just received a single event when the Fragment reached the Active, representing all mutations up to that point in a single initial event.
To fix it these changes were made:
onCreate() - create ViewModel
onViewCreated() - start observing the LiveData
onResume() / onStart() - make the network request (which starts the flow of mutated view states)
In debugging this issue it was really helpful to add logging to this, to see what state the Fragment was in: fragmentManager?.registerFragmentLifecycleCallbacks(...)