Is It possible To Pass Constructor Parameters In XML? - android

I'm writing a class which extends Fragment and I would like to use it in an XML file. For my class I have a constructor which takes one parameter which specifies how the fragment should layout its UI.
I would like to use my class in XML but would also like to specify this layout parameter as well but is it possible to include it in the XML?

Fragments are required to have parameterless constructors. This is because fragment instances can be created multiple times. Quoting the docs:
Every fragment must have an empty constructor, so it can be
instantiated when restoring its activity's state. It is strongly
recommended that subclasses do not have other constructors with
parameters, since these constructors will not be called when the
fragment is re-instantiated; instead, arguments can be supplied by the
caller with setArguments(Bundle) and later retrieved by the Fragment
with getArguments().
Unfortunately, you cannot use the suggested setArguments in the XML but it might be possible to do it differently, depending on what possible set of arguments you are planning to pass to your fragment. You can, for example, subclass your fragment appropriately:
public class MyPortraitFragment extends MyFragment {
public MyPortraitFragment() {
Bundle args = MyFragment.getPortraitArgsBundle();
setArguments(args);
}
}
public class MyLandscapeFragment extends MyFragment {
public MyLandscapeFragment() {
Bundle args = MyFragment.getLandscapeArgsBundle();
setArguments(args);
}
}
You would then use MyLandscapeFragment or MyPortraitFragment in your XML, depending on which variant of the fragment you want to use.

Related

Why Fragment have non-empty constructor now?

We've been told not pass variable via Fragment constructor for a long time.
But it seems Fragment itself now have other Fragment constructor to pass data like below. Is it still best practice for us to not passing data from Fragment constructor? Or is there any trick to get rid of it but only in Framework level?
public Fragment() {
initLifecycle();
}
#ContentView
public Fragment(#LayoutRes int contentLayoutId) {
this();
mContentLayoutId = contentLayoutId;
}
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.

Can we pass param to fragment directly use set method

I know that we can pass data to Fragment using setArguments method.
But I also test that if I add a public field in the target fragment and set it from the other fragment, I can also correctly get the field value.
Then why should we use Bundle as the communication bridge?
Is there any performance issue that directly setting the fragment field?
public class SecondFragment extends Fragment {
//can we directly set this field rather than using setArguments(Bundle b)?
public List<String> strings;
}
I think one of the main reasons are that if Android recreate the fragment for various reasons outside of your code, it then calls an empty public constructor of your fragment, so that's why it's recommended to use Bundle. Or at least that's what I've been told.

Fragment and factory method

What is the aim of using a Factory to construct our fragments?
Android Studio provides some boilerplate code generation, and provides the factory method when creating a fragment.
So what is the purpose of that? What is the gain?
Using static factory method is not just for the fragment, I use it to create intent, adapter and other classes as well. By this approach you simply have the control of the object creation.
One of the good advantage is you simply increase the cohesion by encapsulating bundle keys.
class FragmentFoo extends Fragment{
}
When you want to send a bundle to this fragment from outside
// Activity Foo
Bundle bundle = new Bundle();
bundle.putString("name","Foo");
Fragment fragment = new FragmentFoo();
fragment.setArgs(bundle);
To extract this name, you need to use "name" key in fragment as well, if you use it as hardcoded, you may have some errors,typos. So you can use a constant in order to make sure you don't make typo. But in this case you need to put it somewhere both can see it. Some creates another class in order to keep all contants which is very ugly and hard to maintain, some puts the keys in the fragment and make it public and use it everywhere,
by static factory method, you can simply keep everything in fragment and no need to expose. Whoever needs to use this fragment will have a clear idea what it requires and also will not need to know what keys are. Just send the required params will be enough for them.
class FragmentFoo extends Fragment{
private static final String KEY_NAME = "name";
private String name;
public static Fragment newInstance(String name){
Bundle bundle = new Bundle();
bundle.putString(KEY_NAME, "name");
Fragment fragment = new FragmentFoo();
fragment.setArgs(bundle);
return fragment;
}
}
Static factory methods allow us to initialize and setup a new Fragment without having to call its constructor and additional setter methods. Providing static factory methods for your fragments is good practice because it encapsulates and abstracts the steps required to setup the object from the client.
http://www.androiddesignpatterns.com/2012/05/using-newinstance-to-instantiate.html
A Fragment must have a constructor with no arguments. That's because Android will call the default constructor to recreate the fragment. If you're not passing in arguments, then you should not need either a constructor or a static factory method (AKA, virtual constructor).
If you create a fragment via template then remove all code in fragment then add constructor and hypothetical arguments which you want to pass to fragment
public class BlankFragment extends Fragment {
public BlankFragment(int arg)
{
}
}
you will get this pop up message
Avoid non-default constructors in fragments...
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
The Android system instantiates fragments with the no args constructor, which means you need to add a second constructor with no args for the Android System to re-initialize the fragment on state change events like when phone goes into landscape mode and fragment is destroyed then recreated by Android (not your host activity)
public class BlankFragment extends Fragment {
public BlankFragment()
{
}
public BlankFragment(int arg)
{
}
}
Now if you tilt your phone and go to land scape mode, Android will re-init the fragment from the empty constructor, but you won't get your arguments passed to it.
You can still get the bundle on a state change, but you now have two constructors when you could have a single static factory method.

