Why LiveData still notify Activity which is in onPause state? - android

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.

Related

Is there any guarenteed way to mimic some action between `onDestroy()` and `onCreate()` methods of an activity being recreated?

Here I have written a MutableLiveList<T> class that is intended to replace MutableLiveData<MutableList<T>> in my project. This is working fine till now. But if multiple modifications are done to the list when that activity is in inactive state, then only the last modification to that list will trigger adapter notify methods. In this case I don't know will the app crash. So I needed to mimic that situation exactly. When the screen rotates, activity gets destroyed and recreated. But I don't know how to add items to that list between that interval. Anyone please help me. Or tell me a better way to do it.
You could add them in the activity's onDestroy callback function. At that point the activity's Lifecycle object is either in the CREATED or DESTROYED state:
Point is it's not in the STARTED state at that point, and:
LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state. LiveData only notifies active observers about updates. Inactive observers registered to watch LiveData objects aren't notified about changes.
So by the time you're in onDestroy, any observer using that activity's Lifecycle won't see any updates from the LiveData. So you should be able to add values to the LiveData without getting notified.
By the way, that "only notified of the last value" update is by design:
Generally, LiveData delivers updates only when data changes, and only to active observers. An exception to this behavior is that observers also receive an update when they change from an inactive to an active state. Furthermore, if the observer changes from inactive to active a second time, it only receives an update if the value has changed since the last time it became active.
So if the data goes A, B, C while the observer is inactive, it won't be notified of those values. When it goes from inactive to inactive, it'll get the most recent value, C. It doesn't save a history of all the values an observer has missed

Can a user interaction trigger events before OnResume has finished?

In one of my Fragments, I am registering an OnFocusChangeListener for an EditText in onResume():
override fun onResume() {
super.onResume()
editText.setOnFocusChangeListener {
// do something here
}
}
I am registering the listener in onResume() because if I would set it in an earlier lifecycle method, it would be triggered on every configuration change. Setting it in onResume() makes sure that the focus that existed before a configuration change is already restored before the listener is registered, so the listener won't automatically fire after a configuration change / focus restore.
Now I fear that I am maybe registering this listener too late. So my question is: Can a user interaction already lead to focus for an element before or while onResume() is executed? (That would mean, that I would loose this focus event, because I am setting up the listener during onResume()). Or more general: is user interaction already possible while onResume() is being executed? The Fragment documentation says about onResume():
Called when the fragment is visible to the user and actively running.
It's clear what "visible to the user" means, but what exactly means "actively running"? Does this already mean accepting user input? Or is user input first accepted after onResume() has finished?
Focus restoration is done in the Activity's onRestoreInstanceState(), which is done separately from when Fragment's restore their own View's state (that would be in the Fragment's onViewStateRestored()).
As per the onRestoreInstanceState() documentation, it is called between the Activity's onStart() and onPostCreate() (which runs prior to onResume() and onPostResume() - onPostResume() is when Fragment's get their onResume() callbacks).
This means that you are correct in that there is no callback available at the Fragment level prior to onResume() where the focus is set correctly prior to that method being called.
That being said, yes, users can interact with a Fragment prior to it reaching the resumed state. For example, ViewPager2 (as well as ViewPager 1 when using BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) keep the non-selected fragments (i.e., those not in the middle of the screen) in the STARTED state. Through the power of multi-touch, users could drag a page over slightly and then use another finger to tap on a view that is partially visible. You'd see the same behavior if you use setMaxLifecycle() with Lifecycle.State.STARTED yourself (which is what those are doing under the hood) - the fragment is interactable, but not RESUMED.
In most general cases, however, if you're not using any of the above APIs, the Fragment lifecycle will generally match the Activity lifecycle. And an activity, as per the ActivityThread source code, does run its updates all in the same handleStartActivity() method.
It should be noted that every time the activity is destroyed, you will get a callback to your OnFocusChangeListener with hasFocus of false as the View is removed from the Activity (which always loses the View's focus). This happens after the state is saved, the View's focus state isn't actually lost, it is just something you already need to handle in your callback, usually by checking isStateSaved() and ignoring focus loss after the state is saved and checking isRemoving() if you are manually removing / replacing the Fragment (i.e., by doing a replace() operation).
Given that you already will have to have logic in your listener to avoid handling the hasFocus of false events post destruction, the 100% correct case for handling gaining focus would involve saving your own focused state (i.e., true or false for a specific view) in your saved instance state and only running your logic if the hasFocus is changing from what you already have saved. This means that you'd restore your saved instance state earlier in the Fragment's lifecycle (say, in onViewStateRestored() method that Fragments provide) and add your listener there. Then, your logic could safely ignore callbacks with the same focus:
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
// Restore your member variable of focus
focused = savedInstanceState?.getBoolean("HAS_FOCUS", false) ?: false
editText.setOnFocusChangeListener { _, hasFocus ->
if (focused == hasFocus) {
// ignore
return
}
focused = hasFocus
if (hasFocus) {
// We gained focus
} else if (!isStateSaved() && !isRemoving()) {
// We lost focus
}
}
}
Looking at FragmentManager source code, performResume call which triggers onResume is executed immediately after the fragment is started (and onStart is called):
https://android.googlesource.com/platform/frameworks/support/+/84448d71fda0a24ba5d60fe9368ac47b97564c88/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java#926
Starting the fragment is required for user interaction and no interactions can happen between onStart and onResume calls, since they can execute only on the same Main thread.
So, yes, no user input is possible before onResume.

