Fragment must be a public static class to be properly recreated from instance state
A

6

22

After updating to the latest support repository,

compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
compile 'com.android.support:percent:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'

I'm getting the weird exception.

java.lang.IllegalStateException: Fragment null must be a public static class to be  properly recreated from instance state.
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:435)
at android.support.v4.app.BackStackRecord.add(BackStackRecord.java:414)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:154)
at com.androidapp.base.BaseActivity.showDialogFragment(BaseActivity.java:78)
at com.androidapp.MainActivity.showNewDialog(MainActivity.java:304)
at com.androidapp.MainActivity$6.onClick(MainActivity.java:228)

In my BaseActivity class, I've created a re-usable fragment which can be used in activity class that extends the BaseActivty

public void showDialogFragment(DialogFragment newFragment) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
        if (prev != null) {
            ft.remove(prev);
        }
        ft.addToBackStack("dialog");
        newFragment.show(ft, "dialog");
    }

Back to the MainActivty I've used the fragment like this,

public class MainActivity extends BaseActivity {

    @SuppressLint("ValidFragment")
        public void showNewDialog(int type, String title, String message) {
            final DialogNew dialog = new DialogNew() {
                @Override
                public void success(boolean isLandscape) {
                    .......
                }

                @Override
                public void cancel() {

                }
            };
            dialog.setArgs(title, message);
            super.showDialogFragment(dialog);
        }
}

The DialogNew class is below,

public abstract class DialogNew extends DialogFragment {

    private View rootView;

    private String title;
    private String message;

    public void setArgs(String title, String message) {
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("message", message);
        setArguments(args);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NO_TITLE, 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        rootView = inflater.inflate(R.layout.fragment_new_dialog, container, false);

        init();
        setListeners();

        return rootView;
    }

    public abstract void success(boolean isLandscape);

    public abstract void cancel();
}

PS: The same code works with older support repository.

Alike answered 2/9, 2016 at 14:59 Comment(5)
Why is DialogNew abstract? You cannot instantiate an abstract class.Stilly
@Stilly that's fine. When doing such kind of thing yes you are right on the point that you cannot instantiate an abstract instead it will initialize an anonymous class that extends that Abstract class. In short there is no problem with that.Cluster
facing same error in support library version 24.2.1Jeep
add then ! , what is the solution ! , I got old code , and try to update the support libs , and it crashes with the reason , what do we have to do ?Joyjoya
You need to have a public no-argument constructor defined explicitly, Android is not a plain vanilla java!That
A
32

The error is not especially weird. If you were not getting this error before, that was weird.

Android destroys and recreates fragments as part of a configuration change (e.g., screen rotation) and as part of rebuilding a task if needed (e.g., user switches to another app, your app's process is terminated while it is in the background, then the user tries to return to your app, all within 30 minutes or so). Android has no means of recreating an anonymous subclass of DialogNew.

So, make a regular public Java class (or a public static nested class) that extends DialogNew and has your business logic, replacing the anonymous subclass of DialogNew that you are using presently.

Agatha answered 2/9, 2016 at 15:3 Comment(3)
Thanks for the reply! I agreed with your logic. Right now my DialogNew already extends DialogFragment, so I can't extend it in MainActivity cause the MainActivity itself extends the Activity. I don't know how but the same code was working properly on with older support repo. I just want to keep the dialog and activity class separated.Alike
What if your DialogFragment subclass is part of an Android library and you don't want to expose it in your public API?Spiroid
@AdamJohns: Unfortunately, given the way Java works, your objectives are mutually incompatible, if "public API" is defined as "things marked as public". In documentation, you can point out that this fragment is not intended for use by consumers of the library. You could even put a custom Lint check in your library to warn developers proactively, though this process is under-documented and in a state of flux right now. But, from a technical standpoint, given the way that they implemented fragments, you need them to be instantiatable via a public zero-argument constructor.Agatha
G
4

I recreated my fragment from scratch, it's solved the problem for me.

New -> Fragment -> Fragment (Blank) and you uncheck the 2nd box before confirming.

Godevil answered 5/5, 2020 at 15:39 Comment(0)
L
2

The reason for this error is very well explained on Android Developers guides.

When the system issues a configuration change, it needs to be able to create a new instance of your fragment. In order to do so, it relies on a default constructor of the fragment which takes no arguments and therefore cannot have any dependencies. If your Fragment class is not a static public class, the system is unable to reflectively find this default constructor and the error indicates just that.

To get around the problem, you will have to override the default implementation of the FragmentFactory of the FragmentManager instance which will handle creation of your fragment. This is explained by code in the link I provided.

Lacrimatory answered 12/3, 2021 at 1:48 Comment(0)
C
1

Edit: You probably don't want to do this... See the comments.

The code sample looks similar to what I had suggested over here, and I also recently discovered that the solution I had there was not working anymore. I've updated my answer there for Java7, but if you have Java8 the solution is super easy:

(I haven't tested this yet)

public class DialogNew extends DialogFragment {
    private View rootView;
    private String title;
    private String message;

    // Do nothing by default
    private Consumer mSuccess = (boolean b) -> {};
    private Runnable mCancel = () -> {};

    public void setArgs(String title, String message) {
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("message", message);
        setArguments(args);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NO_TITLE, 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.fragment_new_dialog, container, false);
        // use mSuccess.accept(boolean) when needed
        init();
        setListeners();
        return rootView;
    }

    public void setSuccess(Consumer success) {
        mSuccess = success;
    }

    public void setCancel(Runnable cancel) {
        mCancel = cancel;
    }
}

Then in the Main activity:

public class MainActivity extends BaseActivity {
        public void showNewDialog(int type, String title, String message) {
            final DialogNew dialog = new DialogNew();
            dialog.setArgs(title, message);
            dialog.setSuccess((boolean isLandscape) -> {
                //....
            });
            super.showDialogFragment(dialog);
        }
}
Coquillage answered 25/2, 2017 at 1:36 Comment(1)
This will not work for a few reasons. First, mSuccess and mCancel will not survive the Fragment being parcelled. If you made this Fragment retained, you have a worse problem: When you call dialog.setSuccess and pass it a lambda expression, you've actually created an inner class of MainActivity. When the activity is recreated on a config change, you'll have a memory leak, and the callbacks will attempt to call methods on a destroyed Activity.Further
A
1

Create Fragment from new >Fragment> Blank Fragment

it works for me ♥♥♥

Anglophile answered 8/8, 2020 at 10:13 Comment(0)
J
0

This error was occurred because of virtual methods is used in creating an instance of fragment.

Virtual methods must be removed from declaration and a handler class to should be used for listening to DialogNew class events.

public class MainActivity extends BaseActivity {

   @SuppressLint("ValidFragment")
   public void showNewDialog(int type, String title, String message) {

       final DialogNew dialog = new DialogNew(
            // use DialogHandler for manage success or cancel click
            new DialogHandler() {
                @Override
                public void success(boolean isLandscape) {
                    
                }

                @Override
                public void cancel() {

                }
            }
        );
       dialog.setArgs(title, message);
       super.showDialogFragment(dialog);
   }
}
Jimmyjimsonweed answered 9/7, 2022 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.