I got this exception for API 21 and API 23.
Fatal Exception: android.util.AndroidRuntimeException: requestFeature() must be called before adding content at com.android.internal.policy.impl.PhoneWindow.requestFeature + 301(PhoneWindow.java:301) at android.app.Dialog.requestWindowFeature + 1060(Dialog.java:1060) at androidx.fragment.app.DialogFragment.setupDialog + 403(DialogFragment.java:403) at androidx.fragment.app.DialogFragment.onGetLayoutInflater + 383(DialogFragment.java:383) at androidx.fragment.app.Fragment.performGetLayoutInflater + 1403(Fragment.java:1403) at androidx.fragment.app.FragmentManagerImpl.moveToState + 880(FragmentManagerImpl.java:880) at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState + 1237(FragmentManagerImpl.java:1237) at androidx.fragment.app.FragmentManagerImpl.moveToState + 1302(FragmentManagerImpl.java:1302) at androidx.fragment.app.BackStackRecord.executeOps + 439(BackStackRecord.java:439) at androidx.fragment.app.FragmentManagerImpl.executeOps + 2075(FragmentManagerImpl.java:2075) at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether + 1865(FragmentManagerImpl.java:1865) at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute + 1820(FragmentManagerImpl.java:1820) at androidx.fragment.app.FragmentManagerImpl.execPendingActions + 1726(FragmentManagerImpl.java:1726) at androidx.fragment.app.FragmentManagerImpl$2.run + 150(FragmentManagerImpl.java:150) at android.os.Handler.handleCallback + 739(Handler.java:739) at android.os.Handler.dispatchMessage + 95(Handler.java:95) at android.os.Looper.loop + 135(Looper.java:135) at android.app.ActivityThread.main + 5221(ActivityThread.java:5221) at java.lang.reflect.Method.invoke(Method.java) at java.lang.reflect.Method.invoke + 372(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run + 899(ZygoteInit.java:899) at com.android.internal.os.ZygoteInit.main + 694(ZygoteInit.java:694)
This was the offender’s code:
public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = super.onCreateDialog(savedInstanceState); View decorView = dialog.getWindow().getDecorView();
TLDR
I should have changed this to:
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); View decorView = dialog.getWindow().getDecorView();
A little deeper analysis without links
Technically, you can make my call to Window#getDecorView (which launches PhoneWindow#installDecor ) anytime after DialogFragment#setupDialog , and the earliest place that appears to be within onCreateView (before or after super.onCreateView doesn't matter) However, I prefer DialogFragment call PhoneWindow#installDecor with DialogFragment#onActivityCreated , which seems like a normal path. Then I can call Window#getDecorView as part of my own onActivityCreated AFTER calling super.onActivityCreated .
Ultra-deep analysis using links to Android sources
The problem was that Window#getDecorView creates window decorations, which makes the window element request to throw an AndroidRuntimeException . See source com.android.impl.policy.PhoneWindow#getDecorView :
public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; }
Note that com.android.impl.policy.PhoneWindow#installDecor installs mContentParent :
private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); } if (mContentParent == null) { mContentParent = generateLayout(mDecor);
And that requestFeature throws an AndroidRuntimeException if mContentParent set :
public boolean requestFeature(int featureId) { if (mContentParent != null) { throw new AndroidRuntimeException("requestFeature() must be called before adding content"); }
And, of course, DialogFragment#setupDialog calls Window#requestFeature , where the trace of our stack comes from:
public void setupDialog(@NonNull Dialog dialog, int style) { switch (style) { case STYLE_NO_INPUT: dialog.getWindow().addFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
And one level higher in the stack trace, we see that DialogFragment#onGetLayoutInflator calls setupDialog , but before that it calls onCreateDialog :
public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) { if (!mShowsDialog) { return super.onGetLayoutInflater(savedInstanceState); } mDialog = onCreateDialog(savedInstanceState); if (mDialog != null) { setupDialog(mDialog, mStyle); return (LayoutInflater) mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); }
And FragmentManagerImpl calls Fragment#performGetLayoutInflater (which calls Fragment#onGetLayoutInflator ) just before it calls Fragment#performCreateView (which calls Fragment#onCreateView ), then Fragment#onViewCreated , then Fragment#performActivityCreated (which calls Fragment#onActivityCreated (which) :
Fragment f; // fragment if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { if (f.mContainerId == View.NO_ID) { throwException(new IllegalArgumentException( "Cannot create fragment " + f + " for a container view with no id")); } container = (ViewGroup) mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { String resName; try { resName = f.getResources().getResourceName(f.mContainerId); } catch (Resources.NotFoundException e) { resName = "unknown"; } throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + resName + ") for fragment " + f)); } } f.mContainer = container; f.performCreateView(f.performGetLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); setViewTag(f); if (container != null) { container.addView(f.mView); } if (f.mHidden) { f.mView.setVisibility(View.GONE); } ViewCompat.requestApplyInsets(f.mView); f.onViewCreated(f.mView, f.mSavedFragmentState); mLifecycleCallbacksDispatcher.dispatchOnFragmentViewCreated( f, f.mView, f.mSavedFragmentState, false); // Only animate the view if it is visible. This is done after // dispatchOnFragmentViewCreated in case visibility is changed f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE) && f.mContainer != null; } } f.performActivityCreated(f.mSavedFragmentState);
And DialogFragment calls Dialog#setContentView in onActivityCreated :
public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (!mShowsDialog) { return; } View view = getView(); if (view != null) { if (view.getParent() != null) { throw new IllegalStateException( "DialogFragment can not be attached to a container view"); } mDialog.setContentView(view); }
So we don’t have to do anything to launch PhoneWindow#installDecor before DialogFragment#onActivityCreated so that DialogFragment and PhoneWindow can play together as they expect.
Then, according to the onCreateDialog JavaDoc :
This method will be called after onCreate(android.os.Bundle) and before Fragment.onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)
And Window#requestFeature JavaDoc confirms what our exception says
This must be called before setContentView ()
So my Window#getDecorView failed when I called it anytime in onCreateDialog because Window#getDecorView starts PhoneWindow#installDecor , which sets PhoneWindow#mContentParent , which fires AndroidRuntimeException when DialogFragment#onGetLayoutInflator setupDialog immediately calls setupDialog and onCreateDialog setupDialog call Window#requestFeature .
Technically, you can make my call to Window#getDecorView (which launches PhoneWindow#installDecor ) anytime after DialogFragment#setupDialog , and the earliest place that appears to be within onCreateView (before or after super.onCreateView doesn't matter) However, I prefer DialogFragment call PhoneWindow#installDecor with DialogFragment#onActivityCreated , which seems like a normal path. After that, I can call Window#getDecorView in my own onActivityCreated AFTER calling super.onActivityCreated .
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); View decorView = dialog.getWindow().getDecorView();