Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make an Activitly correctly observe Lifecycle event

Tags:

android

Currently, I need to perform some actions, when

  • Application is launched.
  • Application is quit.
  • But not during activity recreation, configuration change, ...

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, ...

like image 513
Cheok Yan Cheng Avatar asked Jul 07 '18 06:07

Cheok Yan Cheng


People also ask

What is the correct sequence of execution for the activity lifecycle methods?

Activity-lifecycle concepts To navigate transitions between stages of the activity lifecycle, the Activity class provides a core set of six callbacks: onCreate() , onStart() , onResume() , onPause() , onStop() , and onDestroy() . The system invokes each of these callbacks as an activity enters a new state.

How Mvvm is lifecycle-aware?

Lifecycle Awareness: ViewModel objects are also lifecycle-aware. They are automatically cleared when the Lifecycle they are observing gets permanently destroyed. Data Sharing: Data can be easily shared between fragments in an activity using ViewModels .


Video Answer


1 Answers

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.

like image 105
Keivan Esbati Avatar answered Oct 19 '22 15:10

Keivan Esbati