My activity uses a FragmentPagerAdapter that creates a new instance of my Fragment from the FragmentPagerAdapter.getItem() method.
Whenever I select the page containing my fragment of interest, I want to update its views because they may have been invalidated in the meantime when this page wasn't on screen.
What is the correct moment/method to do this? I've tried a few things:
MyFragment.onCreateView: this is only called the first time
PagerAdapter.onPageSelected: since the PagerAdapter creates new instances all the time, field references to Views obtained with findViewById in onCreateView are unavailable (unless they're static members I guess).
The other lifecycle events including onResume, onAttach
I found that if I pass the Activity reference to the Fragment from the onPageSelected event, and use findViewById on that to obtain new references to my Views it works, but I can't imagine there isn't a more intended way of doing this.
OverRide this method, setUserVisibleHint Set a hint to the system about whether this fragment's UI is currently visible to the user. This hint defaults to true and is persistent across fragment instance state save and restore. Whenever it is true then that means the fragment is being viewed, either from a view pager or something triggering the validation of its views.
An app may set this to false to indicate that the fragment's UI is scrolled out of visibility or is otherwise not directly visible to the user.
More info:
Related
I found a very weird behaviour of view pager. While using FragmentPagerAdapter, I observed getItem() was called in correct order i.e. position 0, position 1, position 2......
but the onCreateView() is called in opposite order, i.e. position 2, position 1, position 0. (assuming viewpager maintaining 3 offscreen pages)
What I think, that Viewpager is somehow maintaining a stack of these fragments. When it require to create views, it pop of top most active fragment from the stack and make it to call it's onCreateView().
So my question is can we control the order of calling of onCreateView()?
If not, can I order the network requests which I make when onCreateView() is called, such that Fragment 0 network requests should be executed first, then Fragment 1, then Fragment 2. But I think this would create another problem, as I'm performing UI related task after network request completed and if onCreateView() is not called till now, then UI elements might not be initialized.
So what is the best way to make these network calls to be in right sequence, i.e. for Fragment 0, then 1, then 2 and so on....
Since onCreateView() is a lifecycle method - I don't think you can "modify the order" - instead, you should conceptualize your design around the Fragment/Activity lifecycle so that you know when you should be doing what. Also in terms doing UI related tasks, you should do that in the onActivityCreated. This method is called after the onCreateView() method when the host activity is created. Activity and fragment instance have been created as well as the view hierarchy of the activity. At this point, view can be accessed with the findViewById() method.
In your case, if you want to perform some UI operations do then like this (for each Fragment:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
View rooView = getView();
//here you can do your UI related tasks
}
In terms of ordering the creation of your Fragments, please follow the discussion here because they treat a use case that relates closely to yours.
I am not sure, but I think that you can't control order by witch adapter is filling tabview. More important, you shouldn't control it. Fragments should be independent as much as possible. So, start your network requests after onCreateView is being called, In Activity store just data that you will use in all 3 fragments. If you have network request that will provide data for all three fragments, refresh fragments (using interfaces) when request is done.
Fragment transaction has method add(Fragment fragment, String tag), which does not place fragment to container, so it cannot have view. For what it can be used?
From the Android Documentation:
However, a fragment is not required to be a part of the activity layout; you may also use a fragment without its own UI as an invisible worker for the activity.
How about this purpose ?
Simple example: an Activity starts an AsyncTask, but when device rotated activity restarts, causing AsyncTask to lose connection with the UI Thread. But this Activity can hold a Fragment (invisible, with no UI at all) that can handle all the AsyncTask work. When Activity recreated the Android OS takes care reattaching the Fragment, thus no data loss will occur.
For Dialogs you don't have any container on normal app layer. It is directly added on Window with WindowManager(See WindowManager.LayoutParams for various types of layers).
DialogFragment has an API like DialogFragment.html#show(android.app.FragmentManager, java.lang.String) which corresponds to this.
You can use fragments without UI (container) as a background worker (one benefit is that you can retain it during rotations etc) and for retaining data during rotations and other changes.
Reading http://developer.android.com/guide/components/fragments.html is strongly recommended.
Example of instance retaining: https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java
Also, here are similar questions (so this questions seems to be a duplicated but cannot be flagged due to bounty):
What is the use case for a Fragment with no UI?
Android non-UI Fragment usage
As #Lucius Hipan mentions, it can be used to prevent data loss.
Almost always this king of fragments are used as Retained container ( setRetainInstance(true) called in onCreate method), then after device configuration changes (e.g. orientation changing) fragment will not be recreated but remembers previous state.
It's recommended way to use asynctask.
Here is an example:
There is login activity. The user enters their credentials and presses the Login button. After that configuration change occurs (user rotates phone). So, network task was completed, but your handlers was not listening for it now. If you show any login animation, it can be stored via savedInstance, but listeners not. And instead of creating service you can simply create new retained fragment with persistant asynctask and interface to communicate with activity.
This method is a good compromise for small projects where using bus libraries is overstatement.
By calling the method add(Fragment fragment, String tag) internally calls add(int containerId, Fragment fragment, String tag) with a 0 containerId.That will be add(0, fragment, tag).
If 0 is supplied as containerId, it will not be placed the fragment in a container.
Suppose I have a Fragment A. It has an instance variable mViewPager that points to its ViewPager.
In the onCreate(Bundle) of Fragment A, I invoke setRetainInstance(true).
Upon orientation change:
onCreateView(LayoutInflater, ViewGroup, Bundle) is called, and a new view is inflated. So, I have a new ViewPager inside the newly inflated view.
mViewPager points to the original ViewPager upon orientation change.
My question is: how do I get the new ViewPager in (1) to be associated with the retained mViewPager in (2)?
Or should I just use onSaveInstanceState(Bundle)?
As mentioned in #Selvin's comment, you should let the UI element to be recreated.
Some information which you should know:
setRetainInstance(true) should be used for non-UI Fragment only. And my personal advice would be not to consider this first, unless you are run out of option.
To properly handle a restart, it is important that your activity
restores its previous state through the normal Activity lifecycle, in
which Android calls onSaveInstanceState() before it destroys your
activity so that you can save data about the application state. You
can then restore the state during onCreate() or
onRestoreInstanceState().
You are right about using onSaveInstanceState(Bundle), in general, you should use to save your state. Please be noted that, it is the state you save, but not the UI or the whole Fragment.
For example, a state can be a count on how many times a button is clicked.
Check the link below on how to save state
http://developer.android.com/training/basics/activity-lifecycle/recreating.html#SaveState
Moreover, some UI states, e.g. text inputted in EditText are already handled in the system API. So you only need to handle states you maintained by yourself.
Edit:
If you are new to this, and do not know what you need to save and what do not, simply skip it first, and play around orientation change WITHOUT onSaveInstanceState. Then you would soon find out what is lost in the process, and that would be the state which you need to keep.
I have been struggling to find out what the correct management of Fragments within a FragmentActivity with a ViewPager is. Before I go into details, a quick summary of the issue that I am facing is the following:
I have a FragmentActivity with a ViewPager. The ViewPager uses a custom, yet very simple FragmentPagerAdapter. Each Fragment within the ViewPager comprises of an ExpandableListView. I also have an action bar button called "Refresh". For now, let's assume that the ViewPager has only one Fragment. The activity is created, and the Fragment's ExpandableListView is populated (so far so good). When the Refresh button is clicked, the handling method within the FragmentActivity iterates over the list of Fragments that are assigned to the FragmentPagerAdapter and calls refresh() on each Fragment to populate its ListView. However, when the orientation of the device changes (e.g. from portrait to landscape), the Activity is recreated and so are the fragments. Clicking the Refresh button now will iterate over non-initialised Fragments.
I know that I am being quite vague, especially without sample code, but please bear with me. I have traced the problem and method calls as follows from the start of the application/activity:
FragmentActivity.onCreate()
FragmentActivity.setContentView()
FragmentActivity.createPagerFragments() <-- this creates an ArrayList of Fragments and assignes them to a new FragmentPagerAdapter which is in turn assigned to the ViewPager.
Fragment.onAttach()
Fragment.onCreate() <-- nothing special here, just calling the super method.
Fragment.onCreateView() <-- nothing special here either, just inflating the layout
Fragment.onActivityCreated() <-- nothing here either.
<< All good, orientation changes here >>
FragmentActivity.onCreate()
Fragment.onAttach()
Fragment.onCreate()
FragmentActivity.setContentView()
FragmentActivity.createPagerFragments()
Fragment.onCreateView()
Fragment.onActivityCreated()
<< Refresh button clicked >>
FragmentActivity.refresh() <-- iterates over the newly created Fragments from #13 (not these by Android!).
<< Crash: NullPointerException for mExpandableListView in Fragment. >>
So the problem, as I see it, is as follows:
When Android re-creates the FragmentActivity and its Views after a change of screen orientation (calls #9-15 above), it creates new Fragment objects with their state restored to what the original ones were. However, these ones appear to be completely managed by the FragmentManager, and not by the FragmentPagerAdapter. In contrast, when the FragmentPagerAdapter is re-created along with the Fragments in the activity's onCreate method (see call #13) the Fragments that get assigned to the adapter never have their Fragment.onCreate() or Fragment.onCreateView() methods called at all. So when the refresh() method is called (see #17) the method iterates over these Fragments that have not been initialised. Therefore, when they try to populate the ExpandableListView, the view's instance variable is NULL. This is to be expected as the instance variable is only assigned in the Fragment.onCreateView() method that never gets called on these Fragments.
So my question is: how does one properly make re-use of the re-recreated (by Android) Fragments after the screen orientation has changed in order to avoid creating new ones that don't get initialised? I need to have a valid reference to them, in order to call their refresh() method that populates them on-demand. Ideally, they should also be assigned to the FragmentPagerAdapter as well.
I hope I have been clear in describing the issue, and the reason that I have not provided sample code is because the problem (as can be seen) is not from the code itself but from a rather incorrect (seemigly) re-creation of Fragments rather than re-use. But if needed, I can give you sample code, I just through this way would be clearer.
Thank you!
It's lot to read, but after reading just introduction and the question and having experience with FragmentStatePagerAdapter, which is similar to FragmentPagerAdapter I can tell you that:
After rotation your adapter will AUTOMAGICALLY attach old fragments. So it seems that although activity creating adapter is being recreated, FragmentManager, which is global and it's instance preserve activity's recreation will detect that new FragmentStatePagerAdapter is combined with the same ViewPager and is asking for the same Fragments and will simply fetch them from Fragment's BackStack.
You as designer of Fragments can notice this behavior by continues invocation of Fragment.onAttach() and Fragment.onDetach(). When onAttach() occurs it's either creation of your Fragment or reusing it after rotation. You should be able to distinguish that Fragment was rotated with use of callback onRestoreRnstanceState().
You will see in your logs many onCreate() and other states logs simultaneously, because FragmentStatePagerAdapter always fetches/creates min 3 Fragments (except if you set that they are only 2 or 1), so also after screen rotation 3 fragments will be reattached from backstack.
I hope that it helped.
I believe that this question, about retrieving the current fragment from a ViewPager, will help you. As already pointed out, fragments are managed by the Fragment(State)PagerAdapter and NOT Activity's or Fragment's lifecycle.
The first time the activity is created, fragments are returned by the getItem method. This method is called only once per fragment, even if the activity gets recreated.
Subsequent times, the fragments are returned by the instantiateItem method. Most probably, this is the place, where you need to get hold of your fragments and call their refresh methods.
How about adding this to Activity Tag in your manifest:
android:configChanges="orientation"
or this for API 13 or higher
android:configChanges="orientation|screenSize"
so it won't recreate your fragments when it changes orientation..
Short version: I would like to know how i can recreate a fragment state (e.g. after screen-rotation) when this fragment contains a reference to an object that cannot be serialized or duplicated and needs to stay in memory.
Long version:
In my app i use a ViewPager connected to a custom FragmentPagerAdapter that instantiates a number of fragments which display data (a schedule) contained in a ListView. All this is contained inside a parent fragment. When instantiated by the adapter, each page fragments is passed a reference to one single object (the "ScheduleManager") that does several things:
Contains the data to be displayed
Holds a reference to a Context object (in order to access SharedPreferences)
Holds a reference to the parent fragments LoaderManager so it can reload the data
Implements OnClick- and ActionMode callback listeners (to be able to create and handle an action mode that works across all pages; Page Fragments add the object as a listener to their ListViews)
Defines a callback interface to notify listeners of state changes or when data is reloaded (Page Fragments register themselves as listeners).
Essentially, the ScheduleManager holds everything together and implements the main logic of this ("schedule") part of my app, i.e. loading and providing the data, and the means to modify and relaod it via an ActionMode. I don't know whether this is good design...
My question is how am I supposed restore the Fragments instance state under these circumstances? I cannot serialize the ScheduleManager to a bundle, since it would loose its references to the Context and the LoaderManager (otherwide, of course I would use setArguments / getArguments). Also, all the page fragments must have a reference to the same instance of the ScheduleManager, otherwise the shared action mode won't work. Aside from that I don't want to duplicate the entire schedule data each time a fragment is restored. I want to keep this object in memory and let the page fragments reclaim a reference to it when they are restored.
I guess I could let the containing activity hold the ScheduleManager and have the PageFragment query it for a reference. But I would prefer to keep the everything inside the parent fragment self-contained and modular if this is possible (there can be different schedules for different items). While writing this though I get the feeling there will be no way around this.
Of course, when the parent fragment is recreated it can also recreate the ScheduleManager and feed to it the references it needs (Context + LoaderManager). The problem is there can be no more than one instance of ScheduleManager for each instance of the parent fragment, so how to make the nested page fragments reconnect to it.
Here is what I ended up doing:
public void onCreate(Bundle savedInstanceState)
{
Fragment fragment = this.getActivity().getFragmentManager().findFragmentByTag("SCHEDULE");
if (fragment instanceof ScheduleMasterFragment)
{
ScheduleMasterFragment master = (ScheduleMasterFragment) fragment;
this.scheduleManager = master.getScheduleManager();
}
else
{
throw new RuntimeException("SchedulePageFragment must be " +
"the child of a ScheduleMasterFragment with Tag set to 'schedule'");
}
super.onCreate(savedInstanceState);
}
The ScheduleManager is the reference I wanted to keep. At least this way I keep all the code inside the fragment itself, except for getScheduleManager in ScheduleMasterFragment which makes sense because the ScheduleMasterFragment somehow is the owner of the ScheduleManager instance.
Any better solutions are still welcome...