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!
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.
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.
Go to the res > values > styles. xml file and change the style parent to “Theme. AppCompat. DayNight.
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.
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) {
}
});
Quick answer:
@Override
protected void onRestart() {
super.onRestart();
recreate();
}
You add the above codes to your MainActivity and it will work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With