Do fragments really need an empty constructor?
Asked Answered
X

5

284

I have a Fragment with a constructor that takes multiple arguments. My app worked fine during development, but in production my users sometimes see this crash:

android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment 
make sure class name exists, is public, and has an empty constructor that is public

I could make an empty constructor as this error message suggests, but that doesn't make sense to me since then I would have to call a separate method to finish setting up the Fragment.

I'm curious as to why this crash only happens occasionally. Maybe I'm using the ViewPager incorrectly? I instantiate all the Fragments myself and save them in a list inside the Activity. I don't use FragmentManager transactions, since the ViewPager examples I have seen did not require it and everything seemed to be working during development.

Xeniaxeno answered 4/5, 2012 at 13:59 Comment(6)
in some versions of android (at least ICS), you can go to settings -> developer options and enable "Don't keep activities". Doing this will give you a deterministic way of testing the cases where a no-arg constructor is necessary.Augean
I had this same issue. I was assigning the bundle data instead to member variables(using a non-default ctor). My program was not crashing when I killed the app-it was only happening when the scheduler put my app on the backburner to "save space". The way I discovered this is by going to Task Mgr and opening up a ton of other apps, then re-opening my app in debug. It crashed every time. The issue was resolved when I used Chris Jenkins answer to use bundle args.Glyoxaline
You might be interested in this thread: #15519714Babbling
A side note for future readers: if your Fragment subclass doesn't declare any constructors at all, then by default an empty public constructor will implicitly be made for you (this is standard Java behavior). You do not have to explicitly declare an empty constructor unless you also declared other constructors (e.g. ones with arguments).Stertor
I'll just mention that IntelliJ IDEA, at least for version 14.1, provides a warning alerting you to the fact that you shouldn't have a non-default constructor in a fragment.Simplicity
This is happening because of low memory condition / process death. See #49047273Redeemable
H
374

Yes they do.

You shouldn't really be overriding the constructor anyway. You should have a newInstance() static method defined and pass any parameters via arguments (bundle)

For example:

public static final MyFragment newInstance(int title, String message) {
    MyFragment f = new MyFragment();
    Bundle bdl = new Bundle(2);
    bdl.putInt(EXTRA_TITLE, title);
    bdl.putString(EXTRA_MESSAGE, message);
    f.setArguments(bdl);
    return f;
}

And of course grabbing the args this way:

@Override
public void onCreate(Bundle savedInstanceState) {
    title = getArguments().getInt(EXTRA_TITLE);
    message = getArguments().getString(EXTRA_MESSAGE);

    //...
    //etc
    //...
}

Then you would instantiate from your fragment manager like so:

@Override
public void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState == null){
        getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.content, MyFragment.newInstance(
                R.string.alert_title,
                "Oh no, an error occurred!")
            )
            .commit();
    }
}

This way if detached and re-attached the object state can be stored through the arguments. Much like bundles attached to Intents.

Reason - Extra reading

I thought I would explain why for people wondering why.

If you check: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Fragment.java

