I have a FragmentActivity with a ViewPager and a FragmentPagerAdapter.
Each fragment in the ViewPager needs to load data from a webservice. I've written the LoaderManager/custom Loaders to perform the webservice calls, parse the XML etc and these are working fine.
There are two possible designs for managing the loading and fragments.
1) The main activity to manages the loading on behalf of the fragments.
The fragment makes a call back to the main FragmentActivity requesting it's data. The activity checks if a loader for that data is already running, if not it creates one and performs the load.
In this case how do I tell the fragment that the data has been loaded and is ready for painting. I am using custom loaders not simplecursors so the fragment will populate its textviews etc from an object instance so it must be told when the objects have been populated.
It doesn't seem that a FragmentPagerAdapter allows you to allocate Tags or Id's to the fragments when you instantiate them in getItem. How can I find the fragment from the FragmentActivity and tell it to paint its data?
2) Each fragment manages its own loading.
The fragment initialises its own Loader, each fragment has its own onCreateLoader/onLoaderFinish etc.
The problem I've come across here is if the user pages off that page whilst the loader is running the fragment seems to be destroyed (sometimes). As a result the onLoaderFinished isn't being called and the fragment can't tell the main activity that it's finished - the main activity is controlling a progress indicator (setProgressBarIndeterminateVisibility) in an actionbar.
So what is the best design pattern when you have a FragmentPagerAdapter, a ViewPager with several pages which need their own loaders?
Another issue I've encountered is using getSupportLoaderManager to check if any loaders are running. I call this from the onLoadFinished. If no loaders running I can hide the progress indicator. However the hasRunningLoaders sometimes returns true, even though all the loaders have completed.
LoaderManager lm = getSupportLoaderManager();
// If the loader is not already running, start it...
if (! lm.hasRunningLoaders()) {
setProgressBarIndeterminateVisibility(Boolean.FALSE);
}
Many thanks for any advice or pointing me in the direction of some decent samples.
Ta
Martin.
With regard to design 1, I did something like this by maintaining a reference variable to the fragments that I create. In your FragmentPagerAdapter's getItem method, you can assign the created fragment to that variable before returning it. This will allow access to your fragments from the main activity.
I'm not sure if this is entirely correct, but I remember that when your Fragment is destroyed while a Loader is getting data in the background, the onLoaderReset method is called (instead of the onLoaderFinished). This would allow you to reset your progress indicator appropriately.
Related
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.
First I explain the structure of my app. To be precise MainActivity of my app contains Viewpager with two fragments. Each fragment contains a listview. The data inside listview is being populated from web service call.
This web service call is actually done in activity not in the fragments through AsyncTaskLoader which loads data for both listviews and saves the response in two Application variables (Lists of data for both listviews). On finish of loader , i just do viewpager.notifydatasetchanged() (which reloads the fragments , thus sets the data in listview).
As the fragments in viewpager are initialiased by viewpager's adapter no by the activity directly, I found this approach better.
Options I had, but rejected (pls correct me If I could utilise them)
Asynctask to call webservice (headache if handling rotations)
IntentService (updation of UI)
Is there any other approach to load data and handles the fragments?
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..
I have a scenario and I an not sure on what path to go.
Scenario
The app has a Home activity which displays various fragments. The data in the fragments can come either from the web or a local database and is retrieved using an asynctask.
From what I saw, I have 2 alternatives:
Put the Asynctask in parent activity and then use fragment.newInstance(parameters) to pass the result to the fragment. However, if in my asynctask I need to update the progress or some info on the fragment, each time I will have to call newInstance with the new set of parameters.
Add the fragment and put the asynctask in it, in this way when progress is needed, I can update the fragment's views, as I have access to them + when the asynctask is done, I can populate the list with the info.
What would be the correct approach ?
LE: actually for point 1 in order to update the fragment I can call fragment's public methods after I find it with findFragmentById in the parent activity
A better way if you have multiple tasks would be to use an IntentService :
http://mobile.tutsplus.com/tutorials/android/android-fundamentals-intentservice-basics/
You would have a better control to what you're requesting and what you want to cancel.
I would go with the second approach.
My primary reason though would be to avoid the issues that can happen on screen orientation change while the AsyncTask is working.
I would go with method 2, but take it a step further.
Have a separate fragment to run your async task. This way, you can handle any configuration changes (not just rotating screen) without any issues.
In another fragment, you can display the data. You can pass the data from your async task fragment via callbacks to the activity, and have the activity call a method in the display fragment to update the data.
I have one FragmentActivity that holds ViewPager in which I trough custom FragmentPagerAdapter create 4 child Fragments. Fragments contain ListView in which I show data. I make a call to the web API in FragmentActivity when the response is finished; I want to notify all fragments that data is ready and fill the adapter with that data. Note that data is not available at the time of Fragment initialization.
I could save a reference to fragment when creating it and call “myFragment.hereIsYourData(Object data)”, but the reference get changed after recreating Activity when killing app for low memory. (If I’m not mistaking + I read that keeping a reference to fragment in Activity is a bad thing)
Just for clarification I can’t use “findFragmentById(id)” because ViewPager does’t expose that. I could use the “getFragmentTag” hack explained here https://stackoverflow.com/a/9744146/1025364 but I don’t want to : D.
Then I have tried to ask for data from Fragment onResume method trough interface to Activity (like explained in Developer guide), but the data is not reedy at that moment. Should I ask for data in some sort of a loop until its ready?
What is the best approach in situation like that?