I know the recommended way of passing parameters to Fragments is to use a static method and create a bundle and call setArguments()/getArguments() on the Fragment:
public static MyFragment newInstance(int arg1, int arg2) {
Bundle b = new Bundle();
b.putInt("key1", arg1);
b.putInt("key2", arg2);
MyFragment frag = new MyFragment();
frag.setArguments(b);
}
public View onCreateView(....) {
Bundle b = getArguments();
memberVar1 = b.getInt("key1");
memberVar2 = b.getInt("key2");
.....
}
Correct me if I'm wrong, but it seems like the following approach works as well, which doesn't require calling getArguments() later on:
#Override
public void setArguments(Bundle args) {
memberVar1 = args.getInt("key1");
memberVar2 = args.getInt("key2");
}
This is based on the way that setArguments() is implemented in the Fragment class. If this allows mArguments to be accessed in the event of Fragment recreation, then shouldn't the same apply to other variables set within the setArguments() call?
659 public void setArguments(Bundle args) {
660 if (mIndex >= 0) {
661 throw new IllegalStateException("Fragment already active");
662 }
663 mArguments = args;
664 }
Aside from convention, does either scenario have an advantage over the other?
There shouldn't be a reason for you to have to override the setArguments(...) method... Here is a possible approach for you to take to 'cleanup' the arguments passing.
Create a new class, something like MyFragmentArguments.java.
public class MyFragmentArguments {
public long keyOne;
public long keyTwo;
public MyFragmentArguments(long keyOne, long keyTwo) {
this.keyOne = keyOne;
this.keyTwo = keyTwo;
}
public MyFragmentArguments(Bundle bundle) {
this.keyOne = bundle.getLong("KEY_ONE");
this.keyTwo = bundle.getLong("KEY_TWO");
}
public Bundle toBundle() {
Bundle args = new Bundle();
args.putLong("KEY_ONE", keyOne);
args.putLong("KEY_TWO", keyTwo);
return args;
}
}
Then, you can (very cleanly) call:
final MyFragmentArguments arguments = new MyFragmentArguments (getArguments());
if(arguments != null) {
// do something with arguments.keyOne & arguments.keyTwo
}
This way, you avoid unintended side effects of overriding something that's built into the android OS - something that you should strongly question whenever you do.
Once you call the super implementation of setArguments you maintain the integrity of the pattern.
Related
Hi I'm learning now about Fragments and I have some doubts.
V1:
So the correct way to create a new Fragment should be with "newInstance" instead of a constructor and would be something like this:
Fragment fragment = MyFragment.newInstance(variable);
And the code on the class "MyFragment" should be this:
private static final String ARG_PARAM1 = "param1";
private int variable;
public MyFragment() {
//Empty constructor
}
New instance will receive the param and will put them on a Bundle and set the argument for the class
public static mi_fragment newInstance(Int param1) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putInt(ARG_PARAM1, param1);
fragment.setArguments(args);
return fragment;
}
Then after setting it the method onCreate will pick up the argument:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
variable = getArguments().getInt(ARG_PARAM1);
}
V2:
But I still can't see the problem with using the constructor and setting the arguments programatically:
Bundle bundle = new Bundle();
bundle.putInt("key", variable);
Fragment fragment = new MyFragment();
fragment.setArguments(bundle);
Then I pick up the argument on the method onCreate:
public void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle bundle = this.getArguments();
if(bundle!=null){
variable = getArguments().getInt("key");
}
}
So I decided to search on the internet about this and I found a good answer but I still can't seem to understand it or when you could use it.
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.
This Bundle will be available even if the Fragment is somehow recreated by Android.
So my conclusion is that you keep the bundle alive and using the newInstance could make you return to the last fragment for example. But probably I'm wrong so I need help.
Question: What's the main difference between both and how do you benefit from newInstance ? An example.
I have a StudentList fragment, which has a List; the Student class implements Parcelable; clicking an item in the StudentList fragment invokes the following StudentFragment:
public static StudentFragment newInstance(Student student_) {
StudentFragment fragment = new StudentFragment();
Bundle args = new Bundle();
args.putParcelable("STUDENT", student_);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mStudent = args.getParcelable("STUDENT");
}
}
private void setStudentName(String newName_) {
mStudent.setName(newName_);
}
This fragment is instantiated from another "StudentList" fragment, which has a List; an object from his list is provided as the parameter to StudentFragment.newInstance().
I was surprised to see that any changes to mStudent in the "StudentFragment" automatically get reflected on the corresponding object. On checking further in the onCreate method of StudentFragment, I found that the mStudent object reference is the same as the reference of the object that was passed to newInstance.
When I stepped through the code, I found that the Student.writeToParcel is never called.
How is this possible? Shouldn't I get a NEW object reference when I call mStudent = args.getParcelable("STUDENT") ?
Does the "arguments" bundle or the Parcelable interface preserve some link to the object reference, and use the parcel/unparceling as a last resort?
This post states that there's no guarantee that writing/reading to/from a Bundle will cause parceling/unparceling. Moreover it states that one shouldn't assume either behavior. This is probably why I keep getting back the exact same reference in onCreate.
Im trying to pass a variable back to a previous Fragment, similar to startActivityForResult but with Fragments, Is this possible?
The code I am using To call a Fragment is this:
FragmentFullScreen fragment = new FragmentFullScreen();
Bundle args = new Bundle();
args.putParcelable(ARG_VIDEO_SELECTED, mVideoSelected);
fragment.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.container, FragmentFullScreen.newInstance(mVideoSelected))
.addToBackStack("FragmentDetails")
.commit();
And then I using popBackStack to go to the previous Fragment:
getFragmentManager().popBackStack();
And there is when I want to update a Variable from the Previous Fragment.
You may implement Observable object in your 1st fragment and create Observer, which may be shared through Application class or Activity.
Then in your 2nd fragment you get this Observer from Application and update data.
If you 1st fragment still exists your variable will be passed there.
http://developer.android.com/reference/java/util/Observer.html
http://developer.android.com/reference/java/util/Observable.html
Communication between fragments should be done via the activity. And to achieve this communication between fragment and activity, this is the best way http://developer.android.com/guide/topics/fundamentals/fragments.html#CommunicatingWithActivity.
Bundle args = new Bundle();
args.putParcelable(ARG_VIDEO_SELECTED, mVideoSelected);
fragment.setArguments(args);
Put this variable in 'args' (just a key-value map). Just like:
args.putExtra("id", id);
args.putExtra("parcelable", p)
A cool way to do this is to create a singleton class :
public class Singleton {
private static Singleton mInstance = null;
private int mVar;
private Singleton() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
public static getInstance () {
return mInstance;
}
public int getVar () {
return mVar;
}
public void setVar (int val) {
mVar = val;
}
}
I'm sure this question has been asked lots of times but didn't find any useful answer.
I'm trying to implement Fragment which can be use in my app for X times (yes yes..even 100 and more..)
I want to create my fragment only once and in other times to pass bundle and make lifecycle do what he needs to do with the bundle data.
So far so good? So what i've found is a nice implementation from google document:
public static class MyFragment extends Fragment {
public MyFragment() { } // Required empty constructor
public static MyFragment newInstance(String foo, int bar) {
MyFragment f = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_FOO, foo);
args.putInt(ARG_BAR, bar);
f.setArguments(args);
return f;
}
You can then access this data at a later point:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
// Use initialisation data
}
}
So what's wrong here? First, the Fragment MUST be an inner class. Second, if the class is static, and the function newInstance(...) is static, why they always create new instance without checking (By keeping a static member of the fragment) if it's not null like a Singleton ?
Assuming my fragment will be added many times to my fragment manager, can I use my fragment class as full Singleton? it means that each time I'll call:
public class MyFragment extends Fragment {
private static MyFragment sInstance;
public MyFragment() { } // Required empty constructor
public static MyFragment newInstance(String foo, int bar) {
if (sInstance==null) {
sInstance = new MyFragment();
}
Bundle args = new Bundle();
args.putString(ARG_FOO, foo);
args.putInt(ARG_BAR, bar);
f.setArguments(args);
return f;
}
}
Than use the new/exist fragment to make the transaction.
Hope I make myself clear, and didn't write some nonsense here :)
Thanks very much for your help
Is it always necessary to initLoader from onCreate in a Fragment? What if critical arguments for the loader are dependent on the results of another loader?
i.e. You have 2 loaders: LoaderA, and LoaderB. LoaderB needs the result from LoaderA to run. Both LoaderA and LoaderB are initialized in onCreate of a fragment, but LoaderB is given no arguments so that it intentionally fails.
Once LoaderA finishes, LoaderB is restarted with new arguments so that it can perform its desired request.
Loader initialization in fragment:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(LOADER_A, new Bundle(), this);
getLoaderManager().initLoader(LOADER_B, null, mLoaderBCallback);
}
Call backs for LOADER_A in fragment:
#Override
public Loader<MyResultObject> onCreateLoader(int id, Bundle args) {
return new LoaderA(getActivity(), args);
}
#Override
public void onLoadFinished(Loader<MyResultObject> loader, final MyResultObject result) {
if (result != null) {
Bundle args = new Bundle();
args.putInt("id", result.getId());
getLoaderManager().restartLoader(LOADER_B, args, mLoaderBCallback);
}
}
Definition of mLoaderBCallback in fragment:
private LoaderBCallback mLoaderBCallback = new LoaderBCallback();
(The implementation of LoaderBCallback is not important, its just the standard LoaderCallbacks interface that creates an instance of LoaderB and handles when the loader is finished.)
LoaderB class (please excuse any potential compiler errors with this class definition, its just an example):
public class LoaderB<List<AnotherResultObject>> extends AsyncTaskLoader<List<AnotherResultObject>> {
private Bundle mArgs;
public LoaderB(Context context, Bundle args) {
super(context);
mArgs = args;
}
#Override
public List<AnotherResultObject> loadInBackground() {
if (mArgs == null) {
// bail out, no arguments.
return null;
}
// do network request with mArgs here
return MyStaticClass.performAwesomeNetworkRequest(mArgs);
}
// boiler plate AsyncTaskLoader stuff here
......
}
Is there a better way? Can we do without the initLoader for LoaderB?
Edit: I am under the impression that loaders ALWAYS have to be initialized in onCreate, so that they can handle configuration changes. This may be true ONLY for loaders in Activities . Do loaders created in Fragments get managed no matter where they are initialized?
You can init a loader anywhere in your code.
In your case you should replace your restartLoader in onLoadFinished with initLoader. Just remove the initLoader from your onActivityCreated for LOADER_B
Also, you should check the ID of the loader in onLoadFinished so you know which loader finished.
edit: you are using a separate listener for the LOADER_B callback so my ID checking point kinda gets defeated there.. but at any rate.. you can combine them into one if you want
#Override
public void onLoadFinished(Loader<MyResultObject> loader, final MyResultObject result) {
switch (loader.getId())
{
case LOADER_A:
if (result != null) {
Bundle args = new Bundle();
args.putInt("id", result.getId());
// i put "this" as the callback listener. you can use your custom one here if you want
getLoaderManager().initLoader(LOADER_B, args, this);
}
break;
case LOADER_B:
//do whatever
break;
}