Wednesday, April 8, 2015

Tips: Fragment

If you have looked at my source code lately, you will notice that I brought in a Fragment.  Fragments were introduced in honeycomb to help deal with the Fragmentation problem in Android.  It is a very flexible object that allows content and logic to move around an Activity easily.  They are not mini-activities or components (views), nor are they anything in-between.  They are fall into unique category that comes complete with its own lifecycle, but that is heavily reliant (and basically slaved) to an owning activity.

There is some controversy around fragments.  I haven’t met anyone that really loves them, but there are plenty of people hate them.  I initially disliked them. A lot.  After using them for a few years, I have come to appreciate them and what they can provide.  That being said,  I think they are complex and have a steep learning curve to do anything really interesting in them (correctly, anyway).  The good news is that once you master them they can be a very powerful tool.

One of the biggest misconception about them is that they are a UI element like activities and views/components.  They are not.  They can create and manage a UI, but it is not required.  A fragment can exist solely to manage data or to be an intermediary between other fragments.

Flavors

Fragments come in the standard variety and DialogFragments.  DialogFragments are Fragments and provide similar behaviors to a Dialog. If it has a UI then it will also look just like a normal Dialog.  It is now a recommended best practice  to make any custom dialog a DialogFragment.  That is because they solve a lot of the Dialog problems (like the memory leak if you forgot to dismiss one).  They are also pretty much the only convenient way to show a dialog from a fragment. 

DialogFragments can also be shown as a standard fragment.  The presentation mode  is not up to the Fragment.  Whether it is shown as a standard fragment or a dialog is driven by how it is added the FragmentManager.  A standard transaction will show it as a normal fragment.  The show() method will display it as a dialog.  Either way, it is better to code the DialogFragment to handle either case.

Activity Interaction

The fragment’s UI (if it has one) is added into a ViewGroup that is present in the activity (either directly or indirectly).  The fragment itself is never added to the UI hierarchy tree.  The Fragment’s onCreateView() returns a View.  It is that view that is added to the View hierarchy tree.

If the View isn’t added to the hierarchy tree, then the UI cannot show.  This can be done by  adding the fragment with only a tag, but not providing a container to display it.   In this case, the fragment has a UI to manipulate, but since it was never added to the hierarchy tree it cannot show on the screen.

Fragment Manager

The fragment exists and is managed by the activities fragment manager.  The fragment manager lives in the activity and is available to all fragments.  It is not directly visible within the Views\Components (nor should you ever add it).  The fragment manager manages the fragment lifecycle, addition and deletions, restorations, and the back stack.  The fragment back stack provides a way to stack the fragments within an activity and allow the user to use the back button to pop them off.

Fragments are found via two methods : findViewById() or findViewbyTag().  If you added a fragment without a tag, you are out of luck on finding it again unless you stored a reference to the fragment.  But if you stored a reference to it, then you wouldn’t need to find it again anyway, so that is a moot point.  At least until a configuration change happens and then you are out of luck again.

The id part of findViewById() does not refer to the id of the container for the fragment.  It is the id of the fragment itself.   This is only really useful for finding fragments inflated from the xml:

<fragment android:name="yourFragment"
android:id="@+id/yourfragmentId"
android:layout_width="wrap_content"
android:layout_height="match_parent" />

For all dynamically added fragments, you must use findFragmentByTag().  It is best practice to give every fragment a unique enough tag that you can find it again.


Fragment Transactions


Fragments are updated in the Fragment Manger asynchronously via transactions.  A transaction provides a lot of methods to add, replace, or delete a fragment.  You can manipulate multiple fragments in a single transaction.  It also allows you to customize the entry and exit animations for the fragment.  A standard Fragment transaction will always follow this pattern:


FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
//manipulate transaction
transaction.commit();

A DialogFragment transaction that will show it as a Dialog looks like this:


DialogFragment dialogFragment = new YourDialogFragment();
dialogFragment.show(getFragmentManager(), TAG);


Big Caveat 1


One very important thing to remember is that the Fragment Manager has a drop dead date for adding or removing fragments.  That date expires once the activity calls its onSaveInstanceState().  After that you cannot (reliably) add or remove a fragment.  It will throw a nasty exception. 


There is a grudging workaround : commitAllowingStateLoss()/dismissAllowingStateLoss().  They can work, but may lead to bigger problems.  The most notable being a crash on restore or a zombie fragment.  Between the two, the zombie is the more common occurrence.  This typically occurs when the fragment was killed after the onSaveInstanceState(), but the thing resurrects itself during a restore.


Big Caveat 2


