4

I wrote a very simple program with one Activity that contains a fragment. I made the Fragment constructor to be private and i'm using static newInstance() method to return the fragment. The problem start when i'm rotating the phone. I'm getting an exception that says:

Unable to start activity ComponentInfo{com.example.todeleteimmediatley/com.example.MainActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.example.DatesFragment: could not find Fragment constructor

I debuged the program and saw that the exception is in the first line in onCreate method (when calling to super.onCreate()). Someone can explain me why the Fragment must have a constructor and why the exception occurs in the super.onCreate() phase?

DorVak
  • 297
  • 2
  • 9
  • 1
    Check https://stackoverflow.com/q/51831053/8298909 for more info. The important point is that the Android framework needs to be able to create instances of your Fragment, and it can only* do so using a public no-argument constructor. (*: Yes, FragmentFactory exists, but it is likely too complex for most to bother with.) – Ben P. Nov 23 '20 at 18:50
  • @BenP.,thanks!I for sure approve your answer.I will be glad to ask small questions(if you think they require a new post tell me but I really think they aren't required because they are small and related to original post):1. Does it mean that in *onCreate* I should check if the container already contain a Fragment before adding and commiting? Because as I understand from you,the onCreate().super will restore the old Fragment.2. Why it is better In *newInstance* method to add the information that the Fragment needs to the Bundle and not to store them as a members(attributes) of the *Fragment*? – DorVak Nov 23 '20 at 19:43
  • I've updated my answer to hopefully address your questions. – Ben P. Nov 23 '20 at 20:15

2 Answers2

1

When you rotate your device, your Activity will be destroyed and recreated. Part of this is destroying and recreating any Fragments that your Activity is hosting.

During the recreation step, the Android framework needs to instantiate a new instance of your Fragment. By default, it does so by invoking your Fragment's no-argument constructor. This means that this constructor must (a) exist and (b) be public.

This recreation of Fragments is triggered by the super.onCreate() of your Activity.

The general recommendation is to create a newInstance() factory method (as you have done), but to leave the default constructor alone (i.e., do not make it private). Yes, this means that it is still possible for someone to call the constructor directly, which you don't want, but it is required if you don't want to get involved with FragmentFactory.

Further questions from comments

Does it mean that in onCreate I should check if the container already contain a Fragment before adding and commiting? Because as I understand from you,the onCreate().super will restore the old Fragment.

My recommendation here is to only commit the Fragment transaction once, the first time your Activity starts up. Generally, this is achieved by checking that the savedInstanceState Bundle is null before committing the transaction:

if (savedInstanceState == null) {
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.foo, FooFragment.newInstance(...))
            .commit();
}

Because your Fragments are part of the instance state of your Activity, any recreation (any time savedInstanceState is not null) will be handled for you automatically.

Why it is better In newInstance method to add the information that the Fragment needs to the Bundle and not to store them as a members(attributes) of the Fragment?

Everything comes back to the fact that the Android framework needs to create a new instance of your Fragment. If you simply have member fields on your Fragment that you set, the Android framework will not be aware of these and will have no way to save or restore them.

However, the arguments Bundle is something that Android does know about. Arguments are considered part of the Fragment's instance state (and therefore part of the containing Activity's instance state), and will be automatically saved and restored. That is why you can only put certain types of data into the arguments Bundle; Android only knows how to "write" and "read" certain types of data during this recreation process.

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • thanks again. You wrote *replace* method, is it can be replace with *add* method? and I don't know how and which types the android know how to read, but as I understand the method newInstance() will run both if it is recreation or a new Fragment? But I am not sure with which parameter it would run, maybe this is the problem :) , because it won't runs with the parameter I "set" when I first call the *newInstance* method from the activity when I have committed the "add" transaction. I think that the question is:"with which parameters the method *newInstance* will run in recreation phase? – DorVak Nov 23 '20 at 21:20
  • Presumably your `newInstance()` method adds things to the `arguments` Bundle. Whatever values you put in that Bundle will be automatically saved and restored, so it will all work "automagically". And yes, you can use `add()` instead of `replace()` just fine with the code I wrote. – Ben P. Nov 23 '20 at 21:38
1

In Java

  • If a class does not define any constructor, then at compile-time, the compiler will generate a constructor that has no arguments (we usually called it as default constructor, no-argument constructor or zero-argument constructor).

  • If a class defines any constructor that has arguments, then at compile-time, the compiler will not generate a default constructor.

Someone can explain me why the Fragment must have a constructor?

You don't need to define a construtor if your fragment does not receive any parameter.

Why the exception occurs in the super.onCreate() phase?

In Android, there are several scenarios when the system needs to re-create an activity, such as.

  • When configuration changed, such as users rotate screen orientation or change language.

  • When the system is on low memory

When re-creating an activity, the system will create a new instance of the activity, then call activity lifecycle, the first one will be onCreate() callback. In your activity (for example MainActivity), you need to call super.onCreate() of its parent. This statement will restore all fragments (such as DatesFragment in your case) that managed by the activity.

Because this is a new instance of MainActivity, so it needs to create a new instance of all managed fragments as well (including DatesFragment). To do that they will invoke the default argument constructor.

But in DatesFragment you made the default constructor is private, which means it only accessed inside that class, there is no way the system can invoke the constructor of DatesFragment class, so they throw InstantiationException.

Solution:

  • If your fragment does not receive any parameter, don't define any constructor

  • If you need to pass parameters to your fragment, then use default constructor along with setArguments(Bundle)

Son Truong
  • 13,661
  • 5
  • 32
  • 58
  • Thanks for the great explanation. Too bad I can't approve more than one answer so I only voted your post up – DorVak Nov 24 '20 at 17:23