Pass custom class to Fragment

I have a custom class called Data which contains all the data. The main activity creates two fragments. I have a field in the main activity like this:
private Data data = new Data();
The fragments are created with this method:
private ArrayList<Fragment> getFragments() {
ArrayList<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(new fragment_one());
fragments.add(new fragment_two());
return fragments;
}
I need to pass the data field to the fragments, so the fragments can acces the methods of Data.
I tried by creating a bundle, but I can't pass a custom class. What can I do?
Bundles can accept custom classes, if they implement either Parcelable or Serializable, Parcelable is faster but more work to implement and Serializable is easier to implement, but slower.
Then you can do this:
Bundle bundle = new Bundle();
bundle.putSerializable("MyData", data);
fragment_one.setArguments(bundle);
Now fragment_one will have access to data in it's onCreate(Bundle bundleHoldingData) method.
Another option is to have a public setter in your fragment that takes in data. Benefit of this is you don't have to wait till data is ready to add the fragment.
Data needs to either implement Parcelable or Serializable.
You can then either use bundle.putParcelable() or bundle.putSerializable() to pass the data to both fragments via the setArguments() method.
You should not pass references to fragments, all your data should be passed using setArguments (unless your fragment is retained). The reason is that android might destroy your fragment during configuration change, and recreate it during activity creation.
So you should either pass your data inside setArguments, or give access to it using singleton class, ie. application class.
[edit] - havent tried this myself but you can find online tools to make your data class parcelable, here is one: http://devk.it/proj/parcelabler/
You can set a static object depending how much data you keep and careful with the memory leaks. That way you can reach it within the fragments. But making the data parcelable and pass it with the bundle is always a better choice.
One option is to provide an accessor for the data on the Activity class. Then, in your fragment, you call getActivity(), cast it to the derived type, and get the data, as needed.
This of course creates a dependency from your fragment to the activity, but if it's not meant to be a generic, re-usable fragment, it would be very simple and straightforward to implement and means you can get a reference to the current Activity data and not a copy like the Bundle / Parcelable / Serializable strategy would.

What's the point of setArguments?

Hi I was looking at the following Fragments example on the android site.
http://developer.android.com/guide/components/fragments.html#Example
I would like to know why certain methods are performed.
Why for instance, in the detailsFragment is the following method performed:
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
Could you not also simply instantiate the DetailsFragment and use a setter method to set index instead. Bypassing the whole setArguments.
What's the point of using setArguments in the first place? Could you not just use setters and getters?
You can use getters and setters, but by passing in a bundle you don't need to write that code, since it's already there. Also, I believe that these arguments are automatically passed in again if the screen orientation changes, which also makes life easier.
Essentially, setArguments and getArguments is just a design pattern that Google suggests you follow:
Every fragment must have an empty constructor, so it can be
instantiated when restoring its activity's state. It is strongly
recommended that subclasses do not have other constructors with
parameters, since these constructors will not be called when the
fragment is re-instantiated; instead, arguments can be supplied by the
caller with setArguments(Bundle) and later retrieved by the Fragment
with getArguments().
http://developer.android.com/reference/android/app/Fragment.html
I take that to include setters which are needed for your Fragment to operate as well. Then again - there's nothing forcing you to do it this way, and as you know - it's not the only way things could be made to work.
Just to add to Matthew's answer: he quoted correctly that Fragments need to have an empty constructor, so that the framework can re-instantiate them when needed.
It is fine to use getters and setters, but as the framework may destroy and re-create your Fragment, you must ensure to not lose those parameters.
This must be done via Fragment.onSaveInstanceState(). The saved stated will be passed back to you as the parameter savedInstanceState in Fragment.onCreate(), Fragment.onCreateView() and several other methods.
Using Fragment.setArguments() is (in most cases, I assume) easier, at the framework will automatically preserve the arguments and thus will do most of the work for you.
Setters may be the way to go for parameters you supply to the Fragment initially and which the fragment may adjust itself over time. Dealing with the savedInstanceState alone may be easier in this case than dealing with savedInstanceState and arguments - where you have to make a decision which is the valid parameter.
public void setArguments (Bundle args)
Supply the construction arguments for this fragment. This can only be
called before the fragment has been attached to its activity; that is,
you should call it immediately after constructing the fragment. The
arguments supplied here will be retained across fragment destroy and
creation (may be the text in bold was missing from official
documentation previously)
Fragments.setArguments(Bundle args)
In addition setters can be misused. If updateSomeOtherStuff() will change some view this will crash.
public class MyFragment extends Fragment {
void setData(someData){
this.someData = someData;
updateSomeOtherStuff()
}
}
If one passing in a bundle it is not possible to misuse the setter and you will always know that this will be set within the lifecycle methods.

Categories

Resources