We are working Android-Binding on supporting new features in Honeycomb and Ice-cream sandwich is underway. The two major changes (for UI), compare to 2.x, are ActionBars and Fragments. ActionBar, of course, Android-Binding will support it without doubt. But what about fragments? We are actually considering to provide an alternative way of working with it.

Before we start discussing our solution, let’s look at what fragment do, according to the official documentation, fragment is:

  1. Can be a portion of UI, e.g. the mail box list of the Gmail client
  2. Can be invisible worker pieces of code
  3. Fragment is reusable
  4. Declare or load from layout, hence different layout/screen sizes can have different set of fragments loaded in the same Activity

Advantages of fragments

Recyclable piece of UI elements, across different sizes or layouts. Take Gmail client as example, 
On a cell phone, the layout should looks like the left hand side; but on tablets (landscape) would look like right hand side. The Mail list and detail is two different fragments in the above example.
Since the code for loading the list resides in the fragment, Activity A and B need not to write redundant code. And the beauty of fragment is not only this, but also the Activities are not coupled with fragments; the fragments can be declared in xml file (which is selected according to different configuration).
It sounds pretty promising when using fragment, with only one set of code, we can have an UI that adopt to different configurations, not only the UI is loaded dynamically, but also the functionality; and ideally Activity and Fragments within it is all isolated and decoupled.

Disadvantages

I studied fragments with the official sample: the HC Gallery. I think the sample itself worth a post for me to write a review, but in short, I think it further expose how messy we can have with fragments.
First look at the TitlesFragment, which represents the left hand column of the program; Fragment is very much like an extension to Activity, and like Activity, it mixed up the UI code and controller logic together; you have numbers of listeners to buttons, lists etc registered to it. But if you accept the traditional Android Activity pattern, it is fine.
Suppose the user clicks (or touch) the title, what should happen? The Content Fragment need to load a different picture (if in tablet mode) or show Content Activity (with Content Fragment embeded) in cell phone mode. Titles Fragment should not aware (or care) which mode it is using, right? So, in the sample code, the click event is routed back to the main Activity:
// In onCreate
mListener = (OnItemSelectedListener) activity;
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity via OnItemSelectedListener callback
mListener.onItemSelected(mCategory, position);
mCurPosition = position;
}

Above is the portion how it route the item click back to Activity. So, how the activity handles it?

 public void onItemSelected(int category, int position) {
if (!mDualFragments) {
// If showing only the TitlesFragment, start the ContentActivity
// pass it the info about the selected item
Intent intent = new Intent(this, ContentActivity.class);
intent.putExtra("category", category);
intent.putExtra("position", position);
intent.putExtra("theme", mThemeId);
startActivity(intent);
} else {
// If showing both fragments, directly update the ContentFragment
ContentFragment frag = (ContentFragment) getFragmentManager()
.findFragmentById(R.id.content_frag);
frag.updateContentAndRecycleBitmap(category, position);
}
}

There are two problems with the above code:

  1. Activity became layout aware.
  2. Activity in this case coupled with the (specific) fragment.
Looking further down the TitlesFragment, we have another problem, in onActivityCreated, something like this appears:
    @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
<p>ContentFragment frag = (ContentFragment) getFragmentManager()
.findFragmentById(R.id.content_frag);
if (frag != null) mDualFragments = true;
ActionBar bar = getActivity().getActionBar();
bar.setDisplayHomeAsUpEnabled(false);
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//...
if (mDualFragments) {
        // ...
}
// If showing both fragments, select the appropriate list item by default&lt;br /&gt;<br />
if (mDualFragments) selectPosition(mCurPosition);
}
  1. TitleFragment is coupled with ContentFragment!
  2. TitleFragment also aware of the ActionBar