What lifecycle method should a network request be put into if I want it to execute every time that activity comes into view?

The question is fairly self explanatory. If I want my app to make a request every time the user sees a particular activity, which method e.g. onCreate, onStart, etc., be put into so that it is always executed every time the user views the activity?
https://developer.android.com/guide/components/activities/activity-lifecycle
I think you are looking for onResume() method. When the activity enters the Resumed state, it comes to the foreground, and then the system invokes the onResume() callback. This is the state in which the app interacts with the user. The app stays in this state until something happens to take focus away from the app. Such an event might be, for instance, receiving a phone call, the user’s navigating to another activity, or the device screen’s turning off.
When the activity moves to the resumed state, any lifecycle-aware component tied to the activity's lifecycle will receive the ON_RESUME event. This is where the lifecycle components can enable any functionality that needs to run while the component is visible and in the foreground, such as starting a camera preview.
When an interruptive event occurs, the activity enters the Paused state, and the system invokes the onPause() callback.
If the activity returns to the Resumed state from the Paused state, the system once again calls onResume() method. For this reason, you should implement onResume() to initialize components that you release during onPause(), and perform any other initializations that must occur each time the activity enters the Resumed state.

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 .

Android Architecture Components Lifecycle state

https://developer.android.com/reference/android/arch/lifecycle/Lifecycle.State.html
STARTED
Lifecycle.State STARTED
For an Activity, this state is reached in two cases: after onStart
call; right before onPause call.
As the above documentation says, I couldn't understand the rationale behind the STARTED state right before onPause call. Can someone please explain that?
Notice that the values in the Lifecycle.State enum do not include a PAUSED state. There are only five states: CREATED, DESTROYED, INITIALIZED, RESUMED, STARTED. These do not exactly correspond with the normal Activity lifecycle that we all know and love:
Also note the following from the Lifecycle class documentation:
ON_CREATE, ON_START, ON_RESUME events in this class are dispatched after the LifecycleOwner's related method returns.
ON_PAUSE, ON_STOP, ON_DESTROY events in this class are dispatched before the LifecycleOwner's related method is called
The execution of the onPause() is the closing boundary for the RESUMED state. At this point, the Activity is no longer considered RESUMED and it is certainly not DESTROYED. Since we don't have a PAUSED state it follows it must now be in the STARTED state. This is technically true, the Activity hasn't yet stopped but it is no longer resumed.

Categories

Resources