Why LiveData can receive the last available data automatically? - android

I am reading LiveData source code, and for this method:
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<?super T> observer) { .. }
And this is part of its doc:
When data changes while the owner is not active, it will not receive
any updates. If it becomes active again, it will receive the last
available data automatically.
I was trying to figure out why LiveData can achieve this lifecycle-awareness. I read source code of setValue but still couldn't get it. Can anybody help me with the on the general idea?

Suppose you are not using live data and you are showing list of data with pagination concept . You as user have scroll down to view more and more data and application is calling apis to get data as you scroll . Now you have rotated your device so as developer we know that your activity will be recreated and user will be at initial stage again as all things were destroyed .. Well you can achieve this using onSaveInstance but you will have to code and manage yourself ..
So user will have to scroll again to view all data but imagine if you have some mechanism where you get your last updated data whenever activity lifecycle changes so you can easily set data again and allow user to use your app like nothing happened .. and here livedata concept come into picture with lifecycle awareness
Hope this answer will clear your doubts
Edit :-
To understand how they are managing lifecycle , you can visit this link
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
here is source code method of LiveData class where you can see(at last line) how they are adding lifecycler owne to observer

Read the source code of LiveData.java (in lifecycle-livedata-core:2.2.0#aar) again, it seems clear to me now.
When adding an observer to LiveData via liveData.observe(lifecycleOwner, Observer { .. } ), the magic happens in the observe method.
In the observe method, it puts the lifecycleOwner and the Observer into a new object called LifecycleBoundObserver like this:
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// ... some other code
owner.getLifecycle().addObserver(wrapper);
Important to note and we can also tell it from the last statement: the LifecycleBoundObserver is an instance of LifecycleObserver, that means, it can be notified when the given lifecycleOwner gets state updated, the key lies in the onStateChanged method of LifecycleBoundObserver.
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
So:
If the lifecycleOwner (the Activity or Fragment) gets DESTROYED, it will remove the observer (the callback observer, not the LifecycleObserver), hence the observer won't be notified for new data once it's DESTROYED.
If it is not DESTROYED, it requires the lifecycleOwner in Active states (STARTED or RESUMED), this is restricted by the return value from the method shouldBeAlive(), and finally the new data gets delivered to observer callback in the method activeStateChanged.
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
If the input param newActive is true, then finally it will reach the statement of dispatchingValue(this) - the last statement, and if it is false (i.e.: inactive states: PAUSED / STOPPED / DESTROYED), it won't call dispatchingValue(this), thus the observer callback won't be triggered.
If the Activity / Fragment goes back to foreground from background, it becomes Active again, then the LifecycleBoundObserver will be notified and the onStateChanged will be called again, and this time, when calling activeStateChanged(newActive), it passes true, therefore, dispatchingValue(this) will be called, and the latest data set via setValue or post will be picked up, that's the reason for explaining why the Activity can get the last emitted / latest value of LiveData.

Related

livedata observe event triggered at each fragment onStart

I am having issues with LiveData.
I have 3 fragments A (menuFragment), B (doThingsFragment), C (displayMessageFragment).
We can move from A to B, from B to C and finally from C to A.
On the onStart method of doThingsFragment, I observe LiveData From doThingsViewModel and I move to displayMessageFragment when this value changes.
doThingsViewModel :
private var _message : MutableLiveData<String> = MutableLiveData()
var message : LiveData<String> = _message
fun someFunction() {
_message.value = "blablabla"
}
doThingsFragment :
override fun onStart() {
super.onStart()
mViewModel!!.message.observe(
viewLifecycleOwner,
Observer<String>
{
msg ->
if(!msg.isNullOrEmpty() && mViewModel!!.toastType.value != null){
val args = Bundle()
args.putString("message", msg)
Navigation.findNavController(requireView()).navigate(R.id.mountUnmountValidationFragment, args)
}
})
}
It works great the first time I update _message (we go from B to C as expected).
However, when I try to move from A to B again, we go into doThingsFragment observer callback again and I move on to fragment C before fragment B is even displayed.
Since _message value has not changed, I expect not to trigger the observer callback everytime doThingFragment.onStart method is called...
What am I missing ?
Thanks a lot !
It is the way how livedata behave, if you need receiver event only one time, google or stackoverflow about SingleLiveData
LiveData works on an Observer pattern, where something registers as an observer, and every time the thing it's observing changes, it gets notified. It's worth reading the docs on this:
Adds the given observer to the observers list within the lifespan of the given owner. The events are dispatched on the main thread. If LiveData already has data set, it will be delivered to the observer.
When data changes while the owner [LifecycleOwner, e.g. the Activity] is not active, it will not receive any updates. If it becomes active again, it will receive the last available data automatically.
These are the important points - when you register to observe the data, you'll immediately be notified of the most recent value (if there is one), not just when there's a change. So you can't rely on that callback triggering as a signal that you need to move from B to C.
What you actually need to do depends on your app logic, how you can tell the difference between "fragment B just displayed, here's a value, and now I need to show fragment C" and "fragment B just displayed, here's a value, but I happen to know I don't need to show fragment C this time". You could do things with setting an initialised flag the first time you receive some data (might need to persist that state in onSaveInstanceState) or holding the last-seen value (harder to persist) but you get the idea

Android ViewModel observer not called from background

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
}
}

