Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refresh the backstack Activities after Night Mode has changed

I have seen a lot of questions and answers about recreating the current activity after changing the application's Night Mode, but I have seen nothing on how to refresh the back stack Activities.

Say I have the backstack A > B > C. Activity C allows to change the night mode by calling AppCompatDelegate.setDefaultNightMode(). After this call, the current Activity (C), can refresh its theme with delegate.applyDayNight() or recreate().

However, when the user navigates back to B or A, the activities are still using the "old" mode, either day or night.

I tried to add something like that to the Activities:

override fun onResume() {
  super.onResume()
  delegate.applyDayNight()
}

But it does not seem to work.

I did multiple attempts to fix this:

One idea would be to recreate the backstack completely like suggested here or here, but since the backstack is not static, it's not doable for me.

Another idea would be to have a class that handles the night mode change and provides a LiveData. Each Activity would listen to the LiveData for a mode change and call recreate(). However, we are stuck in an infinite loop because the Activity would recreate directly after starting to listen to the LiveData.

I find it hard to believe that I am the first one trying to refresh the Activities from the backstack after changing the night mode. What did I miss?

Thanks!

like image 570
Jonas Schmid Avatar asked Feb 28 '19 13:02

Jonas Schmid


People also ask

What is a backstack in android?

A task is a collection of activities that users interact with when trying to do something in your app. These activities are arranged in a stack—the back stack—in the order in which each activity is opened. For example, an email app might have one activity to show a list of new messages.

How do you change the day and night theme on Android?

Use the system setting (Settings -> Display -> Theme) to enable Dark theme. Use the Quick Settings tile to switch themes from the notification tray (once enabled). On Pixel devices, selecting the Battery Saver mode enables Dark theme at the same time.

How do you implement Dark mode in Kotlin?

Go to the res > values > styles. xml file and change the style parent to “Theme. AppCompat. DayNight.


3 Answers

If you can detect when the day/night mode has changed, you can simply recreate an activity that is resumed when the back stack is popped.

In the following demo, there are three activities: A, B and C. A creates B and B creates C. Activity C can change the day/night mode. When C is popped, activity B sees the change in the day/night mode and calls reCreate() to recreate the activity. The same happens in activity A when activity B is popped.

The video below shows the effect. The light-colored background is the "day" mode and the dark is "night" mode.

I have created a GitHub project for this demo app. If this works as a solution, I can incorporate more text into the answer from the project.

enter image description here

like image 110
Cheticamp Avatar answered Sep 16 '22 11:09

Cheticamp


Refreshing your back stack completely is probably overkill and may add some overhead/lag to the UX; and as you mentioned, most applications will not have access to a full, static back stack.

You are essentially describing a more general issue: global changes to the theme or WindowManager itself affect the subsequent drawing of views. But previous layouts for Activities in the stack may not be redrawn. It might seem odd for you in this situation, but there could also be many good reasons why one would not want to redraw an Activity in the stack if once the user goes back to it. And so this is not an automatic feature.

I can think of a couple of options:

1) Write a custom class inheriting from Activity that invalidates all it's views when it moves to the front of the stack again. E.g. in onResume() or onRestart(), call (if in Fragment)

View view = getActivity().findViewById(R.id.viewid);
view.invalidate();

Use this custom Activity for all your activities that you want to keep consistent with the current day/night mode.

2) Use ActivityLifecycleCallbacks. This helps keep all your logic in one place, and avoids the need for custom inheritance as above. You could invalidate your views here as needed as activities are paused/resumed. You could include a Listener, if it is your app that is changing the theme, and record as SharedPreference, for example.

To use, add the callbacks to your Application class:

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

    @Override
    public void
    onActivityCreated(Activity activity, Bundle savedInstanceState) {
        //can check type of Activity for custom behaviour, if using inheritance
        if(activity instanceof MainActivity) {
           mMainActivities.put(activity, new MainActivityEntry((MainActivity)activity));
            //...
        }
    }

    @Override
    public void
    onActivityDestroyed(Activity activity) {

    } 

    @Override
    public void
    onActivityPaused(Activity activity) {
    }

    @Override
    public void
    onActivityResumed(Activity activity) {
        if(activity instanceof MainActivity) {
        //...
        }
        //can update Entry properties too
        final MainActivityEntry activityEntry = mMainActivities.get(activity);

        if(activityEntry != null) {
        //record state /perform action
        }
    }

    @Override
    public void
    onActivitySaveInstanceState(Activity activity, Bundle outState) {


    }

    @Override
    public void
    onActivityStarted(Activity activity) {


    }

    @Override
    public void
    onActivityStopped(Activity activity) {
    }
});
like image 38
dr_g Avatar answered Sep 17 '22 11:09

dr_g


Quick answer:

    @Override
    protected void onRestart() {
        super.onRestart();

        recreate();
    }

You add the above codes to your MainActivity and it will work.

like image 23
myworldbox Avatar answered Sep 17 '22 11:09

myworldbox