Why Do I Want to Avoid Non-Default Constructors in Fragments

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.

How to ignore the avoid non-default constructors in fragments error?

Found the solution. The easier way is to add these to the gradle:

 android {
lintOptions {
checkReleaseBuilds false
}
}

Or another way is to add @SuppressLint("ValidFragment")

Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]

This is a good strong suggestion by Android Studio. The reason is "constructors will not be called when the fragment is re-instantiated". Instead setArguments(Bundle) will. This is according to Google webpage @ Fragment.
Do you have code for Bundle passing?

In Android framework, override methods like onCreate and onCreateView are reinstated, not constructors, like when user changes orientation.

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

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


Related Topics



Leave a reply



Submit