Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42760

How to make an Activitly correctly observe Lifecycle event

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

WeNoteApplication.java

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

When the app is launched

onCreate LifecycleOwner called
onResume LifecycleOwner called
onResume LifecycleOwner called    <-- Why onResume of LifecycleOwner is called twice??

When I rotate the device

onCreate LifecycleOwner called
onResume LifecycleOwner called    <-- Why onCreate and onResume of LifecyclOwner is called during configuration change?

Try again with LiveData

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

Answers (4)

Keivan Esbati
Keivan Esbati

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

Alex
Alex

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

TpoM6oH
TpoM6oH

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

Melvin Kent
Melvin Kent

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

Related Questions