What happens to a Fragment once it's removed or replaced? - android

Android documentation doesn't seem to have much on it, other than stating that the fragment is removed once the Transaction is committed. Is the Fragment just gone? Like in a metaphysical sense? Or does it exist somewhere still, able to be called when needed? If so, how would you call it?
As a more pragmatic question, if you have an Activity that contains one fragment View and multiple Fragments that eventually go into that View, is there a way to reference Fragments other than the most recent one on the back stack (for question purposes, assuming they're all placed on there)

When you remove or detach fragment it doesn't removed from the project, it can be attch with another activity.
But once you pop fragment from the stack and destroy, it can't be revoked. Because when you destroy any fragments, a cleanup function is being called which cleans every part of that fragment.

Related

Why does clearFindViewByIdCache() have to be called in onDestroyView()?

I'm trying to understand Kotlin Android extensions and this article says says:
When asked for a view, it will try to find it in the cache. If it’s not there, it will find it and add it to the cache. Pretty simple indeed.
Besides, it adds a function to clear the cache: clearFindViewByIdCache. You can use it for instance if you have to rebuild the view, as the old views won't be valid anymore.
Why won't the old views be valid anymore once you rebuild the view after onDestroy()? The view references are still going to be there once you rebuild the view, for example in OnActivityCreated().
The important note here is that Fragments usually have a longer lifecycle then their views. Let's consider the following flow:
You created Fragment A and placed it into a container
Fragment A view got created (this onCreateView + onViewCreated)
You created Fragment B and replace Fragment A with the new fragment in the container + keep back stack
Fragment A views will be destroyed at this moment, but the fragment will be kept inside of FragmentManager.
You back press and Fragment B got fully destroyed because you logically leaving that. At this moment system replaces it back with Fragment A and it creates a new view again.
So here are a few important notes:
At step #4 if you keep any links to subviews, then your memory just leaks because all of them already got detached out of your view hierarchy and can't be used anymore. Basically, you still hold all those views in memory, even though they are not in use anymore. Usually, ppl don't notice that and all views will get rebound again with new onViewCreated (and findViewById), but kotlin extensions keep them in a cache and basically do not execute this findViewById again, so you can get old (read as dead) views that are not those views that just got created.
If you use ButterKnife at the moment for instance, they have description about that as well in Binding Reset section. (http://jakewharton.github.io/butterknife/#reset).
Last important note is that Kotlin extensions do this job for you, so you don't really need to call it inside of onDestroyView, you just use it and it works (as magic...)

When (& where) are my fragments created

I'm using the viewpagerindicator library (http://viewpagerindicator.com/) to create some sort of wizard for my android app. It works fine and does exactly what I want.
I would like to "extend" the functionality a bit by having "previous"/"next" buttons in my ActionBar - pretty much as in Android's "Done Bar" tutorial - to step through the wizard. Works like a charm, too.
HOWEVER:
I would like to display information about the "next" & "previous" fragment in the ActionBar's buttons. Information I pass to the fragments that live in the ViewPager at the time of their "creation" (actually at the time of their object instantiation - using the classical "newInstance(...)" approach to create the instance of my fragment, store the parameters in a Bundle and extract them in the fragment's "onCreate" method). The same way the template does it, when you create a new fragment for your project.
So, this information is the thing I actually want to display in my wizards button to know what fragment is next and which was last.
The type of this information is not important for my problem. It could be a String or an icon or an int or ... anything else you want.
However, wherever I've tried to access my fragments data, the fragment has not yet been fully initialized (meaning its "onCreate" method has not been called yet).
I've tried it in the host fragment's "onViewCreated" method, because I thought that's where all its subviews should be initialized already (at least their "onCreate" method should have been called, I thought), but it seems that this is handled differently for ViewPager to retain only the number of fragments in memory that was set by setOffscreenPageLimit.
So, what I'm looking for (and probably just missing) is the correct callback method here. One that is called when the ViewPager's next Fragments have been loaded and initialized. If such a callback exists, I could place my little piece of code there to update the text in my "previous"/"next" buttons within the ActionBar.
Any help, comments, ideas are highly appreciated. If needed, I can also try to attach some code sample to better explain my setup, but I think it should be easy enough to understand what my problem is.
Thanks in advance!
P.S.: I also tried to do this by using EventBus to send "onFragmentInitialized" messages from my fragments within in the ViewPager and the hosting fragment. It actually worked, but it does not seems the proper way to do this.
When a Fragment's onCreate Method is called, its already preparing to be displayed, and practically its past the point where its considered a Next or Previous fragment instead its considered current.
A fragment's onCreateViews method is called after committing a transaction in the FragmentManager. which takes less than 1 sec to bring it in front of the user (depending on the device and runtime environment)
But in your case, your data should be initalized outside the Fragment that uses it, and displayed where ever you want by passing the data itself then displaying whatever you want form it.
decouple your data from android objects (Fragment, Activity ...) and you should be able to load, maintain, access it cleanly and without worrying about their callbacks.
The Fragment's arguments can be read and loaded in its onAttach callback rather than onCreate, the Activity will then (after onAttach is complete) get a onAttachFragment callback with the Fragment as a parameter. However, I doubt onAttachFragment will be called when switching between already loaded pages in the view pager.
If not, you could have the fragment notify the activity (through an interface) that it is now active during its onActivityCreated, onViewCreated or similar method.
But it sounds more like the activity should register as a page changed listener to the ViewPager itself, and update its state depending on the page rather than which fragment is active.
As a side note, ViewPagerIndicator is quite old now (hasn't been updated in 3 years), a more modern approach is the SlidingTabs example from Google, which has been built into a library available here: https://github.com/nispok/slidingtabs

Avoid fragment recreation with hashmap

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

Dynamic Fragment recreation and setRetainInstance(true)

I don't completely understand how an activity and its fragments are recreated.
Imagine the following scenario: you have a database, activity reading this DB and a fragment, which will be created, if the activity's query to DB has some data. If DB is empty a fragment's onCreateView will cause an exception.
Now imagine we moved the app to background for a while and activity was destroyed. Moreover, the DB changes and gets empty.
How this structure will be recreated? Activity starts and understands that fragment is not needed, so everything is ok, or the fragment is recreated then anyway, because it already was created? Does setRetainInstance(true/false) has an influence on this process?
Making it more complex: we have several fragments, where one decides, if the other should be created or not in a similar way. What is the order of fragments recreation? Is it some kind of a race condition or the order is predefined?
Finally, how can we prevent or control the recreation of a fragment? I guess I have a situation, when sometimes a fragment is recreated not in right time. How such an architecture could be fixed? Simply removing fragment with onStop()?
I don't completely understand how an activity and its fragments are
recreated.
First of all it's very important to have very clear how the lifecycle of the Activity/Fragments works in Android. Here I link what is IMHO the most complete diagram on the subject (even more than the official one):
http://staticfree.info/~steve/complete_android_fragment_lifecycle.svg
Imagine the following scenario: you have a database, activity reading
this DB and a fragment, which will be created, if the activity's query
to DB has some data. If DB is empty a fragment's onCreateView will
cause an exception. Now imagine we moved the app to background for a
while and activity was destroyed. Moreover, the DB changes and gets
empty.
This kind of operation, like reading or writing to a DB (or connecting to a web service or more) are considered I/O and should get managed properly in background. I don't know concrete details in your case but in your example, you can use a CursorLoader. Android docs says the following:
A loader that queries the ContentResolver and returns a Cursor. This
class implements the Loader protocol in a standard way for querying
cursors, building on AsyncTaskLoader to perform the cursor query on a
background thread so that it does not block the application's UI.
Moreover
If DB is empty a fragment's onCreateView will cause an exception.
If a DB is empty the fragment should not cause an exception (any), if you're using, for example, a CursorLoader it will eventually get a Cursor with 0 entries. Later on (and again this depends on your implementation), if you update your database you can do a new CursorLoader petition to get a refreshed Cursor with the new data (the same applies if you rotate your device).
Two good tutorials that talks more in deep about CursorLoader are How to use Loarders written by Wolfram Rittmeyer and Life before Loaders by Alex Lockwood
Activity starts and understands that fragment is not needed, so
everything is ok, or the fragment is recreated then anyway, because it
already was created? Does setRetainInstance(true/false) has an
influence on this process?
An activity is a "dumb" component, by itself it does not know nothing about what a fragment is (well that's not quite true because the implementation of the activity knows about fragments). But for good sake it really depends on what you try to do. If you have a fragment in your activity it will be eventually recreated. Two classes you have to keep an eye to understand how this work is:
FragmentActivity.java
FragmentManagerImpl.java
Finally, how can we prevent or control the recreation of a fragment? I
guess I have a situation, when sometimes a fragment is recreated not
in right time. How such an architecture could be fixed? Simply
removing fragment with onStop()?
Never mess with lifecycle events by yourself, this is managed properly by the android runtime.
The fragment's lifecycle is tied to that of the Activity. From the Android docs:
Stopped
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
When the Activity is destroyed, it will call the onCreate() method to recreate the Activity. This means that if the underlying database has changed, the Activity can decide not to launch the fragment.
You can read more about the Fragment lifecycle here, and the Activity lifecycle here.

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.

Categories

Resources