Reputation: 337
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?
Upvotes: 4
Views: 588
Reputation: 14173
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)
Upvotes: 1
Reputation: 54204
When you rotate your device, your Activity
will be destroyed and recreated. Part of this is destroying and recreating any Fragment
s 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
.
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.
Upvotes: 1