More to the point, in onCreate/onCreateView I am already calling FragmentManager.findFragmentByTag() to lookup any existing instance of my fragment, and it seems to find it.
So what is the point of putFragment/getFragment? Does it save something extra or cause additional lifecycle stuff to happen? Is it just an alternative to findFragmentByTag() that does more or less the same thing? Because it seems to me that the fragment is being automatically saved for me without needing to use FragmentManager.putFragment().
So what is the point of putFragment/getFragment?
According to the current implementation, what putFragment(Bundle bundle, String key, Fragment fragment) do is put the index of a fragment into the bundle with the parameter key. And then getFragment(Bundle bundle, String key) get the fragment at the same index which can be retrieved from the bundle with the same key. A Fragment has its index in the FragmentManager only after it is added to it, so putFragment() can be called on a Fragment only after it is added.
Does it save something extra or cause additional lifecycle stuff to
happen?
It save the index of a Fragment only, no more things else, nor do it cause any additional lifecycle stuff.
Is it just an alternative to findFragmentByTag() that does more or
less the same thing?
Yes, I thik so.
According to the current implementation, what putFragment/getFragment does can be achieved with findFragmentByTag() too. But the function of putFragment/getFragment are quite limited because you can't use them without the bundle parameter, means you must call putFragment() in onSaveInstanceState().
Seems like putFragment/getFragment its just a safe way of storing fragments and its states inside fragment manager without displaying it.
For example you have 2 fragments that stored in your activity fields. You displaying one of them and than replace it another after that you change orientation of screen. Fields in your activity are reinited but fragment that currently displayed saved its state and other don't. But if you store fragments inside fragment manager you will have two fragments with actual states.
I am using the Fragment class with the Android support library v7. In my Activity's onCreate() method I create a bunch of fragments and store them into properties of my activity.
this.firstFragment = new FirstFragment();
this.secondFragment = new SecondFragment();
// and so on
I use the navigation drawer pattern to switch between the fragments of my app. To change the active fragment I use following code.
// Replace the current content of the fragment holder with the selected section fragment.
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.container, selectedFragment).commit();
This results in a call to onDestroy() of the removed fragment. Is it safe to reuse the fragment after it's onDestroy() has been called, or should I recreate the fragment every time it is shown to the user.
This is a question of time vs. memory consumption, as at least one of the fragments needs some time to get created.
onDestroy functionality is to destroy all the used variables and consumed memory. All those will be flagged as a Dummy data to enable the garbage collector to remove them from the memory whenever it is needed.
Calling the Fragment again after calling onDestroy will pass through the lifecycle again from the beginning through onCreate and all the variables and local object will be re-initialized again.
Is it Safe? Yes, it is safe.
You are thinking deeper to handle the lifeCycle of the Fragment that is already being handled by the OS.
If you want to prevent the Fragment from being destroyed you can create it as a static object.
The Fragments developer guide seems to tell no, according to the lifecycle diagram.
If you want to keep your fragment for a later use, i suggest you use FragmentTransaction.detach(Fragment) or FragmentTransaction.hide(Fragment) instead of FragmentTransaction.remove(Fragment).
This is not going to be a straightforward question. I'll explain myself as best as I can, so this might be a little bit long.
Explanation
Recently, I've been fighting a lot with Fragments and ViewPager.
What I found out, is that ViewPager, holds as many fragments as you want. When it's instantiated for first time, depending on the type ( FragmentStatePagerAdapter vs FragmentPagerAdapter Difference between FragmentPagerAdapter and FragmentStatePagerAdapter), it's going to create all Fragments or just the "needed" ones.
In those fragments, you're free to use methods like getActivity() which basically is the context used for creating Views, finding Views, displaying dialogs...
However, as soon as you leave the fragment, the getActivity() starts returning null, because the fragment is dettached from the Activity. Sounds logic.
But, what sounds illogical to me, hence im asking here, is that when you go back to the fragment page (or close enough), it's again triggering the base methods, like onAttach.
As soon as you've onAttach, you can keep the context provided by Activity, but if you try to do something like the following (pseudo-code):
class MyFragment extends Fragment
{
private Context context;
private doThingsAfterAttach()
{
getActivity(); //this is null.
null != context; //TRUE
}
#Override
public onAttach( Activity activity )
{
context = activity;
doThingsAfterAttach();
}
}
What you can see, is that getActivity() is returning null, even though you're calling this method after onAttach is triggered.
If you decide to cast the kept context to an Activity, to perform findViewById tasks, you will see that the view you're trying to find, is null, which means that couldn't be found / doesn't exist.
Issue
Imagine you have a ViewPager with 5 tabs (fragments).
When a task is performed on ANY of those 5 tabs, you want to notify the "holder" activity, in order to notify all fragments that should update it's content, as something has changed.
But when you notify them, if they have to change layout, they can't do it, because as soon as you try to findViewById, two things can happen: getActivity() is returning null, hence you can't get Views, and if you cast context to activity, it won't return any View when you search for any.
What scares me the most, is that when you rotate the device, acts like leaving the fragment page and going back; "loses" activity.
Real question
So what I'm looking for, is an answer which explains me what's happening internally, so I can find the appropriate code to handle those situations.
There's not much code I can provide, because it would be useless. Whoever uses ViewPager with Fragments, probably handled with those things, so you will understand me.
Thank you, and here I am ready to answer your questions.
Thanks.
note: This is a lengthy and probably boring post
Your problem with the ViewPager is not new. Don't feel bad. We've all went through what appeared to be a magic solution to a new paradigm (and I will put new in Italics because it wasn't necessarily new at the time). With the added support for Fragments (through the State and Stateless adapters that you mention) things quickly became more interesting.
I'm not going to go into the details whether the adapters are good or annoying (you can draw your own conclusions) or why the adapters (and the widget itself) lack very basic stuff that nobody understands what where the Android developers thinking when they exposed the public API for these.
Instead, I'm going to help you manage Activity/Fragment communication the way -I think- everybody does.
What is happening with Fragments in a ViewPager?
The Activity/Fragment concept is -in my humble opinion- horrible and it was a hack. But an effective one, since it quickly proved that it worked, to the point where the Android team decided to start adding Fragment support to more and more stuff. Even nested Fragments, yes Fragments inside Fragments! And since Android developers (less and less but still very often) have to deal with old versions of the platform, then all this support was initially added to the Support Library, and it never moved out of it.
But let's get back on topic. The Fragment lifecycle is somewhat confusing (in part thanks to the poor naming of some lifecycle methods and the order they are -not- guaranteed to occur). Some things are obscurely hidden behind unfriendly callbacks (TreeLayout anyone?).
So… the ViewPager needs an adapter. The adapter is in charge of supplying the ViewPager with its views. So while the ViewPager is a View that understands what a touch, drag, fling, draw, measure, etc. is, it really expects an Adapter to provide the data to display. (This is a huge simplification).
And here we have Two types of adapters which know how to deal with Fragments. One maintains a state and the other doesn't. Meaning one doesn't really release anything (FragmentPagerAdapter) and one does indeed release its fragments (FragmentStatePagerAdapter)… but wait… what is "release" in this context?
Turns out that Fragments don't really exist in a free world, they are dominated by a FragmentManager, who makes sure that they are not late and decides when to release them. This is a rule that not even the Adapters can override! So they must report to this FragmentManager anyway. You can tell that the adapter must talk to this FragmentManager because it must receive one in the Constructor!
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
Who is this FragmentManager anyway?
Well, the documentation is not very friendly, it just says:
Interface for interacting with Fragment objects inside of an Activity
Ok, thank you Mr. obvious!
In practice, the FragmentManager is a static class, you don't create it, you just get it from an Activity.
Now think of the FragmentManager of a controller who keeps a reference to your fragments, even long after you've left them. It knows how to bring them back, it helps them save their state when configuration changes (rotations, for example), it keeps a pile/stack of them, knowing in which order they can be popped back to life and it does all this, sporting FragmentTransactions that must be committed, much like a relational database transaction.
Most of this complexity (and I'm sure they had their reasons) happens behind the scenes, so you don't really have to worry too much about it.
Can we go back to the original question now?
Yes yes… it's a long subject as you can see, but let's get to the point, because I have to go back to work…
When the Fragment gets out of view, the adapter secretly tells the FragmentManager: "Yo dawg, this Fragment is no longer needed here -for now-". (It may not use that phrase but something like that). For a more detailed response, you can actually look at the more or less recent code of the FragmentStatePagerAdapter and learn a lot from it.
Look how it has a list of Fragments and SavedStates:
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
and a reference to a FragmentManager:
private final FragmentManager mFragmentManager;
Not surprisingly, FragmentPagerAdapter doesn't have a list of Fragments nor a list of saved States, it simply lets the FragmentManager do its job.
So let's first look at the "State" pager adapter to see what's doing when it's time to Destroy a Fragment…
With Google's permission, let's look at the source code for destroyItem():
1 #Override
2 public void destroyItem(ViewGroup container, int position, Object object) {
3 Fragment fragment = (Fragment)object;
4 if (mCurTransaction == null) {
5 mCurTransaction = mFragmentManager.beginTransaction();
6 }
7 if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
8 + " v=" + ((Fragment)object).getView());
9 while (mSavedState.size() <= position) {
10 mSavedState.add(null);
11 }
12 mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
13 mFragments.set(position, null);
14 mCurTransaction.remove(fragment);
15 }
Line 4 starts a FragmentTransaction if one hasn't already been started.
Lines 9-11 pads out the mSavedState array with null entries until it’s at least the size of the index of the fragment we’re removing.
Line 12 saves the state of the Fragment being removed (so it can be restored in the future if needed).
Line 13 is effectively removing the Fragment reference…
Line 14 adds this to the Fragment's Manager transaction so the FragmentManager knows what to do.
(Note: there's an obscure bug in the ViewPager when you add/change/remove Fragments dynamically, I'll link to the problem/solution at the end).
When you add a Fragment to the ViewPager, the process is relatively similar (see instantiateItem() method…
It first checks if the object is already instantiated, then it returns it immediately.
If the Fragment is not there, one is created…
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
Remember you extend this Adapter and create your own getItem() method, this is where it gets called. You are giving the Adapter a newly created Fragment.
Next the Adapter checks for the savedState of the fragment to see if it can find one (and here it makes a mistake) (see link at the end)…
Finally it proceeds to add the newly received Fragment:
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
It must add null padding for the array to be the exact size, and also uses the FragmentTransaction of course.
The moral of the story so far is that the Adapter keeps its own collection of stuff but it keeps the boss (a.k.a.: FragmentManager) happy by letting him know he's in control.
For reference, the support v13 versions are pretty much the same, but have references to the non support version of Fragment, FragmentManager, FragmentTransaction, etc.
So if the Adapters either keep a list and ask the FragmentManager (through a FragmentTransaction) or just use the FragmentManager, what does the FragmentManger do?!
This is lot more "complicated" but the FragmentManager implementation has a list of Added and Active fragments (along with a myriad of other collections and data structures).
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
^ That's coming from the FragmentManagerImpl class!
So I won't go into details about the FragmentManager (you can find its source code here), because it's a really big class and it uses transactions for everything and it's super messy. It keeps a state machine about each fragment (created, initializing, etc.). The interesting method is perhaps moveToState() and this is where the Fragment lifecycle callbacks are made, so take a look at the source code to see what's going on.
Also take a look at the removeFragment() method there, which ends up calling the moveToState() method in the end.
Enough with all this… when can I call getActivity() and not get null in my Fragments?
Ok, so you provided an example of what you wanted to do.
You have ViewPager with 5 Fragments (for example) and you want to notify them that something has happened so they can do something about it. A lot of somethings.
I'd say that this is a typical Observer pattern + ViewPager.OnPageChangeListener().
The observer pattern is simple, you create an Interface like:
public interface FragmentInterface {
void onBecameVisible();
}
Your Fragments implement this interface…
public Fragment YourFragment implements FragmentInterface {
and then you have a:
#Override
public void onBecameVisible() {
if (getActivity() != null && !getActivity().isFinishing()) {
// do something with your Activity -which should also implement an interface!-
}
}
And who calls onBecameVisible?
Your ViewPager.OnPageChangeListener() in your Activity:
public void onPageSelected(final int i) {
FragmentInterface fragment = (FragmentInterface) mPagerAdapter.instantiateItem(mViewPager, i);
if (fragment != null) {
fragment.onBecameVisible();
}
}
So now the Activity can reliably tell the Fragment that it has become visible.
Now… if you want to tell the OTHER Fragments, then you have to have a list of "listeners" (Observer pattern) but you must understand that according to what we've seen about the Adapters and the Fragment Manager, Fragments may be detached (even tho they may not necessarily be destroyed). The fact that they are not visible, could mean they are prone to have their view hierarchy destroyed (so changing it at that point would not be possible).
Your best bet is to make sure your onCreateView is capable of asking which view should be displayed based upon your business logic.
The best way to understand the lifecycle is to add a Log to EACH of the Fragment lifecycle (onStart, Stop, Pause, Resume, Detach, Destroy, ActivityCreated, etc…) and see how they react when you move through the ViewPager.
I hope this lengthy post gives you an idea about what's the ViewPager doing with your Fragments. Let us know if you have more specific questions or if I haven't really helped you :)
Regarding the BUG I mentioned earlier, take a look at this post which talks about a bug in (and a fix for) the way FragmentStatePagerAdapter handles fragment restoration.
I think getActivity() return null onAttach() because you haven't called it's super in other words it didn't get the chance to set it as its activity . Also you don't need to call getActivity() to find views, you can keep their references on onCreateView() and update them on onStart() if there should be any change in their values.
Also keeping the activity context as global will prevent the fragment from being garbage collected as long as activity lives. So release the reference on onDetach().
If your problem is that you want to notify the Activity, simply pass the Activity as "listener" to the fragment. Remember to pass it as WeakReg tough to try to avoid leak issues.
If my Fragment implements an interface and another class, say the activity holds a reference to the fragment.
IFragmentInterface myFragmentReference;
At what point in the fragments lifecycle is it no longer available? And can this be detected by checking if the myFragmentReference is null? (I am aware there is a FragmentManager class).
I'm not exactly sure when the reference becomes invalid, but looking at the Android developer page on Fragments (http://developer.android.com/guide/components/fragments.html) it would appear to be in onDetach.
You can't detect this by checking if myFragmentReference is null, because the reference isn't null... it's just pointing to a Fragment that no longer exists.
The best way to handle this is to use something like the following whenever you need to do something with the fragment:
FragmentManager fm = getFragmentManager();
IFragmentInterface myFragmentReference = (IFragmentInterface) fm.findFragmentByTag(getTag());
You'll need to set a tag when adding the fragment to use findFragmentByTag().
The Fragment itself is not usable as a Fragment anymore after onDestroy() is called. Your reference stays valid as long as you keep it. You can access all the methods from your interface, but you can just not use it as a Fragment anymore.
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.