I have an app that has one main Activity that swaps out numerous Fragment's. Well it doesn't matter what Fragment you are on, after low memory kills the Activity and you try to return to the app, it boots you back to the "start" Fragment that the Activity first calls. (Note: Almost all of these are actually ListFragment's)
So here are my questions:
Should I be using onSaveInstanceState() in EACH Fragment? And if so, am I saving the Data in the Fragment OR the Fragment itself? Or do you use onSaveInstanceState() only once in the Main Activity. (If this is even the course to take)
Note: I have setRetainInstance(true) but I don't think I am handling that correctly, if that is the solution. These are all put as the last line of onActivityCreated().
The answer depends a lot on how you are managing fragments.
I'll assume you are not using the Fragment backstack, and that you have called setRetainInstance(true) on EACH fragment.
You need to use a tag when you attach the fragments.
In Activity#onSaveInstanceState() you need to remember which fragments are visible.
In Activity#onCreate you need to find the existing Fragments by tag for each fragment, then create new instances of any Fragments you can't find. Now you can use the information from the saved instance state to make the appropriate Fragments visible (show or add or replace as necessary depending on how your code manages the fragments.)
Edit in response to questions/comments:
activty.getFragmentManager().findFragmentByTag(tag); finds an existing fragment
in a Fragment transaction: add(fragment, tag), replace(id, fragment, tag), etc. lets you specify the tag. You can also put it in a layout file using the attribute
class=".myFrag$tag"
The actual fragment object including its contents still exist when you use setRetainInstance.
Note: If you don't want to use tags, you may also use the fragment manager's putFragment/getFragment methods to put the fragment into the instance state bundle.
Finally you can simply let the fragment save itself by calling FragmentManager's saveFragmentInstanceState but I've had trouble using this correctly.
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.
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 have one Activity which handles 5 fragments. Every time the activity replaces each fragment onCreate and onCreateView are being called. In order to avoid this i created a HashMap where i store each fragment. Before the activity replaces a fragment it checks the hashmap if this fragment already exists. If it exists it replaces the old fragment with the instance from the map. In other case it instatiates the fragment and after that it replaces the old own.
Despite i avoid the instation of the fragment when i find it on hashmap, the onCreate and onCreateView are being called. How can i avoid this? Is there any other way to achieve my goal?
First of all there is no use for a HashMap to save the references of your Fragments. You can set a tag to a Fragment at the point you add/replace it. Have a look at the FragmentTransaction.add(int, Fragment, String) and FragmenTransaction.replace(int, Fragment, String) methods. If you provide a unique String for the tag you can retrieve the Fragment with the FragmentManager.findFragmentByTag(String) method. A container for Fragment references is redundant.
To the point:
If you use the replace/add method to show a Fragment the onCreate() and onCreateView() is called. To avoid the onCreate() call you can just attach and detach your Fragments. This way only onCreateView() will be invoked. But it's not possible to prevent the onCreateView() call.
Maybe update your question with some details what you want to achieve, because it sounds you are completely on the wrong track.
Your goal is not very cleared.
When you deal with fragment, keep in mind that your control over their life cycle is limited, you only extend (system controlled) object. You can read on the life cycle of the fragment here: Creating a Fragment.
Assuming your goal is to switch between 5 active fragments, I can think of two options:
option 1: design your fragment so they can be recreated quickly, maintain the data in some other place, and provide it to the fragment, which only do the work of display the data.
option 2: The android support library has two fragment adapters, FragmentPagerAdapter, and FragmentStatePagerAdapter. The first is an adapter which keep the fragments in memory.
How can i avoid this? Is there any other way to achieve my goal?
If you really want to avoid your activity instance to be recreated again and again just use android:launchMode="singleTop".
Example:
<activity
android:name=".YourActivity"
android:label="SomeLabel"
android:launchMode="singleTop">
</activity>
From developer docs,
If an instance of the activity already exists at the top of the target
task, the system routes the intent to that instance through a call to
its onNewIntent() method, rather than creating a new instance of the
activity.
Source: http://developer.android.com/guide/topics/manifest/activity-element.html
I'm new to android programming.
I just try to save the state of the ListView in my fragment. For that I follow headless fragments (fragment which has no UI). In this fragment, I save the data, used in the ListView, and starting the headless fragment from the main fragment (the one which has the UI).
Now I got the exception:
java.lang.IllegalStateException: Failure saving state: RetainedFragment{4161f850 #1 work} has target not in fragment manager: JobOpeningFramgent{41601c00}
As far my concern, this is happening when I'm trying to replace the fragments with another one in the DrawerLayout.
Please temme the cause of this exception, for better understanding.
Thanks.
Boopathy.
Here's a workaround:
put this in the fragment that causes the problems:
#Override
public void onSaveInstanceState(final Bundle outState) {
setTargetFragment(null, -1);
...
and remember to set it to the real target fragment when you need it.
I'm not sure what do you want to save and where do you want to save it.
The official docs state that: "A Fragment represents a behavior or a portion of user interface in an Activity."
Using a Fragment as a container of another Fragment's UI state is generally a bad idea.
If you want to persist some values throughout the activity lifecycle (that includes screen rotations) just override onSaveInstanceState method. If you want to store some variables even after activity life-time use singelton class or Preferences, and if you want to store your values even after app life-time use SharedPreferences
Please elaborate on what do you exacly want
I am using FragmentStatePagerAdapter to create view pager for a list of objects. Each page is a fragment.
However, on that page, I am also using fragments to display some other data.
I got problem when doing this. I wonder can I put fragments in fragment. Or any other solutions to work this out?
Nested fragments are not supported by current fragment implementation (it was answered by Diane, Android engineer as well here:
Nested fragments are not currently supported. Trying to put a fragment
within the UI of another fragment will result in undefined and likely
broken behavior.
But it does not mean it is not doable - it can be achieved, however requires writing some more code than just fragment. There's comment in same thread by other user:
I managed this by extending FragmentActivity, FragmentManager, and
FragmentTransaction. Basic premise is extend DeferringFragmentActivity
in my activities, providing same api so no other code changes. When I
call getFragmentManager, I get an instance that
DeferringFragmentManager, and when I call beginTransaction, I get a
DeferredTransaction. This transaction stores POJOs with the called
method and arguments. When commit is call, we look for any pending
DeferredTransactions first. Once all transactions have been committed,
we start a real transaction and run all the stored methods with args
In general - unless you are desperated, just redesign your layout.
Fragments cannot hold other fragments.
It's not supported (errors with state)
I have used Fragments inside an Fragment in a project. The way I ended up doing is adding these inside fragments via code and using the fragment attached to the activity as a "proxy" for the other, so on the oncreateview() of the fragment you instantiate the inside fragments, ondestroyview() you remove the inside fragments and so on.