Reputation: 42760
Currently, I need to perform some actions, when
Hence, the following code snippet serve me pretty well so far. I learn such trick from CommonWare's - https://commonsware.com/AndroidArch/previews/other-lifecycle-owners and https://proandroiddev.com/react-to-app-foreground-and-background-events-with-processlifecycleowner-96278e5816fa
public class WeNoteApplication extends Application {
public static class AppLifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onResume(LifecycleOwner owner) {
// Do something when the application launched.
// But not during activity recreation, configuration change, ...
}
@Override
public void onPause(LifecycleOwner owner) {
// Do something when the application quit.
// But not during activity recreation, configuration change, ...
}
}
private static final AppLifecycleObserver appLifecycleObserver = new AppLifecycleObserver();
@Override
public void onCreate() {
super.onCreate();
initLifecycleObserver();
}
private void initLifecycleObserver() {
Lifecycle lifecycle = ProcessLifecycleOwner.get().getLifecycle();
lifecycle.removeObserver(appLifecycleObserver);
lifecycle.addObserver(appLifecycleObserver);
}
}
However, I also need to perform some actions, by using Activity
, Fragment
, ... For instance, showing a DialogFragment
.
For my entry point main Activity
, here's what I had tried.
public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
setContentView(R.layout.activity_main);
}
@Override
public void onResume(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onResume LifecycleOwner called");
}
@Override
public void onPause(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onPause LifecycleOwner called");
}
@Override
public void onCreate(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onCreate LifecycleOwner called");
}
}
It doesn't work as expected due the following following observations
onCreate LifecycleOwner called
onResume LifecycleOwner called
onResume LifecycleOwner called <-- Why onResume of LifecycleOwner is called twice??
onCreate LifecycleOwner called
onResume LifecycleOwner called <-- Why onCreate and onResume of LifecyclOwner is called during configuration change?
I tried to use LiveData
in order for AppLifecycleObserver
to communicate with Activity
. However, during configuration change, onResumeLiveData
treats re-created Activity
as new lifecycle owner. Hence, it will trigger it again.
public class WeNoteApplication extends Application {
public MutableLiveData<LifecycleOwner> onResumeLiveData = new MutableLiveData<>();
public class AppLifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onResume(LifecycleOwner owner) {
// This will only be called during app launch, not configuration change.
android.util.Log.i("CHEOK", "onResume callback happen in application");
onResumeLiveData.setValue(owner);
...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WeNoteApplication.instance().onResumeLiveData.observe(this, new Observer<LifecycleOwner>() {
@Override
public void onChanged(@Nullable LifecycleOwner lifecycleOwner) {
// This will only be called during app launch
// This will also be called during configuration change.
android.util.Log.i("CHEOK", "onResume callback happen in activity");
}
});
So, I'm some how confused. What is a correct way, for an Activitly
(or Fragment
) to observe Lifecycle
event? Meaning, those call back event functions shouldn't be triggered, during configuration change, activity re-creation, ...
Upvotes: 13
Views: 9929
Reputation: 3454
The root of your problem is inside LifecycleRegistry.addObserver
, you see:
void addObserver (LifecycleObserver observer) Adds a LifecycleObserver that will be notified when the LifecycleOwner changes state.
The given observer will be brought to the current state of the LifecycleOwner. For example, if the LifecycleOwner is in STARTED state, the given observer will receive ON_CREATE, ON_START events.
So let's see what's happen when you add a new Observer
to a LifeCycleRegistery
:
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
...
State targetState = calculateTargetState(observer);
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}
...
}
When you add a new observer
, the LifecycleRegistery
tries to bring the observer
state to its own state, in your case iterating through the Activity
state and since the state is starting from INITIALIZED the registry dispatch events all the way to it's current state which is RESUMED:
private State calculateTargetState(LifecycleObserver observer) {
Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer);
State siblingState = previous != null ? previous.getValue().mState : null;
State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1)
: null;
return min(min(mState, siblingState), parentState);
}
TL,DR: So the duplicate event sequence you see when Activity
is re-created (or the future duplicate events you gonna see when you register a new observer in a second activity) are actually from the observer
lifecycle which is the Activity
lifecycle itself!
A WORKAROUND would be querying the process's state itself instead of relying only on events, so replace this:
public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {
...
@Override
public void onResume(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onResume LifecycleOwner called");
}
@Override
public void onPause(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onPause LifecycleOwner called");
}
@Override
public void onCreate(LifecycleOwner owner) {
android.util.Log.i("CHEOK", "onCreate LifecycleOwner called");
}
}
with this:
public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {
...
@Override
public void onResume(LifecycleOwner owner) {
if(owner.getLifecycle().getCurrentState() == Lifecycle.State.RESUMED)
android.util.Log.i("CHEOK", "onResume LifecycleOwner called");
}
@Override
public void onPause(LifecycleOwner owner) {
if(owner.getLifecycle().getCurrentState() == Lifecycle.State.STARTED)
android.util.Log.i("CHEOK", "onPause LifecycleOwner called");
}
@Override
public void onCreate(LifecycleOwner owner) {
if(owner.getLifecycle().getCurrentState() == Lifecycle.State.CREATED)
android.util.Log.i("CHEOK", "onCreate LifecycleOwner called");
}
}
, the SOLUTION would be using a single source of truth like a ViewModel
or ApplicationClass
like the way you did to receive LifeCycle.Event
;
Now if you plan to do an action only once use a SingleLiveEvent or if you plan to do an action in a limited window when conditions are met use some kind of Bus or Event Broadcast!
Remember that every time a observer
register to a LiveData
the latest value will be delivered to it.
Upvotes: 1
Reputation: 888
I found it in Google 'todo app' project. You can use SingleLiveEvent instead MutableLiveData. I hope it will be helpful for you.
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
Upvotes: 2
Reputation: 8585
Activity lifecycle is very different from application lifecycle and if you use Activity's onCreate method to register the observer you do it every time activity is created - that's why the observations are incorrect, i.e. your activity is recreated during configuration change, that causes the onCreate method to be called again and creation of new lifecycle observer that gets the events.
You need to have your DefaultLifecycleObserver independent of your activities. There are a few ways to do it.
The best one in my oppinion would be to keep AppLifecycleObserver in the application class and add a reference to the current activity to the application. Use this API to track current activity inside your Application class, save it to a field and use it in the AppLifecycleObserver callbacks. But keep in mind that according to the docs onPause method of ProcessLifecycleOwner will be called with delay and there might be no activities attached.
Another option is to use broadcasts. Keep your AppLifecycleObserver in the application, send a local broadcast in the onResume and onPause methods, and listen to it in your activity. With this approach you can also listen to it in a service and do some background procvessing.
Upvotes: 1
Reputation: 340
Please have a look at this by google https://developer.android.com/topic/libraries/architecture/. You will be able to find your answers under handling lifecycles. You may also opt to use viewmodel and livedata
Upvotes: 0