AndroidRuntimeException: requestFeature () must be called before adding content to DialogFragment

I understand that I have asked this question many times here, but after spending the last 1.5 hours of reading and trying to figure out my problem, I can’t.

Problem:

When I call the setStyle method in DialogFragment I get the RuntimeException indicated in the header.

This is my source code that does not raise this exception:

 public class MapDialogFragment extends DialogFragment @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.maps_dialog, container, false); return view; } } 

Now I just added ImmersiveMode throughout my application. As some of you may know, the dive mode is lost when the dialog boxes are displayed, so you need to redefine these fragments and set the appropriate flags to save the mode. I successfully did this - it works. But I have to comment on the line: setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); so I am losing my style of dialogue and that is the problem.

setStyle take a setStyle at the setStyle method in the setStyle dialog box, I can’t understand how requestFeature() is requestFeature() :

 public void setStyle(int style, int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame; } if (theme != 0) { mTheme = theme; } } 

Finally, this is my DialogFragment class where the exception occurs. Pay attention to getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); , as well as on clearFlag, which are necessary for ImmersiveMode to work (I hope this will be useful for someone):

 public class MapDialogFragmentv2 extends DialogFragment { @Override public void onCreate(Bundle savedInstanceState) { //I have also tried the code here, before the super.OnCreate but to no avail //setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); super.onCreate(savedInstanceState); } @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { MyDialog mDialog = new MyDialog(getActivity()); mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); view = getActivity().getLayoutInflater().inflate(R.layout.maps_dialog, null); //Line below throws exception. Needs to be commented out setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); mDialog.setContentView(view); return mDialog; } public class MyDialog extends Dialog { public MyDialog(Context context) { super(context); getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); getWindow().getDecorView().setSystemUiVisibility(MainActivity.getImmersiveModeFlags()); } @Override public void show() { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); super.show(); } } } 

I also tried using a combination of onCreate , onCreateView and onCreateDialog , but this did not work. I also read here in Stackoverflow commenting that it was nice to have onCreateView and onCreateDialog at the same time.

And also tried adding style to my XML layout itself, but it also didn't work:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/lib/com.google.android.gms.plus" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/transparent" style="@android:style/Theme.Holo.Dialog" > 

thanks

+6
source share
5 answers

The problem is that you mix too many different things and that you do not respect the life cycle of each class.

First, you need to stop nesting classes like you. This is partly a source of error. If you really need / need to nest Fragments or Dialog , then it is important that you insert nested Fragments , Dialogs as static. If you do not declare them static, you can cause a memory leak and problems like yours. But the best thing you can do is to limit yourself to just one class per file, unless you have a real reason to attach it. It also added the benefits of improving the readability and maintainability of your code.

But the main reason for your error is that you completely ignore the Dialog life cycle. To a large extent, all of the following code should be in the correct Dialog lifecycle methods:

 mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); view = getActivity().getLayoutInflater().inflate(R.layout.maps_dialog, null); //Line below throws exception. Needs to be commented out setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); mDialog.setContentView(view); 

So, to fix your mistake, you need to do 3 things:

  • Declare MapDialogFragmentv2 and MyDialog static as follows:

     public class MainActivity extends Activity { ... public static class MapDialogFragmentv2 extends Fragment { ... public static class MyDialog extends Dialog { ... } } } 

Or it’s even better to move them to your own files together.

  1. Move the code from onCreateDialog() to MapDialogFragmentv2 to the correct lifecycle methods in MyDialog . Then it should look something like this:

     public static class MyDialog extends Dialog { private final LayoutInflater mInflater; public MyDialog(Context context) { super(context); mInflater = LayoutInflater.from(context); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); getWindow().getDecorView().setSystemUiVisibility(MainActivity.getImmersiveModeFlags()); View view = mInflater.inflate(R.layout.maps_dialog, null); setContentView(view); } @Override public void show() { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); super.show(); } } 
  2. Add setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); to the onCreate() method of your MapDialogFragmentv2 after calling super.onCreate() .

     @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); } 

Your MapDialogFragmentv2 should now look something like this:

 public static class MapDialogFragmentv2 extends DialogFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo); } @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { return new MyDialog(getActivity()); } } 

I tested everything on my Nexus 5 running Android 5.0.1 (Lollipop) and it works.

+4
source

I solve this by removing this code.

  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_choose_image, container, false); } } 
+1
source

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(); // do some stuff to decorView return dialog; } 

TLDR

I should have changed this to:

 public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); View decorView = dialog.getWindow().getDecorView(); // do some stuff to decorView } 

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); // ^ this is our culprit // more window stuff that isn't relevant } 

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"); } // more requestFeature stuff that isn't relevant } 

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); // fall through... case STYLE_NO_FRAME: case STYLE_NO_TITLE: dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); } } 

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); } // more irrelevant onActivityCreated stuff } 

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(); // do some stuff to decorView } 
0
source

I solved the problem of the lack of full-screen dialog in MapDialogFragmentv2:

 @Override public void onStart() { super.onStart(); Dialog dialog = getDialog(); if (dialog != null) { int width = ViewGroup.LayoutParams.MATCH_PARENT; int height = ViewGroup.LayoutParams.MATCH_PARENT; dialog.getWindow().setLayout(width, height); } } 

Now all that remains is that the setStyle() method does not work. No style specified. But this does not throw the original exception mentioned in this post.

-1
source

Good,

it almost works now, so I will mark Xaver's answer as correct, as it will lead me to the right path.

I had to change the MyDialog class to:

public MyDialog (context context, int theme) {
super (context, theme);
mInflater = LayoutInflater.from (context);

}

Thus, I manage to save the topic. But for some reason, my buttons do not match the theme. They all returned to default. I am trying to programmatically set the style, but without success at the moment

buttonGuess.setBackgroundColor (android.R.attr.borderlessButtonStyle); buttonOk.setBackgroundColor (android.R.style.Widget_Holo_Button);

However, now it works the way I want.

Thank you all

-1
source

Source: https://habr.com/ru/post/1209974/


All Articles