I'm learning how to build apps using mvvm approach. In my app I have activity with two fragments and ViewPager. So what I'm trying to solve now is how to reuse fragment class, because the are basically identical, the only difference is the data that I want to observe from ViewModel depending on which fragment is displayed. So what I've done so far
stateAdapter.addFragment(BenchmarksFragment.newInstance("collections"), getString(R.string.collection));
stateAdapter.addFragment(BenchmarksFragment.newInstance("maps"), getString(R.string.map));
In activity I have adapter that creates fragments with arguments.
public static BenchmarksFragment newInstance(String value) {
final BenchmarksFragment fragment = new BenchmarksFragment();
final Bundle args = new Bundle();
args.putString(KEY, value);
fragment.setArguments(args);
return fragment;
}
Further in fragment onCreate I pass argument to VMFactory
String arg = getArguments() == null ? DEFAULT : getArguments().getString(KEY);
BenchmarksVMFactory factory = new BenchmarksVMFactory(requireActivity().getApplication(), arg);
In ViewModel constructor I simply assign this argument to variable
public BenchmarksViewModel(#NonNull Application application, String arg) {
super(application);
this.arg = arg;
Log.d("TAG", "BenchmarksViewModel: " + arg);
}
Through logs I see that iI get only argument from first fragment "collections".
So my question is how to correctly pass arguments to ViewModel and further manage the logic of displaying the right list(for "collections" fragment collectionsList and for "maps" fragment mapList) using same fragment class?
If you can advise me articles/examples to read I would appreciate that.
UPDATE
I was able to get argument from second fragment by changing
model = new ViewModelProvider(requireActivity(), factory).get(BenchmarksViewModel.class);
requireActivity() to getViewModelStore()
model = new ViewModelProvider(getViewModelStore(), factory).get(BenchmarksViewModel.class);
What will be the implications if I passed an object to a fragment without using bundle? I just opened one of the old code and found it there and couldn't help asking this question as I have never tried it. Also I am not finding any memory leaks in it.
This is how it is implemented -
Activity class:
MyFragment fragment =
MyFragment.newInstance(getIntent().getStringExtra(DATA),
instance.getCallback(),
instance.getRequest());
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commit();
Fragment class:
public class MyFragment extends Fragment {
public MyFragment() {
/* Required empty public constructor */
}
public static MyFragment newInstance(String data, Callback callback,
Request request) {
MyFragment fragment = new MyFragment();
fragment.setCallback(callback);
fragment.setRequest(request);
fragment.setData(data);
return fragment;
}
private void setCallback(Callback callback) {
this.callback = callback;
}
private void setData(Data data) {
this.data = data;
}
private void setRequest(Request request) {
this.request = request;
}
}
Generally, what I have used till date is Fragment#setArguments(Bundle)
Please check out this answer: Why its not recommended to pass object to fragment with simple getter setter.
Long story short, you will lose your data on configuration changes.
There are mainly two ways of communicating with a fragment: via bundles or via an interface that you implement in your activity. Please see this link in order to see how to properly communicate with a fragment via an interface: https://developer.android.com/training/basics/fragments/communicating
I want to pass a data object to Fragment, here are two ways to do this:
public class MyFragment extends Fragment {
private Serializable way1;
private Serializable way2;
public void setDataWay1(Serializable way1) {
this.way1 = way1;
}
public void setDataWay2(Serializable way2) {
Bundle data = new Bundle();
data.putSerializable("data", way2);
setArguments(data);
}
}
So, what is the difference between the 2 ways? Sometimes, way1 may cause NullPointerException,why? If I want to pass a OnClickListener to Fragment, what should I do?
While both methods can set the appropriate data to your fragment for first time initialization. Note that fragments will be recreated and destroyed by the system (for example on screen rotation). When that happens the system will not really call the setter way (method 1) hence, it will be a giant FAIL. Therefore, it is recommended to use the setArguments() way.
I am creating an app with Fragments and in one of them, I created a non-default constructor and got this warning:
Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead
Can someone tell me why this is not a good idea?
Can you also suggest how I would accomplish this:
public static class MenuFragment extends ListFragment {
public ListView listView1;
Categories category;
//this is my "non-default" constructor
public MenuFragment(Categories category){
this.category = category;
}....
Without using the non-default constructor?
It seems like none of the answers actually answer "why use bundle for passing parameters rather than non default constructors"
The reason why you should be passing parameters through bundle is because when the system restores a fragment (e.g on config change), it will automatically restore your bundle.
The callbacks like onCreate or onCreateView should read the parameters from the bundle - this way you are guaranteed to restore the state of the fragment correctly to the same state the fragment was initialised with (note this state can be different from the onSaveInstanceState bundle that is passed to the onCreate/onCreateView)
The recommendation of using the static newInstance() method is just a recommendation. You can use a non default constructor but make sure you populate the initialisation parameters in the bundle inside the body of that constructor. And read those parameters in the onCreate() or onCreateView() methods.
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.
Your Fragment shouldn't have constructors because of how the FragmentManager instantiates it.
You should have a newInstance() static method defined with the parameters you need, then bundle them and set them as the arguments of the fragment, which you can later access with the Bundle parameter.
For example:
public static MyFragment newInstance(int title, String message) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle(2);
bundle.putInt(EXTRA_TITLE, title);
bundle.putString(EXTRA_MESSAGE, message);
fragment.setArguments(bundle);
return fragment ;
}
And read these arguments at onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
title = getArguments().getInt(EXTRA_TITLE);
message = getArguments().getString(EXTRA_MESSAGE);
//...
}
This way, if detached and re-attached, the object state can be stored through the arguments, much like bundles attached to Intents.
If you use parameter for some class. try this
SomeClass mSomeInstance;
public static final MyFragment newInstance(SomeClass someInstance){
MyFragment f = new MyFragment();
f.mSomeInstance = someInstance;
return f;
}
I think, there is no difference between static constructor and two constructors (empty and parametrized one that stores arguments into a Fragment's arguments bundle), most probably, this rule of thumb is created to reduce probability of forgetting to implement no-arg constructor in Java, which is not implicitly generated when overload present.
In my projects I use Kotlin, and implement fragments with a primary no-arg constructor and secondary constructor for arguments which just stores them into a bundle and sets it as Fragment arguments, everything works fine.
If fragment uses non-default constructors after configuration changing the fragment will lose all data.
I have seen two general practices to instantiate a new Fragment in an application:
Fragment newFragment = new MyFragment();
and
Fragment newFragment = MyFragment.newInstance();
The second option makes use of a static method newInstance() and generally contains the following method.
public static Fragment newInstance()
{
MyFragment myFragment = new MyFragment();
return myFragment;
}
At first, I thought the main benefit was the fact that I could overload the newInstance() method to give flexibility when creating new instances of a Fragment - but I could also do this by creating an overloaded constructor for the Fragment.
Did I miss something?
What are the benefits of one approach over the other? Or is it just good practice?
If Android decides to recreate your Fragment later, it's going to call the no-argument constructor of your fragment. So overloading the constructor is not a solution.
With that being said, the way to pass stuff to your Fragment so that they are available after a Fragment is recreated by Android is to pass a bundle to the setArguments method.
So, for example, if we wanted to pass an integer to the fragment we would use something like:
public static MyFragment newInstance(int someInt) {
MyFragment myFragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
myFragment.setArguments(args);
return myFragment;
}
And later in the Fragment onCreate() you can access that integer by using:
getArguments().getInt("someInt", 0);
This Bundle will be available even if the Fragment is somehow recreated by Android.
Also note: setArguments can only be called before the Fragment is attached to the Activity.
This approach is also documented in the android developer reference: https://developer.android.com/reference/android/app/Fragment.html
The only benefit in using the newInstance() that I see are the following:
You will have a single place where all the arguments used by the fragment could be bundled up and you don't have to write the code below everytime you instantiate a fragment.
Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("someString", someString);
// Put any other arguments
myFragment.setArguments(args);
Its a good way to tell other classes what arguments it expects to work faithfully(though you should be able to handle cases if no arguments are bundled in the fragment instance).
So, my take is that using a static newInstance() to instantiate a fragment is a good practice.
There is also another way:
Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
While #yydl gives a compelling reason on why the newInstance method is better:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
it's still quite possible to use a constructor. To see why this is, first we need to see why the above workaround is used by Android.
Before a fragment can be used, an instance is needed. Android calls YourFragment() (the no arguments constructor) to construct an instance of the fragment. Here any overloaded constructor that you write will be ignored, as Android can't know which one to use.
In the lifetime of an Activity the fragment gets created as above and destroyed multiple times by Android. This means that if you put data in the fragment object itself, it will be lost once the fragment is destroyed.
To workaround, android asks that you store data using a Bundle (calling setArguments()), which can then be accessed from YourFragment. Argument bundles are protected by Android, and hence are guaranteed to be persistent.
One way to set this bundle is by using a static newInstance method:
public static YourFragment newInstance (int data) {
YourFragment yf = new YourFragment()
/* See this code gets executed immediately on your object construction */
Bundle args = new Bundle();
args.putInt("data", data);
yf.setArguments(args);
return yf;
}
However, a constructor:
public YourFragment(int data) {
Bundle args = new Bundle();
args.putInt("data", data);
setArguments(args);
}
can do exactly the same thing as the newInstance method.
Naturally, this would fail, and is one of the reasons Android wants you to use the newInstance method:
public YourFragment(int data) {
this.data = data; // Don't do this
}
As further explaination, here's Android's Fragment Class:
/**
* 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.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
Note that Android asks that the arguments be set only at construction, and guarantees that these will be retained.
EDIT: As pointed out in the comments by #JHH, if you are providing a custom constructor that requires some arguments, then Java won't provide your fragment with a no arg default constructor. So this would require you to define a no arg constructor, which is code that you could avoid with the newInstance factory method.
EDIT: Android doesn't allow using an overloaded constructor for fragments anymore. You must use the newInstance method.
Some kotlin code:
companion object {
fun newInstance(first: String, second: String) : SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putString("firstString", first)
putString("secondString", second)
}
}
}
}
And you can get arguments with this:
val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}
I disagree with yydi answer saying:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
I think it is a solution and a good one, this is exactly the reason it been developed by Java core language.
Its true that Android system can destroy and recreate your Fragment. So you can do this:
public MyFragment() {
// An empty constructor for Android System to use, otherwise exception may occur.
}
public MyFragment(int someInt) {
Bundle args = new Bundle();
args.putInt("someInt", someInt);
setArguments(args);
}
It will allow you to pull someInt from getArguments() latter on, even if the Fragment been recreated by the system. This is more elegant solution than static constructor.
For my opinion static constructors are useless and should not be used. Also they will limit you if in the future you would like to extend this Fragment and add more functionality to the constructor. With static constructor you can't do this.
Update:
Android added inspection that flag all non-default constructors with an error.
I recommend to disable it, for the reasons mentioned above.
I'm lately here. But somethings I just known that might help you a bit.
If you are using Java, there is nothing much to change. But for Kotlin developers, here is some following snippet I think that can make you a basement to run on:
Parent fragment:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
return T::class.java.newInstance().apply {
arguments = Bundle().also { it.putString("key_text_arg", text) }
}
}
Normal call
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
You can extend the parent init operation in child fragment class by:
fun newInstance(): ChildSampleFragment {
val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
// Do anything with the current initialized args bundle here
// with child.arguments = ....
return child
}
Happy coding.
Best practice to instance fragments with arguments in android is to have static factory method in your fragment.
public static MyFragment newInstance(String name, int age) {
Bundle bundle = new Bundle();
bundle.putString("name", name);
bundle.putInt("age", age);
MyFragment fragment = new MyFragment();
fragment.setArguments(bundle);
return fragment;
}
You should avoid setting your fields with the instance of a fragment. Because whenever android system recreate your fragment, if it feels that the system needs more memory, than it will recreate your fragment by using constructor with no arguments.
You can find more info about best practice to instantiate fragments with arguments here.
Since the questions about best practice, I would add, that very often good idea to use hybrid approach for creating fragment when working with some REST web services
We can't pass complex objects, for example some User model, for case of displaying user fragment
But what we can do, is to check in onCreate that user!=null and if not - then bring him from data layer, otherwise - use existing.
This way we gain both ability to recreate by userId in case of fragment recreation by Android and snappiness for user actions, as well as ability to create fragments by holding to object itself or only it's id
Something likes this:
public class UserFragment extends Fragment {
public final static String USER_ID="user_id";
private User user;
private long userId;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userId = getArguments().getLong(USER_ID);
if(user==null){
//
// Recreating here user from user id(i.e requesting from your data model,
// which could be services, direct request to rest, or data layer sitting
// on application model
//
user = bringUser();
}
}
public static UserFragment newInstance(User user, long user_id){
UserFragment userFragment = new UserFragment();
Bundle args = new Bundle();
args.putLong(USER_ID,user_id);
if(user!=null){
userFragment.user=user;
}
userFragment.setArguments(args);
return userFragment;
}
public static UserFragment newInstance(long user_id){
return newInstance(null,user_id);
}
public static UserFragment newInstance(User user){
return newInstance(user,user.id);
}
}
use this code 100% fix your problem
enter this code in firstFragment
public static yourNameParentFragment newInstance() {
Bundle args = new Bundle();
args.putBoolean("yourKey",yourValue);
YourFragment fragment = new YourFragment();
fragment.setArguments(args);
return fragment;
}
this sample send boolean data
and in SecendFragment
yourNameParentFragment name =yourNameParentFragment.newInstance();
Bundle bundle;
bundle=sellDiamondFragments2.getArguments();
boolean a= bundle.getBoolean("yourKey");
must value in first fragment is static
happy code
Ideally we shouldn't pass anything in the fragment constructor, fragment constructor should be empty or default.
Now the second question is, what if we want to pass interface variable or parameters-
We should use Bundle to pass data.
For Interface we can putParceble in bundle and make that interface implement parceble
If possible we can implement that interface in activity and in fragment we can initialize listener in OnAttach where we have context[ (context) Listener].
So that during configuration change (e.g. Font change) the Activity recreation listener won't go uninitialize and we can avoid a null pointer exception.
Best way to instantiate the fragment is use default Fragment.instantiate method or create factory method to instantiate the the fragment
Caution: always create one empty constructor in fragment other while restoring fragment memory will throw run-time exception.
You can use smth like this:
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)
because this answer now is Deprecated
Create an instance of the fragment using kotlin code.
Write in activity
val fragment = YourFragment.newInstance(str = "Hello",list = yourList)
Write in fragment
fun newInstance(str: String, list: ArrayList<String>): Fragment {
val fragment = YourFragment()
fragment.arguments = Bundle().apply {
putSerializable("KEY_STR", str)
putSerializable("KEY_LIST", list)
}
return fragment
}
Using the same fragment retrieve the data from bundle
val str = arguments?.getString("KEY_STR") as? String
val list = arguments?.getSerializable("KEY_LIST") as? ArrayList<String>
setArguments() is useless. It only brings a mess.
public class MyFragment extends Fragment {
public String mTitle;
public String mInitialTitle;
public static MyFragment newInstance(String param1) {
MyFragment f = new MyFragment();
f.mInitialTitle = param1;
f.mTitle = param1;
return f;
}
#Override
public void onSaveInstanceState(Bundle state) {
state.putString("mInitialTitle", mInitialTitle);
state.putString("mTitle", mTitle);
super.onSaveInstanceState(state);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (state != null) {
mInitialTitle = state.getString("mInitialTitle");
mTitle = state.getString("mTitle");
}
...
}
}
I believe I have a much simpeler solution for this.
public class MyFragment extends Fragment{
private String mTitle;
private List<MyObject> mObjects;
public static MyFragment newInstance(String title, List<MyObject> objects)
MyFragment myFrag = new MyFragment();
myFrag.mTitle = title;
myFrag.mObjects = objects;
return myFrag;
}