Brian Griffey

Android and iOS Developer. Design Enthusiast

No Fragments, Rich Views

 

I've seen a lot of debate on Twitter recently regarding Fragments. Are they good, bad, or neutral in Android development? We at HomeAway don't use Fragments, we use a development philosophy that we call "rich views" that we feel takes the best and simplest route to providing good, reusable and testable code.

Why not Fragments?

We don't use fragments, simply because we don't need to. Fragments have a tremendous amount of lifecycle events that can be difficult to manage, and at the end of the day are fairly unnecessary. Let's take a look at the fragment life cycle straight from http://developer.android.com/reference/android/app/Fragment.html#Lifecycle

onAttach(Activity) called once the fragment is associated with its activity.
onCreate(Bundle) called to do initial creation of the fragment.
onCreateView(LayoutInflater, ViewGroup, Bundle) creates and returns the view hierarchy associated with the fragment.
onActivityCreated(Bundle) tells the fragment that its activity has completed its own Activity.onCreate().
onViewStateRestored(Bundle) tells the fragment that all of the saved state of its view hierarchy has been restored.
onStart() makes the fragment visible to the user (based on its containing activity being started).
onResume() makes the fragment interacting with the user (based on its containing activity being resumed).
As a fragment is no longer being used, it goes through a reverse series of callbacks:

onPause() fragment is no longer interacting with the user either because its activity is being paused or a fragment operation is modifying it in the activity.
onStop() fragment is no longer visible to the user either because its activity is being stopped or a fragment operation is modifying it in the activity.
onDestroyView() allows the fragment to clean up resources associated with its View.
onDestroy() called to do final cleanup of the fragment's state.
onDetach() called immediately prior to the fragment no longer being associated with its activity.

Whew! 12 methods in total; that's a lot of methods! All to handle state. On top of all of this there's also the FragmentManager, which you can use to find fragments to ask things such as isAdded(), isDetached(), isHidden(), isInLayout(), isRemoving(), isResumed(), and isVisible(). Alright, you guys get my point.

What advantages are you gaining for having to learn all of these method calls? From our perspective, not a lot. The goal of fragments, as I see it, has always been to write clean MVC code. Fragments are really just a packaged way to inflate a view, grab references to sub views, provide a place to put simple logic in handling those sub-views, and then save and restore state; and the FragmentManager is responsible for adding and removing those inflated views from the current view hierarchy.

So what's an easier route to do all of these things? The answer is pretty simple and it's been around since the beginning of Android, Views!

What's in a view

Views have been around in Android since the very beginning, they're one of the most basic building blocks. Every time you create a new application you'll start with the default Hello World activity that contains a reference to a TextView. The first thing you probably did as an Android developer was change that text to something else like "This is awesome!", run it on your phone, then proudly wave your phone around in the air showing it to all your friends. During this wild waving you probably did something to cause the app to background itself (like accidently hitting the app switcher or the home button), but when you switched back to your app it was just as you left it. Why is that? Well, all of the built in Android views implement onSaveInstanceState and onRestoreInstanceState found inside of every View.

So, how can we use this magnificent fact to create something that duplicates most of the functionality of fragments without most of the headaches? I propose that we extend Android's provided container types that already handle state, and lean on them for the heavy lifting. This is what we call rich views; extending framework types, a lot of the time a FrameLayout, and adding logic into them to suite our needs for displaying a model or handling UI interactions. Earlier I made a list of what I thought Fragments were good at,let's go through those using an example of a view that handles an imaginary Quote object that might contain information regarding a payment on a rental property.

Inflating a view

This one's easy, any Viewgroup already has methods for adding and removing subviews. We can simply inflate any xml we want in our constructor; for example

public class QuoteView extends FrameLayout {
 //constructors you'll need to support
 public QuoteView(Context context) {
  super(context);
  init();
 }

 public QuoteView(Context context, AttributeSet set) {
  super(context, set);
  init();
 }

 private void init() {
  //grab a layout inflater, and inflate R.layout.view_quote, into this FrameLayout
  LayoutInflater.from(getContext())
   .inflate(R.layout.view_quote, this, true);
 }

}

That's it, we're done. We've now added our layout defined in XML to this view. This is the same exact process that a Fragment uses during the onCreateView method. The Fragment constructs a non state saving FrameLayout, hands you an inflater, and then has you inflate xml with that FrameLayout as the parent ViewGroup. Here we have the freedom to actually attach our inflated view to the ViewGroup though, if you try that in a Fragment it'll throw an error because it'll try re-add the inflated view and crash; one of those nice gotchas.

Grabbing references to sub views

Getting references to subviews is now amazingly easy because of a wonderful library provided by Jake Wharton called ButterKnife. ButterKnife allows you to use simple annotations to wire views into any object by calling ButterKnife.inject, no more more messy findViewById nonsense. Although you can still do that if you want to. Let's wire up our Quote View by adding some fields and changing our init function slightly. Here's the new version.

public class QuoteView extends FrameLayout {
 @InjectView(R.id.view_quote_total)
 TextView mQuotetotal;
 @InjectView(R.id.view_quote_checkin_date)
 TextView mCheckinDate;
 @InjectView(R.id.view_quote_payment_breakdown)
 PaymentBreakdownView mPaymentBreakdown;

 //omitted code from earlier....

 private void init() {
  //grab a layout inflater, and inflate R.layout.view_quote, into this FrameLayout
  LayoutInflater.from(getContext())
   .inflate(R.layout.view_quote, this, true);
  ButterKnife.inject(this);
 }

}

Our QuoteView now knows about three of its subviews, one to display the quote total, one to display the check-in date, and another rich view to display the payment breakdown. As long as those views are in our view_quote layout file, they'll be assigned to our fields. Another good thing about ButterKnife is: if the view ids don't exist in view_quote then the inject function will throw an error which can be easily detected using unit tests and Robolectric. Each of our rich views has an accompanying unit test that will find such errors, and free us from the pain of a null view being returned by findViewById leading to an uncaught NPE down the road.

Provide a place to handle display logic and UI element interaction

Here's where we really get into the fun stuff. When fragments first came out developers were excited because it gave them a way to take their 1000 line Activity class and break that up into a few, several hundred line classes. A lot of applications spend a majority of their time in following pattern: launch activity, get DTOs from server, then display DTOs in the UI. Fragments provided a way to break some of this into chunks, allowing a Fragment to handle sections of the UI (the jury is still out on how you should actually give your DTO to the fragment, do you create a newInstance method, or an empty constructor and then setArguments? Wait, why are we putting stuff in a Bundle? I have to remember bundle keys now? wtf). Let's use simple setters on our views here (like sane people)

public class QuoteView extends FrameLayout {
 @InjectView(R.id.view_quote_total)
 TextView mQuotetotal;
 @InjectView(R.id.view_quote_checkin_date)
 TextView mCheckinDate;
 @InjectView(R.id.view_quote_payment_breakdown)
 PaymentBreakdownView mPaymentBreakdown;

 //omitted code from earlier....
 public void setQuote(Quote quote) {
  //set out text views from out DTO
  mQuotetotal.setText(quote.getQuoteTotal());
  mCheckInDate.setText(quote.getCheckinDate("yyyy-MM-dd"));

  //set our PaymentBreakdownView to handle it's own display logic
  mPaymentBreakdown.setPaymentBreakdown(quote.getPaymentSchedule());
 }
}

There we go, easy as pie to display data that we got over the network.

That's it for now, I'm going on vacation so watch for part 2 next week that will cover interacting with UI elements, and state management pieces of our applications.