How do I maintain the Immersive Mode in Dialogs?
Solution 1
After a lot of research into the issue there is a hacky fix for this, which involved tearing apart the Dialog class to find. The navigation bar is shown when the dialog window is added to the Window Manager even if you set the UI visibility before adding it to the manager. In the Android Immersive example it's commented that:
// * Uses semi-transparent bars for the nav and status bars
// * This UI flag will *not* be cleared when the user interacts with the UI.
// When the user swipes, the bars will temporarily appear for a few seconds and then
// disappear again.
I believe that's what we're seeing here (that a user-interaction is being triggered when a new, focusable, window view is added to the manager).
How can we work around this? Make the Dialog non-focusable when we create it (so we don't trigger a user-interaction) and then make it focusable after it's displayed.
//Here's the magic..
//Set the dialog to not focusable (makes navigation ignore us adding the window)
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Show the dialog!
dialog.show();
//Set the dialog to immersive
dialog.getWindow().getDecorView().setSystemUiVisibility(
context.getWindow().getDecorView().getSystemUiVisibility());
//Clear the not focusable flag from the window
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
Clearly this is not ideal but it seems to be an Android bug, they should check if the Window has immersive set.
I've updated my working test code (forgive the hacky messiness) to Github. I've tested on the Nexus 5 emulator, it will probably blow up with anything less than KitKat but its for proof-of-concept only.
Solution 2
For your information, thanks to @Beaver6813's answer, I've been able to get this working using DialogFragment.
in the onCreateView method of my DialogFragment, I've just added the following :
getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
getDialog().getWindow().getDecorView().setSystemUiVisibility(getActivity().getWindow().getDecorView().getSystemUiVisibility());
getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
//Clear the not focusable flag from the window
getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Update the WindowManager with the new attributes (no nicer way I know of to do this)..
WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
wm.updateViewLayout(getDialog().getWindow().getDecorView(), getDialog().getWindow().getAttributes());
}
});
Solution 3
If you want to use onCreateDialog(), try this class. It works pretty well for me...
public class ImmersiveDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
.setTitle("Example Dialog")
.setMessage("Some text.")
.create();
// Temporarily set the dialogs window to not focusable to prevent the short
// popup of the navigation bar.
alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
return alertDialog;
}
public void showImmersive(Activity activity) {
// Show the dialog.
show(activity.getFragmentManager(), null);
// It is necessary to call executePendingTransactions() on the FragmentManager
// before hiding the navigation bar, because otherwise getWindow() would raise a
// NullPointerException since the window was not yet created.
getFragmentManager().executePendingTransactions();
// Hide the navigation bar. It is important to do this after show() was called.
// If we would do this in onCreateDialog(), we would get a requestFeature()
// error.
getDialog().getWindow().getDecorView().setSystemUiVisibility(
getActivity().getWindow().getDecorView().getSystemUiVisibility()
);
// Make the dialogs window focusable again.
getDialog().getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
);
}
}
To show the dialog, do the following in your activity...
new ImmersiveDialogFragment().showImmersive(this);
Solution 4
Combining the answers here I made an abstract class that works in all cases:
public abstract class ImmersiveDialogFragment extends DialogFragment {
@Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
// Make the dialog non-focusable before showing it
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
@Override
public void show(FragmentManager manager, String tag) {
super.show(manager, tag);
showImmersive(manager);
}
@Override
public int show(FragmentTransaction transaction, String tag) {
int result = super.show(transaction, tag);
showImmersive(getFragmentManager());
return result;
}
private void showImmersive(FragmentManager manager) {
// It is necessary to call executePendingTransactions() on the FragmentManager
// before hiding the navigation bar, because otherwise getWindow() would raise a
// NullPointerException since the window was not yet created.
manager.executePendingTransactions();
// Copy flags from the activity, assuming it's fullscreen.
// It is important to do this after show() was called. If we would do this in onCreateDialog(),
// we would get a requestFeature() error.
getDialog().getWindow().getDecorView().setSystemUiVisibility(
getActivity().getWindow().getDecorView().getSystemUiVisibility()
);
// Make the dialogs window focusable again
getDialog().getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
);
}
}
Solution 5
This also works over ride the onDismiss method of your dialog fragment . And within that method call the method of the activity to which it is attached to again set the full screen flags .
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Logger.e(TAG, "onDismiss");
Log.e("CallBack", "CallBack");
if (getActivity() != null &&
getActivity() instanceof LiveStreamingActivity) {
((YourActivity) getActivity()).hideSystemUI();
}
}
And in your activity add this method :
public void hideSystemUI() {
// Set the IMMERSIVE flag.
// Set the content to appear under the system bars so that the content
// doesn't resize when the system bars hide and show.
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
Related videos on Youtube
VanDir
Updated on July 18, 2022Comments
-
VanDir almost 2 years
How do I maintain the new Immersive Mode when my activities display a custom Dialog?
I am using the code below to maintain the Immersive Mode in Dialogs, but with that solution, the NavBar appears for less than a second when I start my custom Dialog, then it disappears.
The following video explains the issue better (look at the bottom of the screen when the NavBar appears): http://youtu.be/epnd5ghey8g
How do I avoid this behavior?
CODE
The father of all activities in my application:
public abstract class ImmersiveActivity extends Activity { @SuppressLint("NewApi") private void disableImmersiveMode() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_FULLSCREEN ); } } @SuppressLint("NewApi") private void enableImmersiveMode() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ); } } /** * Set the Immersive mode or not according to its state in the settings: * enabled or not. */ protected void updateSystemUiVisibility() { // Retrieve if the Immersive mode is enabled or not. boolean enabled = getSharedPreferences(Util.PREF_NAME, 0).getBoolean( "immersive_mode_enabled", true); if (enabled) enableImmersiveMode(); else disableImmersiveMode(); } @Override public void onResume() { super.onResume(); updateSystemUiVisibility(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); updateSystemUiVisibility(); } }
All my custom Dialogs call this method in their
onCreate(. . .)
method:/** * Copy the visibility of the Activity that has started the dialog {@link mActivity}. If the * activity is in Immersive mode the dialog will be in Immersive mode too and vice versa. */ @SuppressLint("NewApi") private void copySystemUiVisibility() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { getWindow().getDecorView().setSystemUiVisibility( mActivity.getWindow().getDecorView().getSystemUiVisibility() ); } }
EDIT - THE SOLUTION (thanks to Beaver6813, look his answer for more details) :
All my custom dialogs override the show method in this way:
/** * An hack used to show the dialogs in Immersive Mode (that is with the NavBar hidden). To * obtain this, the method makes the dialog not focusable before showing it, change the UI * visibility of the window like the owner activity of the dialog and then (after showing it) * makes the dialog focusable again. */ @Override public void show() { // Set the dialog to not focusable. getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); copySystemUiVisibility(); // Show the dialog with NavBar hidden. super.show(); // Set the dialog to focusable again. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); }
-
Tadeas Kriz about 10 yearsHow do you show dialogs? Do you use DialogFragments?
-
VanDir about 10 yearsI don't use DialogFragments but custom Dialog subclasses. developer.android.com/reference/android/app/Dialog.html I show the dialogs simply by calling their show() method.
-
Kevin van Mierlo about 10 yearsWhen the dialog appears onWindowFocusChanged is being called. What is the value of enabled when the dialog appears? Is it true or did something went wrong and is it false?
-
VanDir about 10 yearsDo you mean the onWindowFocusChanged(boolean hasFocus) method of the Dialog class (and not of the Activity class)? In this case the "hasFocus" flag is true.
-
Ruzanna about 10 yearsHi @VanDir, did you found any solutions. I'm stuck into this problem as well. Please, let me know.
-
VanDir about 10 yearsMe too. I can't release an update to my game for this problem.
-
Ando Masahashi about 10 years@VanDir can you explain more via images?
-
VanDir about 10 yearsI edited the answer with a video of the issue.
-
gbero almost 10 yearsDid anybody used immersive mode with dialogfragments ?
-
tomash over 6 yearsLeast intrusive answer is stackoverflow.com/questions/22794049/… as it doesn't prevent showing softbutton bar but removes it along with Dialog
-
-
VanDir about 10 yearsThanks for the sample project but It didn't work. Look what happens in my Nexus 5: youtu.be/-abj3V_0zcU
-
Beaver6813 about 10 yearsI've updated my answer after some investigation into the issue, it was a tough one! Tested on a Nexus 5 emulator, hopefully this one works.
-
VanDir about 10 yearsYou're a genius! It worked. Thank you! Only one thing: I noticed that the solution works also without the last 2 lines. Why did you add them?
-
Beaver6813 about 10 yearsBeing overly cautious! I wasn't sure if clearing the flag on the window would update it after being added to the manager, evidently it does so I'll update the answer.
-
Toni Alvarez almost 10 yearsThanks! Works like a charm!!
-
arberg almost 10 yearsI get 'requestFeature() must be called before adding content' (I think it depends on active features on activity). Solution: Move the dialog.show() one line up so show() is invoked before copying SystemUiVisibility (but after setting non-focusable). Then it still works without jumping nav-bar.
-
IAmKale almost 10 yearsDoes this work with the Builder pattern in onCreateDialog()? I've yet to get this hack to work with my DialogFragments, the dialog crashes my app with "requestFeature() must be called before adding content"
-
androidcodehunter over 9 years@Beaver6813 it doesn't works when EditText is used and softkeyboard is appear in DialogFragment. Do you have nay suggestions to handle it.
-
William over 9 yearsAny idea on how to stop the NavBar being displayed when an ActionBar menu is shown?
-
William over 9 yearsAny idea on how to stop the NavBar being displayed when an ActionBar menu is shown?
-
Menna-Allah Sami about 9 years@androidcodehunter do you find a solution for your problem ?
-
Menna-Allah Sami about 9 yearsCan I find a similar solution but for softkeyboard ?
-
slott almost 9 yearsNot really ideal as you wont be able to press anything inside the dialog :(
-
slott almost 9 yearsUsing this my dialogs become unresponsive. But if you insists that it works I'll give it another go.
-
4emodan about 8 years@slott you need to clear the
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
after dialog shown -
Anton Shkurenko about 8 yearsI still get NPE, because getDialog() returns null. How did you manage that?
-
Denys Vitali almost 8 yearsWorks in Android N Dev Preview 2 on a Google Pixel C, thank you!
-
Navin Gupta over 7 yearsHi Beaver6813 . It does not work when I have an edit text in dialog. Is there any solution??
-
tomash over 6 yearsMost useful comment, as it works great with
AlertDialog.Builder
and.setOnDismissListener()
-
se22as about 6 yearsThis is brilliant and was the only solution to my usage of DialogFragments. Thank you so much.
-
Alessandro Caliaro about 6 yearsTherre is a solution when spinner popup?
-
Droidman over 5 yearsCan confirm that this still works. I was fighting the same issue recently, in case anyone's interested I did generalize this solution for an AlertDialog (Kotlin): gist.github.com/vad-zuev/c8e0f176af24837ba9c3fd420d915d14
-
dragonfly over 5 years@Beaver6813 You solved the Dialog issue, but how to solve solve PopupWindow issue?
-
Peterdk about 5 yearsThis solution works great for dialogs with not
EditText
in them. However, with aEditText
there is no way to restore focus and all keyboard input is directed to the view below the dialog. Did anybody got to fix that? -
Peterdk almost 4 yearsGreat! It works perfectly. Tried so many things, now perfect continues immersive mode.
-
Minki almost 4 yearssince androidx, setupDialog method became a restricted api, how could we deal with that except ignoring it?