Android fragments: is empty constructor really required? - android

I have an activity with a pager and a FragmentStatePagerAdapter inside (I need to swipe across many pages). As we all know, this adapter creates 3 fragment instances at a time, the one to be displayed, the previous and next ones.
My activity was working really nice using a fragment with only one constructor: it received 1 parameter. When testing, I started to get the infamous message:
Unable to instantiate fragment: make sure class name exists, is public,
and has an empty constructor that is public
The funny part is that this message only shows up right after orientation change, but the app just works if orientation remains still. So,
Why does it work when orientation does not change?
Why does it fail when orientation is changed?
What are the differences in activity an fragment life cycles when orientation changes vs activity just created?
Thanks a lot

is empty constructor really required?
Yes.
Why does it work when orientation does not change?
Because Android is not trying to recreate your fragments.
Why does it fail when orientation is changed?
Because Android is recreating your fragments.
When a configuration change occurs (e.g., orientation change), by default Android destroys and recreates your activity, and also destroys and recreates the fragments in that activity. The "recreates the fragments" part is why you need the zero-argument public constructor on your fragments. It is also used in other cases, such as with a FragmentStatePagerAdapter.
Or, to quote the documentation:
All subclasses of Fragment must include a public empty constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the empty constructor is not available, a runtime exception will occur in some cases during state restore.

Related

In which situation we want to add fragment without container?

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.

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

What are good cases not to use setReatinInstance(true) in a fragment?

Since orientation changes happen fairly quickly one would think that keeping a Fragment in memory during that time would be more efficient than recreating it again.
Since it's kept for a short time only, there seem to be little impact on memory.
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
Google's primary concern is that you'll screw up and have data members in the fragment that refer to the old activity that you do not clean up in post-configuration lifecycle method calls (e.g., onCreateView()). For example, you might hold onto a widget in a data member, where you do not immediately null out or repopulate that data member on a configuration change. If your fragment has a reference back to the old activity, the old activity (and everything it holds onto) cannot be garbage-collected until your fragment gets destroyed. This is one of the reasons why Google does not recommend retaining any fragment with a UI.
Firstly maybe check this link: http://android-er.blogspot.com/2013/05/how-setretaininstancetrue-affect.html
Basically you shouldn't always retain the instance of the fragment because fragments are attached to activities. Hence when an activity is re-created (i.e. on configuration change), the fragment needs to be re-associated to the new activity (which leads to extra coding and some extra problems). If you just unnecessarily setRetainInstance(true), you are giving yourself more error checking and coding to do for no reason. By setting the setRetainInstance(true), you will need to deal with a different fragment lifecycle as well because certain methods in the lifecyle are now skipped (i.e. the onCreate() is no longer called after configuration changes). As far as I understand, setRetainInstance(true), won't make it more efficient because you could use onSaveInstance to save any data that you would want to use in the recreation of the same kind of fragment.
I hope this helps.

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.

Categories

Resources