Do Fragments Really Need an Empty Constructor

Do fragments really need an empty constructor?

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...
}

Android fragments: is empty constructor really required?

is empty constructor really required?

Yes.

Why does it work when orientation does not change?

Because Android is not trying to recreate your fragments.

Why does it fail when orientation is changed?

Because Android is recreating your fragments.

When a configuration change occurs (e.g., orientation change), by default Android destroys and recreates your activity, and also destroys and recreates the fragments in that activity. The "recreates the fragments" part is why you need the zero-argument public constructor on your fragments. It is also used in other cases, such as with a FragmentStatePagerAdapter.

Or, to quote the documentation:

All subclasses of Fragment must include a public empty 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 empty constructor is not available, a runtime exception will occur in some cases during state restore.

why fragment have default constructor?

See this question and comments / answers. In short, Fragments need to have a no-args constructor for the Android system to instantiate them (I believe the activity history manager does this, etc).

If the constructor is explicit, as in the unaltered example, then it's really there to ensure the no-args constructor works if other constructors are added, and the comment serves as a reminder (that or the original author didn't really understand the purpose and/or how the language works).

If the no-args constructor may be implicit - ie it is omitted in the source and there are no other constructors declared - then one is created behind the scenes as per the JLS (this is what happened when you deleted the constructor in your example):

If a class contains no constructor declarations, then a default
constructor with no formal parameters and no throws clause is
implicitly declared.

If the class being declared is the primordial class Object, then the
default constructor has an empty body. Otherwise, the default
constructor simply invokes the superclass constructor with no
arguments.

Why the necessity of having a public empty constructor in every Fragment?

Java automatically provides a default empty constructor when no constructor is defined (as is the case with Fragments)

Yes it does, but if you have a parametric constructor then it doesn't.

But in the case of fragments, the FragmentManagerImpl recreates then with reflection using the default constructor after process death (low memory condition), so any parameters you would provide instead of using the arguments bundle will be nulled out.

Why Fragment have non-empty constructor now?

Found the answer, according to Android documentation.

You must set a custom FragmentFactory if you want to use a non-default constructor to ensure that your constructor is called when the fragment is re-instantiated.
https://developer.android.com/reference/androidx/fragment/app/Fragment#Fragment(int)

So we have this new constructor can be used, but we still have to provide FragmentFactory to deal with the case fragment is recreated.

Fragments are required to have a parameterless constructor - what if my Fragment needs parameters?

You need to pass it a Bundle with the parameters, like so:

class MyFragment extends Fragment{
private static final String MY_FIELD = "myfield";


public static MyFragment newInstance(int param){
MyFragment result = new MyFragment();
Bundle args = new Bundle();
args.putInt(MY_FIELD, param);
result.setArguments(args);
return result;
}


...


private void myMethod(){
int myField = getArguments.getInt(MY_FIELD);

}

}

Do we really need to avoid constructors with default values for Fragments and Activites in Kotlin?

This is valid. The reason you're discouraged from overloading fragment constructors is that Android can recreate them, and it will use the default one: MyFragment()

But the way Kotlin implements default parameter values behind the scenes is by creating additional constructors. You can decompile your class and see it contains two constructors now, one receiving someObject, and another empty.

From the JVM perspective the empty constructor would look like this:

public A() {
this(new SomeObjectImpl());
}

Calling it will populate your fragment with new instances of implemented classes.

Why do I want to avoid non-default constructors in fragments?

Make a bundle object and insert your data (in this example your Category object). Be careful, you can't pass this object directly into the bundle, unless it's serializable.
I think it's better to build your object in the fragment, and put only an id or something else into bundle. This is the code to create and attach a bundle:

Bundle args = new Bundle();
args.putLong("key", value);
yourFragment.setArguments(args);

After that, in your fragment access data:

Type value = getArguments().getType("key");

That's all.



Related Topics



Leave a reply



Submit