For the rest of other fragments, I am not going to go through them. In the HC Gallery example, we found that:
  1. Fragment did reusable. But somehow you need to consider how your layout will be; In case you have a super large screen with three columns, you might need to include that case in your fragments and activities; hence, it is not layout independent.
  2. Activity is tightly coupled with fragment. This is even more problematic when we want to add animations to fragment:
    public void toggleVisibleTitles() {
// Use these for custom animations
final FragmentManager fm = getFragmentManager();
final TitlesFragment f = (TitlesFragment) fm
.findFragmentById(R.id.titles_frag);
final View titlesView = f.getView();
// Determine if we're in portrait, and whether we're showing or hiding the titles&lt;br /&gt;<br />
// with this toggle.
final boolean isPortrait = getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_PORTRAIT;
final boolean shouldShow = f.isHidden() || mCurrentTitlesAnimator != null;
        // Truncated the rest..

What Android-Binding team did?

The HC Gallery is the start for most people on fragments, but it did exposes the limitation on fragments. I believe what is demoed in HC Gallery is not the best practice we can have when writing fragment-enabled application. I actually did a quick port of HC Gallery, in Android-Binding way, you can find it in the trunk (called AB Gallery) in the project source.

The demo is not completed. Most stuff is easily ported to MVVM style, even the Action Bar is completed with an extra xml file, but the fragment interaction is the most problematic one.

One reason is, fragment itself is declared in layout, but itself is not a subclass of View. The default layout inflater of Android make special treatment to it, and it replaces the fragment with the fragment’s root view. Most changes made across fragments should be committed via Fragment Manager. The other challenge is the ability to adopt different layout scenarios, for example, when we click on the image title, it should:

  1. Change the ContentFragment’s image (or in MVVM word, change the CurrentImage Observable), or;
  2. Start a new Activity (ContentActivity)
Android-Binding aims at decoupling most view related logic from view model; I really don’t want the TitleFragment to aware of what layout it is using. Yes, it is possible to have two layouts for TitleFragment which declares:
<ListView binding:onItemClicked="ShowContentInNewActivity" .../>
or in tablet mode (probably under layout-xlarge)
<ListView binding:onItemClicked="SetCurrentImage" .../>
Above codes solved the need to distinguish different screen configuration, in our view model, we simply provide two different Commands and to use which one is all up to the View (layout xml) choice.
But another problem arise. TitleFragment should not aware of ContentFragment, and hence the “CurrentImage” should not sit in fragment but in the Activities’ view model. So, finally the hierarchy of View models became:
MainActivity:
  • ShowContentInNewActivity (command)
  • SetCurrentImage (command)
  • CurrentImage (observable)
TitleFragment:
  • TitlesList (array observable)
ContentFragment: nothing

No fragment

The above solution should work, but it turns out fragments does nothing but making even more problem (at least in terms of MVVM). We then go back to the basic: MVVM itself is reusable. View Model itself, is reusable, and this is the fundamental nature of MVVM.
One thing I really like, about fragment, is the ability to ‘plug-in’ behavior or UI on demand, base on layout’s choice. We have to provide this, in Android-Binding, if we want to remove the need of fragment.
A few new feature is added to Android-Binding, which may directly help this. One thing is the ability to bind to “sub” view models / observable. For example:
<TextView binding:text="A.B.C"/>
We call this “InnerField” in Android-Binding, which allows you to bind to child of an observable.
Another addition is a BindableFrameLayout, which acts as a placeholder (similar to ViewStub) but it is bindable and collapsable.
Finally we need a mechanic to on-demand loading and initiating parts of View Model. Which is easy to do with something like:
// This will be the root view model for Titles
    public final Observable<Titles> TitlesVm = new Observable<Titles>(Titles.class){
private Titles instance = null;
@override
public Titles get(){
if (instance==null) instance = new Titles();
return instance;
}
};
<div>
This is very similar to those lazy load or singleton pattern. (And we may provide some helper super class later).

Putting everything together

We can now have a common View Model across the whole app, which will lazy load different portions of sub view model (only when needed). Since portions will be loaded only when first requested, the responsibility of choosing what to load is on the Layout itself (which depends on configuration, automatically).
What we discussed above is still under active development, especially the BindableFrameLayout part (we have good working one in our repository, but still polishing it). Once everything is done, a pure MVVM HC Gallery sample will be provided.
Advertisements