Avoid fragment recreation with hashmap - android

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

Related

FragmentManager.put/getFragment vs findFragmentByTag

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.

Click Events inside fragment

I have a dashboard that is a fragment. Everytime I click a button, the dashboard is replaced by another fragment.
The click listener is implemented inside the dashboard fragment class. But I read somewhere that the better way to do it is to make the listeners inside the activity. Is it true? Why?
If yes, I can change it, i only have to copy the method in dashboard fragment to the activity, and make use of XML onClick feature.
I honestly can't think of a reason for declaring an onClick listener for a fragment in the activity.
First, fragments are suppose to be modular. Maybe you use it with this activity or that one. Putting the onClicks in the activity hardcodes a relationship between the two. Your activity is searching for the fragment, which isn't always there, and your fragment can't work except in that activity.
Second, where you declare your on click determines where it's implicit reference will be to. If you declare it in the activity, it can call activity functions, but It has no idea which fragment it came from. How does it reference fragment functions / data? Sure there's elaborate workarounds but why?
On the other hand, if you put it in the fragment, it can call the fragment functions. and it has the same life-cycle as the fragment (being attached to a fragment view), so the implicit reference isn't going to create a memory leak (by itself anyways). And if you want to call the activity, just use getActivity and cast it to your interface or subclass.

Android Lifecycle management of Fragments within ViewPager and FragmentPagerAdapter

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..

Saving ListData Objects with onSavedInstanceState

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.

Use fragments in fragment

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.

Categories

Resources