You will see the instantiate(..) method in the Fragment class calls the newInstance method:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    try {
        Class<?> clazz = sClassMap.get(fname);
        if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            if (!Fragment.class.isAssignableFrom(clazz)) {
                throw new InstantiationException("Trying to instantiate a class " + fname
                        + " that is not a Fragment", new ClassCastException());
            }
            sClassMap.put(fname, clazz);
        }
        Fragment f = (Fragment) clazz.getConstructor().newInstance();
        if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.setArguments(args);
        }
        return f;
    } catch (ClassNotFoundException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (java.lang.InstantiationException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (NoSuchMethodException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new InstantiationException("Unable to instantiate fragment " + fname
                + ": calling Fragment constructor caused an exception", e);
    }
}

http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance() Explains why, upon instantiation it checks that the accessor is public and that that class loader allows access to it.

It's a pretty nasty method all in all, but it allows the FragmentManger to kill and recreate Fragments with states. (The Android subsystem does similar things with Activities).

Example Class

I get asked a lot about calling newInstance. Do not confuse this with the class method. This whole class example should show the usage.

/**
 * Created by chris on 21/11/2013
 */
public class StationInfoAccessibilityFragment extends BaseFragment implements JourneyProviderListener {

    public static final StationInfoAccessibilityFragment newInstance(String crsCode) {
        StationInfoAccessibilityFragment fragment = new StationInfoAccessibilityFragment();

        final Bundle args = new Bundle(1);
        args.putString(EXTRA_CRS_CODE, crsCode);
        fragment.setArguments(args);

        return fragment;
    }

    // Views
    LinearLayout mLinearLayout;

    /**
     * Layout Inflater
     */
    private LayoutInflater mInflater;
    /**
     * Station Crs Code
     */
    private String mCrsCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCrsCode = getArguments().getString(EXTRA_CRS_CODE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mInflater = inflater;
        return inflater.inflate(R.layout.fragment_station_accessibility, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mLinearLayout = (LinearLayout)view.findViewBy(R.id.station_info_accessibility_linear);
        //Do stuff
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().getSupportActionBar().setTitle(R.string.station_info_access_mobility_title);
    }

    // Other methods etc...
}
Haystack answered 4/5, 2012 at 14:10 Comment(34)
ty, had some serious design flaws in my fragment :) still would like to know when and why the framework is trieng to instantiate the fragments by itself, since im not using them in any layout file and they are hold in a list inside the activity so they should be alive until the activity is destroyed.Xeniaxeno
If you pause the activity or destroy it. So you go to the home screen and the activity is then killed by Android to save room. The fragments state will be saved (using the args) then gc the object (normally). So on return to the activity the fragments should try to be recreated using the saved state, new Default() then onCreate etc... Also if the activity is trying to save resources (low mem phone) It may remove the objects just went paused.. Commonsguy should be able to explain better. In Short You don't know! :)Haystack
The only issue I have with this design, though, @Haystack is when you actually need a large object or list of object in your fragment that you usually supply in your constructor. I'm intending this as a point, but if you could maybe share your thoughts, I'd be appreciative.Abamp
@Abamp Really if you need ALOT of Objects/Models you should grab them asynchronously from a Database or ContentProvider.Haystack
@Abamp on that point, the Arguments are designed to 'define' the fragment, not to populate it, if you DO need to pass objects to it, make sure they are ParcelableHaystack
@Haystack What good are constructors, then, if that much can be done with the Parcelable, too? And the whole point is to avoid dumping stuff and then reading into a copy (the issue of memory management keeps coming up and yet everyone disregards this). Or share a nonparcelable, such as a caching (SoftRefs+flash) downloader - you can't think of that as a misguided idea. Sigh... and I was so happy to see constructors after trouble with Activity-based screens operating on the same data...Pontificate
@Pontificate I'm not quite sure what your getting at? Constructors are still needed as part of java, but they don't fit into the Android Lifecycle. You need a common way of telling the Android Framework how to save and restart this Activity/Fragment constructors can't do that. Where you can do YourFrag f = Class<YourFrag>.newInstance(); f.setArguments(savedBundle); at any point in time regardless of the constructor. Open a new question if you need something explaining more. :)Haystack
@Haystack Sorry if I wasn't clear... my point was that, unlike Activities, Fragments do not make it as clear that constructors must not be used for passing/sharing data. And while dumping/restoring is fine, I believe that holding several copies of data can sometimes take up more memory than view destruction can regain. In some cases, it might be useful to have the option to treat a collection of Activities/Fragments as a unit, to be destroyed as a whole or not at all - then we could pass data via constructors. For now, regarding this issue, an empty constructor is the only one to have.Pontificate
Why would you be holding several copies of data? Bundles|Parcelable actually pass memory reference when it can between states/fragments/activities, (it causes some weird state issues actually), The only time Parcelable actually effectively "duplicates" data is between processes and full lifecycle. E.g. if you pass an object to your fragments from your activity, your passing reference not a clone. Your only real extra overhead is the additional fragment objects.Haystack
@Haystack Well, that, then, was my ignorance of Parcelable. Having read the short javadoc of Parcelable, and a part of Parcel not far past the word "reconstructed", I had not reached the "Active Objects" part, concluding that it was just a low-level more optimized but less versatile Serializable. I hereby don the hat of shame and mumble "Still can't share nonparcelables and making parcelables can be a bother" :)Pontificate
@Pontificate hehe ;) Yes I agree making Parcelables is a bit of a chore. Which is what i dislike about them. Hopefully someone will find these comments useful at least :)Haystack
This is confusing: the instantiate method in the Fragment class calls the newInstance method Static methods aren't polymorphic. So the "newInstance" method of Class is actually what's being called. So, clearly, a no-argument constructor is needed when fragments are created via the framework. However, whatever code calls "newInstance(title, message)" lives outside of that process and does its own construction. So, it could just as well call a multi-arg constructor. It would then have to use onSaveInstanceState to make title/message available to fragments that were created via the framework.Appel
@gmale newInstance() is part of the class method, the newInstance(args..) is just a nice way of creating fragments in your code. You could call it createFragmentInstance(args ...). But thats what you call not the framework. The Class.newInstance() requires a public no-arg constructor. Its all just best practice you can implement how you like.Haystack
@gmale ~"lives outside of that process". What do you mean? An Android app is single-threaded by default, and the only way you can use a different process is by creating a Service.Bondsman
@Bondsman he means someone calling newInstance(arg,arg) is outside of the Application Lifecycle calling it. (Not process as in the App process).Haystack
@Chris.Jenkins, tks for your answer. I just have one question, why do we need to put an 'int' to the title. bdl.putInt(EXTRA_TITLE, title); TksUnvoiced
@TungMaiLe this is just an example, i pass through a R.string.some_title qualifier for title. You can pass through what you want, It's just a simple builder pattern to make sure you pass what ever arguments the Fragment needs to it.Haystack
I wouldn't recommend using newInstance() instead of a default constructor. It relies on Android internals that are subject to change. newInstance() uses the default constructor, so what is the advantage of going around it?Caaba
@BarryFruitman You still are using the default constructor, this is just a builder method, as stated before. it could be called buildFragment(arg1,arg2..), don't confuse this with the class method. I might change the answer as I get asked this alot.Haystack
I have a question regarding this matter: Suppose I already have a class "Fragment1" that extends from Fragment, and in some activity, I need to use this class but with a function being a bit different thatn the one inside "Fragment1", is it OK to use an anonymous class that uses "Fragment1" and sets the method? Meaning something like : "new Fragment1(){ public void foo(){...}}" ?Terryn
@androiddeveloper No, as that would create an anonymous subclass, which is not public and static. See https://mcmap.net/q/110038/-fragment-instantiationexception-no-empty-constructor-gt-google-maps-v2Haystack
@Haystack I had the feeling that's the case, even though it does work. I think that in case the activity/fragment get restored, it will cause this issue to appear. Thank you.Terryn
@androiddeveloper you will get an exception if it tries to resume from state. (But yes it will work, It should fail fast really).Haystack
@Haystack Yes. I assume that's the same exception described on the link you've mentioned: "InstantiationException: Unable to instantiate fragment" . I think that the real declaration of such a class look like "behind the scenes" an inner, private class, which is why it can't be instantiated.Terryn
@Haystack I didn`t catch newInstance() method where is being called? Please give some clear solution!Sanctify
Could you not just call frag.setArguments() in an overloaded constructor and also have a default constructor as well? I don't see why we need to create a whole new method.Mcalpin
@kennyworden the fragment manager can only create a new instance using a default public constructor. So that would not work unfortunately.Haystack
@Haystack Right, but what I said is that, why can't we have: public MyFragment() { super(); } and public MyFragment(String arg) { // Set args here // } ?Mcalpin
Sure, but than that public api is confusing to any other developer that picks up your project. You can. But it's better practice to create static methods as you are separating your responsibly.Haystack
Thanks for this. I'll just mention that when I copied-and-pasted your code into my program I got the following comment from IntelliJ IDEA: "When a static method is overridden in a subclass it can still be accessed via the super class, making a final declaration not very necessary. Declaring a static method final does prevent subclasses from defining a static method with the same signature." I.e., it's saying that "final" is unnecessary - so I removed it just to avoid being nagged.Simplicity
A 200+ upvote answer should have better variable naming!Stantonstanway
I always thought the compiler would add the empty constructor.Havener
@Havener In java, if you define no constructor, an empty constructor matching the class visibility is generated. As soon as you define any constructor the default empty one is no longer generated and you have to define one. (This is a Java spec not Android incase you were wondering).Haystack
@Haystack When a static method is overridden in a subclass it can still be accessed via the super class, making a final declaration not very necessary. Declaring a static method final does prevent subclasses from defining a static method with the same signature. As flagged by Annotations processor in Android Studio.Mastrianni
W
19

As noted by CommonsWare in this question https://mcmap.net/q/110038/-fragment-instantiationexception-no-empty-constructor-gt-google-maps-v2, this error can also occur if you are creating an anonymous subclass of a Fragment, since anonymous classes cannot have constructors.

Don't make anonymous subclasses of Fragment :-)

Wrecker answered 24/6, 2013 at 13:38 Comment(1)
Or, as CommonsWare mentioned in that post, make sure you declare an inner Activity/Fragment/Reciever as "static" to avoid this error.Immunochemistry
B
8

Yes, as you can see the support-package instantiates the fragments too (when they get destroyed and re-opened). Your Fragment subclasses need a public empty constructor as this is what's being called by the framework.

Bullate answered 4/5, 2012 at 14:4 Comment(3)
Empty Fragment Constructor should call super() Constructor or not? I am asking this as I fount that empty public Constructor is mandatory. if calling super() doesn't make sense for empty public constructorDeme
@Deme as all Fragment abstractions have an empty constructor super() would be fruitless, as the parent class has broken the empty public constructor rule. So no you do not need to pass super() inside your constructor.Haystack
In fact it isn't a requirement to explicitly define an empty constructor in a Fragment. Every Java class has an implicit default constructor anyway. Taken from: docs.oracle.com/javase/tutorial/java/javaOO/constructors.html ~ "The compiler automatically provides a no-argument, default constructor for any class without constructors."Bondsman
F
1

Have a look at the official documentation: Fragment: https://developer.android.com/reference/android/app/Fragment

All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.

Fabrication answered 20/1, 2022 at 22:43 Comment(0)
C
-8

Here is my simple solution:

1 - Define your fragment

public class MyFragment extends Fragment {

    private String parameter;

    public MyFragment() {
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    } 
}

2 - Create your new fragment and populate the parameter

    myfragment = new MyFragment();
    myfragment.setParameter("here the value of my parameter");

3 - Enjoy it!

Obviously you can change the type and the number of parameters. Quick and easy.

Cornew answered 5/4, 2016 at 7:47 Comment(1)
This doesnt handle the reloading of the fragment by the system though.Fireproofing

© 2022 - 2024 — McMap. All rights reserved.