Fragments can be also added via the xml.  Any fragment added this way cannot be moved or deleted.  It exists for the life of the UI it is attached to. These are normally found via its id and not its tag.


The Lifecycle


When it comes to fragments, you must understand the lifecycle.  There are a lot of states and functionality may only be available within certain states of the lifecycle.


The basic lifecycle can found in the android developers guide at http://developer.android.com/reference/android/app/Fragment.html#Lifecycle.  This gives a high level view of the life cycle, but it misses a few states.  There is a lot more going on that you will need to know to make complex fragments.  Here is a more verbose version of the fragment lifecycle:


Fragment Lifecycle - New Page


And here is how it relates to the activity lifecycle:


Activity to fragment states - New Page


Common Pitfalls


Pitfall 1: The Lifecycle


Fragments have a very specific lifecycle that is bound to its Activity.  Unlike Activities, fragments can’t guarantee some fundamental things are not null all the time.  For instance, it is a good thing to know that getView(), getActivtiy(), getFragmentManager(), getString() can and will return null at various inconvenient points.  It is always a good idea to check the state before blinding using any native method unless you are absolutely sure what it is not null. 


The biggest issues I see in Fragments are directly related to not understanding the lifecycle and how it affects the functionality.  Trust me, it is better to think it through up front because it is difficult to retroactively fix lifecycle issues.


Pitfall 2: Commit drop dead date


This is directly related to Pitfall 1, but deserves its own bullet point.  This was discussed in Big Caveat 1 above.


Pitfall 3: DialogFragments


There are two big problems here:



  • Not breaking up the dialogfragment creation correctly.  Most of the creation should be done just like a regular fragment.  Only use the onCreateDialog method to manipulate the dialog window.  Do not perform the UI creation or restoration there. 

  • Issue 2 is how to get the data back to a listener.  The fragments do not support the normal dialog listeners.  You will have to add them or broadcast the results.  If you use listeners, you must handle the restore case correctly.  When the fragment destroys on configuration change, then relationship to the listener is lost.  Either take the fragmentdialog down and restore from interested parties, or broadcast the results.

Pitfall 4:  Is anyone there?


Once a fragment is added to the activity, you may want to find it again.  This can be tricky to do because you either need a tag or id to find it again.  The id only exists on a fragment if it was inflated from the xml.  Your only other option is to tag the fragment. 


Along the same lines, I strongly suggest you come up with unique enough tags to avoid a collision between tag names.  Since fragments can also create other fragments, you need to make sure that you won’t accidently lose a fragment by a reusing tag name accidentally.


Pitfall 5:  #$@ &%! Context


Unfortunately, the fragment does not come with a context.  This is the single biggest pain to deal with when coding fragments.  Context is used everywhere and no great way to get at it that works all the time. 


There are a few lesser-of-evils ways to get it.  They all have Pros and Cons, but a mix of the options will normally get the job done.



  • The most obvious one is getActivity().  Just be aware that this can return in the wrong lifecycle phases.

  • The next option is getView().getContext(), but the getView() can also be null in certain lifecycle-phases and it also only really works if the fragment provides a UI.

  • The third option only works in onCreateView() but it is foolproof : just steal it from the LayoutInflater via inflater.getContext().

  • The fourth option is to use the Application context.  This only works if you override the application class and open it up to pass the context via a static method.  Be careful with this option. It works great, but it can lead to big garbage collection issues if you use it for creating UI elements.

Once you wrangle a Context, never store it in the fragment.  It can seriously mess up your garbage collection.


Pitfall 6: Who’s your daddy?


Inherently all fragments exist in a flat structure.  Basically they are all siblings within the Activity regardless of who created it.  So if the Activity creates Fragment1 and then Fragment1 begetsFragment2.  Fragment2 is not bound in any way to Fragment1.  They are independent siblings.  If Fragment1 is destroyed but does not explicitly take Fragment 2 with it then Fragment 2 will still exist out in the ether (potentially) unknown to anyone. 


You can use child fragments to fix this, but they only came in during Jellybean and there have been reports of problems with them.  I haven’t played with them enough to give a yea or nay about using them yet.


Pitfall 7: Support Fragments vs Native Fragments.


In general, I advise to always use the support library over the native whenever possible.  It updates more often the the native libraries and as a result, bugs get fixed more often.  My only exception to this rule is the Fragments.  For these I don’t have a strong opinion, but I do prefer to use the native Fragments.


Honestly, It doesn’t matter which you use, as long as it is consistent everywhere. Do not ever mix support and native fragments or methods.  It creates all sorts of havoc and weird compilation and runtime errors.

No comments :

Post a Comment