LiveData observer's onChanged gets called on activity rotation even though there was no change in the data

I'm working with ViewModels and LiveData in an Android app, and I want to use them to keep track of data for an Activity even when the screen is rotated. This works quite well, but there's one issue I can't fix. In the Activity's onCreate method, I register an observer for a LiveData containing a list of objects, which should just add a Fragment to the activity if the data is loaded. Then, I only reload the data if savedInstanceState is null, which should stop it from being reloaded in the event of a screen rotation. This looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
model = ViewModelProviders.of(this).get(MainActivityModel.class);
observeList();
if (savedInstanceState == null) {
loadList(); //Reload the list and call postValue() on the LiveData.
}
}
private void observeList() {
model.getList().observe(this, new Observer<ArrayList<Object>>(){
#Override
public void onChanged(#Nullable ArrayList<Object> objects) {
//Add list fragment whenever data changes.
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, ListFragment.getInstance(list))
.commit();
}
});
}
The way I understand it, the ListFragment should only be shown when data changes. However, after some debugging, it seems the onChanged method in the observeList method gets called every single time the screen is rotated. Additionally, I checked whether the actual list was changed and it was exactly the same, no difference at all and there was never even a postValue() or setValue() method call. Therefore, I have no idea why the onChanged method would be called on screen rotation.
This also only happens when the list contains something. When the app is started up, the list has a length of 0 before loadList() is called. When the observer is registered at this state, the onChanged method is not called.
My best guess is that the onChanged method gets triggered either because the list is not empty when the observer is registered so the observer could think there is a change in data. Can anyone explain why this might be happening?
This is working as intended. From livedata overview:
To ensure that the activity or fragment has data that it can display as soon as it becomes active. As soon as an app component is in the STARTED state, it receives the most recent value from the LiveData objects it’s observing. This only occurs if the LiveData object to be observed has been set.
Although there's missing code snippets - loadList should be a part of ViewModel itself, not a method of activity.
I had the same problem and I found a solution in this code lab: Training LiveData
Usually, LiveData delivers updates to the observers only when data changes. An exception to this behavior is that observers also receive updates when the observer changes from an inactive to an active state.
When the (...) fragment is re-created after a screen rotation, it moves from an inactive to an active state. The observer in the fragment is re-connected to the existing ViewModel and receives the current data.
So the solution is basically to create some method in the View Model that resets the value of the variable that triggers the observer (in your case, the list).
when a screen is rotated, the following callback methods in the activity lifecycle are called
onSaveInstanceState()
onPause()
onStop()
onCreate()
onStart()
onRestoreInstanceState()
onResume()
it would help if you kept in mind that the live data observer is lifecycle aware, meaning that it will observe when the UI is in an active state and will stop observing when the UI is not in an active state. when the onStop method is called the live data observer stops listening and when the onCreate method is called again the live data observer starts to observe again. that is why the observer is reloaded .

Why LiveData still notify Activity which is in onPause state?

