I recently found myself looking at Fragment transactions and wondering precisely what each Fragment state meant. I vaguely had an idea of what added, attached, shown, hidden, detached and removed meant, but not the specific behaviors of each. I spent a morning looking through Android's code to better understand what happens to Fragments.
Here's a reference manual for what I learned. I looked into two aspects of Fragments: fragment states, and animations in transactions. This research was done primarily on the compatibility library. It's possible that some of the details below will differ for the 3.x+ implementation of Fragments.
Fragment States
All of the below support animations. Terminology note: when I say "configuration change", I mean when the device changes configuration (for example, from portrait to landscape) and the Activity must be destroyed/recreated.
FragmentTransaction.add()/remove() - These add and remove a Fragment from the FragmentManager. By default, it will begin showing the Fragment as well. During the transaction the returns of Fragment.isAdded() and Fragment.isRemoved() will be modified. These states are preserved on configuration change, so a Fragment that's added will remain added and can be retrieved again from the FragmentManager. Since a removed Fragment is no longer part of the FragmentManager, it will not be retrievable after a configuration change either.
FragmentTransaction.attach()/detach() - These attach/detach a Fragment from the Activity (e.g., make it an active Fragment that can be seen). During the transaction the returns of Fragment.isAdded() and Fragment.isDetached() will be changed. These states are preserved on configuration change, which means that (unlike removing a Fragment) it is possible to detach a Fragment but then retrieve it later after configuration change (even if it's not actively being displayed). Even in a detached state, the Fragment runs through all lifecycle methods (except the ones that are related to being attached to an Activity) - for example, onSaveInstanceState() will still be called even if the Fragment is detached (which is quite useful for maintaining state).
FragmentTransaction.show()/hide() - These show or hide a Fragment (in particular, it calls View.setVisibility() on the Fragment's root View). During the transaction the returns of Fragment.isHidden() will be modified. Showing/hiding Fragments states are NOT maintained on configuration change, meaning that if you hide a Fragment it will reappear on config change (for example, if the user rotates the screen).
FragmentTransaction.replace() - Theoretically it is the exact same as removing all Fragments associated with a particular View, then adding a new Fragment in its place. I did not research this much (since I'm more interested in manual control of which Fragments are shown).
Fragment Animations
There are three different ways to animate Fragment transactions. These are ordered by precedence (in other words, the first method, if available, will be used; then the second, then the last).
One thing to note between the compatibility library and the standard Fragment library is that the standard library uses property animators, whereas the compatibility library uses tween Animations.
1. Override your Fragment's onCreateAnimator() method and return an animation. This is the only way to dynamically generate an animation in code (the other two methods rely on pre-configured animations from your resources directory). In the compatibility library, the method is onCreateAnimation() (since the compatibility library uses animations instead of animators).
2. Call FragmentTransaction.setCustomAnimations(), referencing either animators or animations (depending on whether you're using the compatibility library or not). What is interesting is that setCustomAnimations() affects all fragment transitions added to the transaction after it is called. So you need to call setCustomAnimations() before you want it used, and you can actually setup multiple different custom animations for each part of a transaction (with a call to setCustomAnimations() before each add()/remove()/attach()/detach()/show()/hide()/replace()).
3. Setup a style that defines window animations (from this style), then call FragmentTransaction.setTransition() and FragmentTransaction.setTransitionStyle() to specify that style and target the correct animation.
#3 works different in the compatibility library. FragmentTransaction.setTransitionStyle() is completely ignored. Instead, it uses the specified transition to select a stock animation. So if you want custom animations in the compatibility library, you must use #1 or #2.
I post random things I've learned while coding with the hope that it will save people time and effort.
Tuesday, June 12, 2012
Tuesday, June 5, 2012
Obscure SimpleDateFormat Android Issue
This bug should win an award for the most obscure bug I've ever run into this year.
You might expect the two formats to be different (one for May 11, the other for May 12). You would be wrong on Android 2.2 and lower. It turns out in this one specific case, the SimpleDateFormat will spit out the same formatted date for the two Calendars.
The conditions seem to be thus:
In this one situation, SimpleDateFormat gets into a state where it formats both Calendars to the same date (May 11). Change any of the above assumptions and the problem will not occur; if the Calendars are more than one day apart, or you use multiple SimpleDateFormats, or you don't set the TimeZone, or you don't immediately reuse the same SimpleDateFormat...
This problem does not occur on Android 2.3+. For Android 2.2 and lower, the solution is simple - create a new SimpleDateFormat when formatting two Calendars one after another.
Check out this code below:
Calendar cal1 = new GregorianCalendar(2012, 5, 11); cal1.setTimeZone(TimeZone.getTimeZone("UTC")); Calendar cal2 = new GregorianCalendar(2012, 5, 12); cal2.setTimeZone(TimeZone.getTimeZone("UTC")); DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); df.setTimeZone(TimeZone.getTimeZone("UTC")); Log.i("test", "cal1.format=" + df.format(cal1.getTime())); Log.i("test", "cal2.format=" + df.format(cal2.getTime()));
You might expect the two formats to be different (one for May 11, the other for May 12). You would be wrong on Android 2.2 and lower. It turns out in this one specific case, the SimpleDateFormat will spit out the same formatted date for the two Calendars.
The conditions seem to be thus:
- Two Calendar objects that are one day apart.
- One SimpleDateFormat that you want to use.
- Both the Calendars and the DateFormat are set to the same TimeZone.
- You format both Calendars using the DateFormat, one after another.
In this one situation, SimpleDateFormat gets into a state where it formats both Calendars to the same date (May 11). Change any of the above assumptions and the problem will not occur; if the Calendars are more than one day apart, or you use multiple SimpleDateFormats, or you don't set the TimeZone, or you don't immediately reuse the same SimpleDateFormat...
This problem does not occur on Android 2.3+. For Android 2.2 and lower, the solution is simple - create a new SimpleDateFormat when formatting two Calendars one after another.
Subscribe to:
Posts (Atom)