I'm wondering if calling setArguments on a Fragment immediately after its instantiation creates any problems.
For example, say we have the following snippet:
Fragment myFragment = new CustomFragment();
Bundle args = new Bundle();
args.putBoolean("amIAnArg", true);
myFragment.setArguments(args);
This code seems to work fine, although it looks like the code should create a race condition since a Fragment's arguments can only be set before the onAttach method is called.
Are there any issues with setting a Fragment's arguments in this way?
Just like an Activity, Fragments have a specific lifecycle, and are not "created" like simple Java objects. When you commit a FragmentTransaction, it's asynchronous and isn't immediately attached or created. It's queued on the main thread to occur at a later time. Only then will it go through its lifecycle methods (e.g. onCreate(), onAttach()).
You should set the arguments this way, and should do so before committing the FragmentTransaction -- however, you could technically do it right after committing the transaction with no ill effects. As others have stated, what you're doing is the suggested newInstance() factory method for fragments [1]. For example:
private static final String ARG_IS_ARG = "is_arg";
public static CustomFragment newInstance(boolean isArg) {
CustomFragment result = new CustomFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_IS_ARG, isArg);
result.setArguments(args);
return result;
}
[1] http://developer.android.com/reference/android/app/Fragment.html
There should be no problem. I am working on a project right now that uses this exact format in several spots.
This format is in the Android Developers example project as well (find 'Arguments'):
http://developer.android.com/reference/android/app/Fragment.html
Similar to what #kcoppock said, in most cases you will be instantiating the Fragment on the UI thread, and then Android queues up the arguments you pass to the Fragment on the UI thread as well. There's no race conditions because the operations take place at different times on the same thread.
For more information, check out this blog post on Activities and Fragments: http://www.zerotohired.com/2015/02/passing-data-between-activities-and-fragments-in-android.
Related
I tried to use putFragment to save reference for fragments for using it in future (and not recreate) before replace.
BaseFragment last = (BaseFragment) mContext.getSupportFragmentManager().getFragments().get(mContext.getSupportFragmentManager().getFragments().size() - 1);
mContext.getSupportFragmentManager().putFragment(mContext.getBundle(), last.getType().toString(), last);
And before creating new fragment i check bundle for fragment existing:
BaseFragment f = (BaseFragment) getSupportFragmentManager().getFragment( mBundle, type.toString());
if(f != null)
return f;
else
// create new fragment
FragmentType is just my enum:
public static enum FragmentType{
PROJECTS,
BALANCE
}
But for all fragments (for all keys in bundle) it generates same integer value.
So getFragment method returns wrong fragment. Where is the problem?
I saw this post with same issue. But it still is not resolved...
I needed it for storing fragment state while replacing it. I tried it after this answer.
Use cases of these methods are described in this question: get/putFragment()
Also, don't use getFragments(); It is a method annotated by #Hide and is not supposed to be used, because android can destroy an recreate activities and fragments at any given time, so the list is not always correct.
While the methods do give acces to the 'pointer' of the fragment, It is meant to only restore state.
Java is a language with a garbage collector, and android builds on top of that with objects that do not give us control over their lifecycle.
These methods (put, get) are meant to be used inside onSaveInatanceState and onRestoreInstanceState to save the "state" of the fragment so that it (the state) can be restored later.
Are you using them in that context?
If not, you are essentially trying to override the way the OS handles the fragments' lifecycle, which is asking for trouble.
In other words, don't try to get back the same instance, the system just cannot guarantee that to you.
There is an interesting discussion on this issue over at the android developers
Can someone explain how setArguments() works? I don't get it. I get how to use it, but I don't really understand how the code below can actually work:
public class MyDialogFragment extends DialogFragment{
public static MyDialogFragment newInstance(String test) {
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("test", test);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("DEBUG", getArguments().getString("test")); // This actually works!
}
}
I would have thought that onCreate() got called before the call to setArguments(), but clearly it does not. Is this the way it is supposed to work, or is it just pure luck that the call to setArguments() gets executed before onCreate() on my particular device? Could the opposite happen under different circumstances (faster/slower/different device, etc.)?
Maybe someone can point me to an article that describes how the flow of events for this works? I don't see where onCreate() would get called in the code above, unless it is called asynchronously, which to me sounds like it would be risky to rely on getArguments() inside onCreate()...
Your newInstance() is a factory method you call yourself. It creates the Fragment object and sets the arguments on it.
The created fragment object is then passed to a fragment transaction which eventually makes the fragment lifecycle callbacks such as onCreate() to be invoked at appropriate times.
In case the framework needs to recreate your fragment e.g. due to an orientation change, it will use the no-arg constructor of the fragment and retain the arguments you have set on the object. newInstance() and such are useful when the fragment is created for the first time.
For documentation, the Fragment class documentation is a good starting point.
onCreate() is a method that is only called later by the framework, after the Fragment has been attached to the Activity, as you can see in the Fragment Lifecycle.
I'm starting using fragments in android but I have doubts: 1.- is necessary using this part of code (newInstance, setArguments)?, if so, why?
public static class DetailsFragment extends Fragment {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
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;
}
public int getShownIndex() {
return getArguments().getInt("index", 0);
}
http://developer.android.com/reference/android/os/Bundle.html
2.-if this code would have an OnCreate method, when will this be called?
1) Why is newInstance needed?
newInstance methods are needed because the framework encourages you to only ever have one constructor, taking no arguments. Having more than one constructor is somewhat frowned upon.
It needs this constructor to be public, so that it can recreate you Fragments when, for example, you undergo a configuration change. The same Bundle that you passed to setArguments on the original will be given to the copy too (as well as a savedInstanceState Bundle from onSaveInstanceState).
Doing this for all fragments, including ones that have no arguments leads to consistent usage of fragments and ease of maintenance. Namely, do make sure to always pass an arguments Bundle, even if it's empty, so that you don't have to check getArguments() for null.
2) onCreate in the Fragment lifecycle.
The Fragment lifecycle is officially called "nested" but when adding new fragments I like to think of it as "playing catch up". For example, if your Activity has started (has had its onStart called), your Fragment will get a rapid succession of onAttach, onCreate, onActivityCreated, onCreateView, onStart in essence catching up to the Activity's lifecycle. You can enable debugging with FragmentManager.enableDebugLogging(true); to track the lifecycle changes - look for moveto (forward direction) and movefrom (rewinding the cycle - destroying/detaching the fragment).
That said, onCreate is kinda easy to predict - since you're probably first creating the fragment after onCreate of the Activity, it would be called almost immediately when the transaction executes.
However, if you undergo a configuration change, the Fragments are destroyed with the old Activity and then created before the new Activity (it happens in that super.onCreate(savedInstanceState) call that you have in your Activity) - onCreate of all the fragments will be called before the Activity's onCreate returns. Once it returns, the Fragments' onActivityCreated will be called and from then on the fragments and activity will move in lockstep. This is all unless you have setRetainInstance(true), of course.
I hope this clears it up. I strongly suggest enabling debugging and just playing around with the fragments to get a better feeling for their lifecycles.
If you want to pass arguments to a fragment, the new instance method is necessary.
You pass arguments this way, because fragments can be in a detached state, and when they are re added may need to be reinstantiated. Android will then call the newInstance method again with the same arguments, and your fragment will work correctly.
If you passed your arguments to an onCreate method (which is called only when the fragment is first created), then when a fragment is reinstantiated, it wouldn't get those arguments.
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.
I have this piece of code:
public static DateDialogFragment newInstance(Context context, DateDialogFragmentListener listener) {
DateDialogFragment dialog = new DateDialogFragment();
mContext = context;
mListener = listener;
/*I dont really see the purpose of the below*/
Bundle args = new Bundle();
args.putString("title", "Set Date");
dialog.setArguments(args);
return dialog;
}
Pretty self explanatory, but what I don't understand is what the point of giving it a Bundle. I never really make use of it I guess. The Android Doc's explanation of this (for Fragments) is here:
http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle)
What exactly does a construction argument mean? Since its never used, I fail to see the use of it. Any explanation much appreciated. Thanks.
It is simply a generic mechanism for you to attach data values that you might want to use to configure the Fragment or otherwise read during operation, similar to how you might pass extras in a Bundle to a new Activity via it's Intent.
I do agree, however, that since a Fragment can be instantiated using its constructor, where an Activity cannot, the usefulness of the API may seem compromised because you can just as easily configure a Fragment using setter methods and member variables inside of newInstance() before returning the instance. For example, your code could implement a method on the Fragment called setTitle() that you could call, and you wouldn't need to pass that as an argument. However, arguments do provide a nice way of storing this information as key/value data if that model fits your application.
One key distinction about the arguments of a Fragment is that they are retained as part of the saved instance state. So if your UI rotates or some other change requires the Fragment to be recreated, the arguments Bundle attached will be retained and handed back to the new instance.
HTH