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);
I'm looking to find a solution on how to inject fragment and pass arguments to it.
And i didn't find any proper solution because injecting the fragment means by the constructor which is not safe for states.
Is there any way to do this, without calling the newInstance pattern ?
Thanks,
Best.
Because Android manages the lifecycle of your Fragment, you should separate the problems of passing state into the Fragment through its bundle and injecting the Fragment with injectable deps. Usually, the best way to separate these is by providing a static factory method, which you might be calling the newInstance pattern.
public class YourFragment extends Fragment {
// Fragments must have public no-arg constructors that Android can call.
// Ideally, do not override the default Fragment constructor, but if you do
// you should definitely not take constructor parameters.
#Inject FieldOne fieldOne;
#Inject FieldTwo fieldTwo;
public static YourFragment newInstance(String arg1, int arg2) {
YourFragment yourFragment = new YourFragment();
Bundle bundle = new Bundle();
bundle.putString("arg1", arg1);
bundle.putInt("arg2", arg2);
yourFragment.setArguments(bundle);
return yourFragment;
}
#Override public void onAttach(Context context) {
// Inject here, now that the Fragment has an Activity.
// This happens automatically if you subclass DaggerFragment.
AndroidSupportInjection.inject(this);
}
#Override public void onCreate(Bundle bundle) {
// Now you can unpack the arguments/state from the Bundle and use them.
String arg1 = bundle.getString("arg1");
String arg2 = bundle.getInt("arg2");
// ...
}
}
Note that this is a different type of injection than you may be used to: Rather than getting a Fragment instance by injecting it, you are telling the Fragment to inject itself later once it has been attached to an Activity. This example uses dagger.android for that injection, which uses subcomponents and members-injection methods to inject #Inject-annotated fields and methods even when Android creates the Fragment instance outside of Dagger's control.
Also note that Bundle is a general key-value store; I've used "arg1" and "arg2" instead of coming up with more creative names, but you can use any String keys you'd like. See Bundle and its superclass BaseBundle to see all of the data types Bundle supports in its get and put methods. This Bundle is also useful for saving Fragment data; if your app is interrupted by a phone call and Android destroys your Activity to save memory, you can use onSaveInstanceState to put form field data into the Bundle and then restore that information in onCreate.
Finally, note that you don't need to create a static factory method like newInstance; you could also have your consumers create a new YourFragment() instance and pass in a particular Bundle design themselves. However, at that point the Bundle structure becomes a part of your API, which you may not want. By creating a static factory method (or Factory object or other structure), you allow the Bundle design to be an implementation detail of your Fragment, and provide a documented and well-kept structure for consumers to create new instances.
I have a list like fragment, and currently I am passing in info like so:
Fragment:
public void populate(Map<String, List<Book>> booksGroupedByType)
{
BookListAdapter bookListAdapter = new BookListAdapter(this.getActivity(), booksGroupedByType);
_lstBooks.setAdapter(bookListAdapter);
}
Activity:
private void populateBooksFragment()
{
Map<String, List<Book>> booksGroupedByType = _repository.getAllBooksGroupedByType();
BookListFragment bookListFragment = (BookListFragment) getFragment(R.id.fragBooks);
if (bookListFragment != null)
{
bookListFragment.populate(booksGroupedByType);
}
}
Then I felt it would be better if I could pass this information when creating the fragment, since we have no constructor available I looked up the method and found this:
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;
}
I tried to implement, but found my Map was not serializable as it was and needed more work. So my question is, why go through this? is there a disadvantage to using my original approach (populate), which would even be faster than serializing?
I thought perhaps my fragment will lose its data when rotated, but no, when rotating (in emulator) the list was kept intact.
Let's say you have some data obtained in time/resource consuming way. If you don't want to download them each time configuration changes (and activity is destroyed), you have to somehow persist them.
First option is to put data into the bundle, so it will be available for the fragment even after it is autorecreated by the system. It may work for simple types, but for arbitrary object it's usually not an option because of performance reasons (serialization/parcelization).
Second option would be retaining the fragment, by setting a flag in fragment's onCreate():
setRetainInstanceState(true)
In that case fragment won't be destroyed after configuration change, but just detached from activity being destroyed, and attached to the new one. Any data you will pass e.g. via setters will be available too.
See also: Understanding Fragment's setRetainInstance(boolean)
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;
}