I have the following code in Activity
#Override
public void onPause() {
super.onPause();
if (isFinishing()) {
final LiveData<StickyNoteConfig> stickyNoteConfigLiveData = StickyNoteConfigRepository.INSTANCE.getStickyNoteConfig(mAppWidgetId);
stickyNoteConfigLiveData.removeObservers(this);
stickyNoteConfigLiveData.observe(this, stickyNoteConfig -> {
// Weird, I still can receive call back.
// I thought "this" is no longer active?
});
}
}
I feel puzzled that, Observer is still being triggered, although this activity is already in onPause state? According to https://developer.android.com/reference/android/arch/lifecycle/Lifecycle.State#STARTED
Started state for a LifecycleOwner. For an Activity, this state is
reached in two cases:
after onStart call;
right before onPause call.
May I know why it is so?
According to LiveData reference,
LiveData is a data holder class that can be observed within a given lifecycle. This means that an Observer can be added in a
pair with a LifecycleOwner, and this observer will be notified about
modifications of the wrapped data only if the paired LifecycleOwner
is in active state. LifecycleOwner is considered as active, if its
state is STARTED or RESUMED.
An observer added with a Lifecycle will be automatically removed if the corresponding Lifecycle moves to DESTROYED state.
Now, according to your situation here, LiveData receives updates for your observer (your activity) in onPause() method, because your observer is not already in DESTROYED state.
So LiveData is still active for receive updates according to these methods:
onActive() :
Called when the number of active observers change to 1 from 0.
This callback can be used to know that this LiveData is being used thus should be kept up to date.
&
onInactive() :
Called when the number of active observers change from 1 to 0. This does not mean that there are no observers left, there may still be observers but their lifecycle states aren't STARTED or RESUMED (like an Activity in the back stack).
You can check if there are observers via hasObservers().
So when does observer (your activity) gets DESTROYED state?
For default implementation of LifeCycleOwner indicates that activity gets it's DESTROYED state once onDestroy() method is executed & after onPause() it follows reverse order of LifeCycle state RESUMED -> STARTED -> CREATED -> DESTROYED.
Check this lifecycle graph.
Hope it helps !
https://medium.com/#hanyuliu/livedata-may-lose-data-2fffdac57dc9 has logs to explain this issue. And just like #Vairavan said,in pause state activity can be partially visible.
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
public boolean isAtLeast(#NonNull State state) {
return compareTo(state) >= 0;
}
}
so, observer's isAlive judge according to isAtLeast(STARTED). when OnPause is called, activity does not become DESTROYED.
This becomes obvious when onPause and onStop is related to UI side effects. An activity is paused and not stopped if any part of the activity window is still visible. This could happen when another activity is shown on top of the paused activity as a dialog. In this case, more often than not, developers would like the paused activity's UI to still be updated even though it is only partially visible. LiveData's update in this paused state helps do that.
There are other cases like multi-window feature. One activity could be paused while the user is interacting with another app/activity in a different window. Paused activity could be a video playback with active updates and should be updated even though user is interacting with another app. Note that this implementation is changing from Android P for foldable phones where multiple activities/windows could be in resumed state (that is not the concern for live data updates anyways). https://android-developers.googleblog.com/2018/11/get-your-app-ready-for-foldable-phones.html
Normally, onPause (without onStop) will happened when a partially of screen is cover by other screen (eg: notification from other app, or open a transparent activity).
Because, whole screen is not cover, so we may see some part of the current screen.
Therefore, I think LiveData may still need to notify in on pause state to make sure user always see the latest data.
If user go to background, or navigate to other screen, then the whole of screen is cover, we dont need to update UI because user dont see it. So LiveData dont need to notify in onStop state
Helpful answers:
Android: Scenario where onPause is called but not onStop?
LiveData only updates app component observers that are in an active lifecycle state.
If anyone wants to perform any lifecycle-related work they must override onActive() and onInActive() methods.
eg:
public class StockLiveData extends LiveData<BigDecimal> {
......
#Override
protected void onActive() {
}
#Override
protected void onInactive() {
}
}
Important Line:
An observer added with a Lifecycle will be automatically removed if
the corresponding Lifecycle moves to DESTROYED state.

How to Resubscribe to Observables

I have a bottom bar with four tabs. Each tab is a Fragment. I want to stop any network calls when user moves to another Fragment so I'm adding all Observable calls to a CompositeSubscription and I unsubscribe from them in onDestroyView(). Next time user enters the screen all network calls fail (since I have unsubscribed) so I want to subscribe again.
I'm not sure how I am supposed to do this : somehow I have to re-subscribe when onResume()/onViewAttached() is called for the Fragment. I'm using RxJava 1.
Edit : I have checked similar questions and their answers mention cache and replay operators but I don't think that's the case cause they were asking to also get the previously emitted items, while I just want to be able to perform again any network calls.
This is how I'm subscribing to an Observable for a network call :
subscriptions.add(remoteDataSource.getFeedMore(localDataSource.getFirstStoredId())
.doOnNext(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
if (wrapper != null) {
localDataSource.saveFeed(wrapper.getFeedItemList());
localDataSource.saveServerState(wrapper.getFeedRequestDetails());
}
}
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
// call to View to update
}
}));
And how I unsubscribe :
#Override
public void unsubscribe() {
if (subscriptions != null && subscriptions.hasSubscriptions()) {
subscriptions.unsubscribe();
}
}
Example Use Case : user enters Timeline screen, user clicks a button and a network call is executed ( modeled as an Observable in my Presenter class like the code I posted right above ). Then user exits this screen (
onDestroyView() is called and any subscriptions are unsubscribed). Some time later user enters Timeline screen again and clicks the button.
This is when I receive HTTP FAILED: java.io.IOException: Canceled cause I have unsubscribed and I want to re-subscribe again so I can execute the network call without errors.
Thanks
Update
If you call unsubscribe in CompositeSubscrition you can't add new subscriptions to it again. If you want to use the same composite instance again, then you need to call subscriptions.clear() or you can create a new instance when the fragment is initialized.
Prev
First things first, if you unsubscribe from any observable/stream/flowable etc. you gonna lose the any incoming data/events.
If you want to get the latest result of an subscription then obviously you should do it before unsubscribe happens.
The problem here is your subscriptions should not be dependant on any Fragment or Activity lifecycle unless it's totally finished/destroyed.
So if you know that you have long requests you should subscribe them on a android.app.Service.
Then you will face another problem communicating back and forth between Services and Fragments/Activities.
The easy solution on your case is you can create a BehaviourSubject in a singleton class (better to use Dagger to inject that model to both fragment and service). Then in your Service subscribe to your long running stream and publish the next events to that BehaviourSubject
BehaviourSubject saves the last emitted data. So next time you subscribe them in your newly created fragment it will start with the last emitted item.
Of course this answer just cover one use-case according to your needs you may need to do something else.

Categories

Resources