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 trying to run the below code during onResume().
SharedPreferences sharedPreferences = this.getSharedPreferences("com.example.appname.MainActivity", Context.MODE_PRIVATE);
String foregroundFragment = sharedPreferences.getString("foreground_fragment", "");
try {
Fragment fragment = (Fragment) Class.forName(foregroundFragment);
} catch (ClassNotFoundException e) {
Log.i("EXCEPTION!", e.toString());
}
However, Class.forName() appears to be the wrong method since it gives the following error message:
"Inconvertible types; cannot cast 'java.lang.Class>' to 'android.support.v4.app.Fragment.'
I'm not aware of another method that allows me to retrieve a String and use it to initialise a Fragment variable. I've searched online, but haven't been able to find anything other than Class.forName(). The string I'm retrieving via SharedPreferences does contain the fragment's full name, ie. "com.example.appname.ForegroundFragment".
Is there another method I should be using? Am I going about this all wrong?
Try with newInstance()
Fragment fragment = (Fragment) Class.forName(foregroundFragment).newInstance();
I'm working on wrapping my head around Dagger 2 and I've came upon a situation that I'm not really sure how to properly solve. I'm using dagger.android to set up subcomponents for each of my activities and fragments, where each subcomponent has a presenter with it's own dependencies.
The problem is that one fragment should display info fetched from the web, so I'm passing in a URL to the fragment to tell it what to display. But I'm not sure how to get Dagger too resolve the dependency on fragment load. I thought I needed a subcomponent builder, but that's apparently redundant as the compiler message is telling me it need the URL from a Provides method. I've also set up a qualifier for the URL and annotated a method in the fragment with it. I thought Dagger might pick it up, but I was wrong. Can someone give me an idea as to how best handle this situation?
If I am not wrong you are trying to provide url dependency in Fragment which is provided via Bundle. In that case add Provides method in Module which will take Fragment as parameter. As Fragment instance is on the graph it will automatically satisfy.
Fragment:
#Inject
String url;
public static DetailFragment newInstance() {
Bundle args = new Bundle();
args.putString("url", "http://stackoverflow.com/");
DetailFragment fragment = new DetailFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
printInfo();
}
public void printInfo(){
if (url == null) {
Log.i(TAG, "Url is null");
}else {
Log.i(TAG, "Url: " + url);
}
}
Module:
#Module
public class DetailFragmentModule {
#Provides
String provideUrl(DetailFragment detailFragment){
Bundle bundle = detailFragment.getArguments();
String url = bundle.getString("url");
return url;
}
#Provides
DetailFragmentView provideDetailFragmentView(DetailFragment detailFragment){
return detailFragment;
}
}
As you are injecting url in DetailFragment it will go to provideUrl method and satisfy DetailFragment and it will return url from Bundle.
This worked for me. :)
First of all, I already know that the FragmentManager often destroys then re-creates the Fragment using the default constructor. The coders must save important things in a Bundle of arguments once in the factory method, then take them out every time the Fragment is re-created in onCreate(Bundle).
public class MyFragment extends Fragment {
private static final String MY_STRING_CONSTANT = "param";
private int mIntegerMember;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntegerMember= getArguments().getInt(MY_STRING_CONSTANT);
}
}
My question is, is there any difference between this:
// Inside MyFragment.java
public MyFragment() {
// No-argument constructor required by the FragmentManager.
}
public static MyFragment newInstance(int param) {
// Factory method
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putInt(MY_STRING_CONSTANT, param);
fragment.setArguments(args);
return fragment;
}
// Somewhere else
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.frame_layout, MyFragment.newInstance(123)).commit();
And this:
// Inside MyFragment.java
public MyFragment() {
// No-argument constructor required by the FragmentManager.
}
public MyFragment(int param) {
// Parameterized constructor
Bundle args = new Bundle();
args.putInt(MY_STRING_CONSTANT, param);
setArguments(args);
}
// Somewhere else
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.frame_layout, new MyFragment(123)).commit();
I can see nothing which prevents the FragmentManager from calling the no-argument constructor. And the data I save in the parameterized constructor (in a Bundle object) will be preserved and restore during onCreate(), just like when I use the factory method.
Android never directly invokes a non-default constructor (nor a factory method) - technically, it doesn't really matter which you use. You can call setArguments (in an arbitrary method, even in a constructor) any time before you add the Fragment and that bundle will be saved/restored for you if the Fragment is recreated. Views also have special constructors invoked by Android, but you can make your own with arbitrary arguments if you wish (they just won't get invoked by Android).
Code for Fragment.setArguments:
/**
* 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;
}
Code for Fragment.instantiate:
/**
* Create a new instance of a Fragment with the given class name. This is
* the same as calling its empty constructor.
*
* #param context The calling context being used to instantiate the fragment.
* This is currently just used to get its ClassLoader.
* #param fname The class name of the fragment to instantiate.
* #param args Bundle of arguments to supply to the fragment, which it
* can retrieve with {#link #getArguments()}. May be null.
* #return Returns a new fragment instance.
* #throws InstantiationException If there is a failure in instantiating
* the given fragment class. This is a runtime exception; it is not
* normally expected to happen.
*/
public static Fragment instantiate(Context context, String fname, 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);
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = 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);
}
}
Fragment.instantiate is called when Android wants to create an instance of your Fragment. It simple calls through to Class.newInstance, which is a Java method to create a class using the default, zero-arg constructor. Looking at this code, there seems to be no problem with creating additional constructor and in calling setArguments within it.
As an a convention, it is typical to use a factory method when working with Fragments. Most official sample Fragment code also uses factory methods. Here are some possible reasons why:
If you are writing a custom constructor (with arguments), you will have to specify a zero-arg constructor as well. A common mistake is to create a custom constructor but forget to define a zero-arg constructor - this will lead to a crash when Android tries to invoke the zero-arg constructor when recreating your Fragment.
When creating a custom constructor, you may be tempted to directly assign the constructor arguments to fields. This is how pretty much any other Java class is written (and therefore how you will naturally want to write classes). Since Android will only invoke the zero-arg constructor on a Fragment, this data will not be available to any recreated instances. As you already know, using setArguments is the way to solve this. Even though you can do this within a constructor, using a factory method makes it more obvious that this class cannot be constructed in a normal way, reducing the possibility of committing the above mistake (or similar).
The FragmentManager implementation calls the default constructor of the Fragment. I would think that it would involve a lot of overhead to be able to determine which arguments to pass to a non-default constructor, so the Android team decided to go the Bundle route. If you do use a non-default constructor the data you pass to it, will not be kept during recreation, hence you will end up with null pointers. By using the setArguments()/getArguments() mechanism you guarantee that FragmentManager will initialize the Fragment correctly.
When you do this call:
transaction.add(R.id.frame_layout, new MyFragment(123));
it will be guaranteed that the first time will be fine. Now, lets say that the user rotates the screen (setRetainInstance() is not set), the FragmentManager will create a new instance of the Fragment by calling:
new MyFragment(); //the default constructor.
This means that all the variables that were supposed to be initialized in the non-default constructor will be null.
The docs tell us to avoid overloading Fragment's constructor, I would stick to their rules. You might get some unexpected behavior if you try to overload the constructor (even if you do setArguments() in the overloaded constructor).
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;
}