Sunday, February 9, 2014

Nested Fragments and the Backstack - Part 3

This is the third post in the series about Nested Fragments and the Back Stack in Android. Read the previous posts here:

  1. Part 1
  2. Part 2

The first two posts have looked at the topic taking ViewPager as an example. I have also mentioned repeatedly that this is not the only use case for having to maintain the back-stack of nested fragments. One use case that I threw up often in comments was about Navigation Drawers. That is exactly what this post will look into.


EDIT: Some Google engineers, including the creators of the Android framework have expressed their reservations regarding this article. Read this G+ thread for more details. They point out that using an Application sub-class to save state is not a good idea, but also that saving Fragment instance state explicitly might in itself needs to be considered carefully. I hope to gather their thoughts and write a follow-up post in the coming weeks. Stay Tuned.


Re-cap

Just to re-cap the conclusion from the previous article:

  • Consider pro-actively saving your Fragment states in onPause, particularly is the Fragment happens to nest other fragments inside of it.
  • Do not rely solely on the system saving state for you in onSaveInstanceState.
  • Use FragmentManager#saveFragmentInstanceState to save the Fragment state including the back-stack of nested fragment transactions for you.
  • Do not hold on to the saved state any longer than necessary.

Adapting to Navigation Drawer

If you take the source code for Part 2 of the series, and adapt it as-is to a Navigation Drawer example, you’ll find that things don’t quite work as you’d expect. In particular, you’ll find that even though you have saved the state of the ContainerFragment in onPause, the next time you return to this fragment, its state is cleared.

Why is this? The alert reader might have spotted the reason.

In the case of the ViewPager example, we clear the saved fragment state in onDestroy(). This is because of the way ViewPager works (or rather, FragmentPagerAdapter or FragmentStatePagerAdapter works): When you navigate away from a tab, the Fragment’s `onPause is called but none of the other life-cycle methods are called. This means onDestroy is skipped and the Fragment is simply torn down. onDestroy is only called when the hosting Activity is destroyed.

    @Override
    public void onPause() {
        super.onPause();
        ((NestedFragApp)getActivity().getApplication()).setFragmentSavedState(SAVED_STATE_KEY, getFragmentManager().saveFragmentInstanceState(this));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        ((NestedFragApp)getActivity().getApplication()).setFragmentSavedState(SAVED_STATE_KEY, null);
    }

However, when you use a Navigation Drawer, the case is different. In this situation, there is no PagerAdapter to deal with. When you navigate from one item in the navigation drawer to another, the “old” Fragment undergoes the complete life-cycle - onPause all the way to onDestroy and onDetach. As a consequence, since you’re clearing the saved Fragment state in onDestroy of the ContainerFragment, you end up clearing the state that you had just saved in onPause.

Solution?

Well, the solution is rather simple - just don’t clear the state in onDestroy of the parent Fragment! In addition, there are a few other minor changes - like the way you set the initial state of the ContainerFragment (instead of retrieving the saved state in one of the life cycle methods of the Fragment, you use setInitialSavedState in the static creator method). The source code for this is available at the github repo for this series.

    public static ContainerFragment newInstance(SavedState savedState) {
        ContainerFragment frag = new ContainerFragment();
        frag.setInitialSavedState(savedState);
        return frag;
    }
    ...
    ...
    @Override
    public void onPause() {
        super.onPause();
        ((NestedFragApp)getActivity().getApplication()).setFragmentSavedState(SAVED_STATE_KEY, getFragmentManager().saveFragmentInstanceState(this));
    }

Here’s a video showing this in action (Unfortunately the Android screenrecord tool doesn’t like it if you rotate the device during the recording, but I think the video demnostrates the point sufficiently):

Forgetting the saved state?

The bullet points that we established in the previous post (re-capped at the beginning of this post) say that you should not hold on to the saved state any longer than necessary. However, we had to violate that rule in this solution because - well - it is pointless to save the state only to immediately clear it!

However, depending on your use case you might approach this in a different manner. For example, you might only clear the fragment saved state when the hosting Activity is destroyed. This is not demonstrated in the sample code on github but should be straightforward to implement.

Sunday, February 2, 2014

Nested Fragment and the BackStack - Part 2

This article is the second in this series about Nested Fragments and the Back Stack in Android. You can read Part 1 here. To get this post into context, take a look at the video embedded in the previous post, if nothing else.

Edit: Later posts in this series at

  1. Part 3

EDIT: Some Google engineers, including the creators of the Android framework have expressed their reservations regarding this article. Read this G+ thread for more details. They point out that using an Application sub-class to save state is not a good idea, but also that saving Fragment instance state explicitly might in itself needs to be considered carefully. I hope to gather their thoughts and write a follow-up post in the coming weeks. Stay Tuned.


At the risk of sounding repetitive, I’ll start off this post by once again stating the gist of the previous post:

A Fragment’s onSaveInstanceState method is not guaranteed to be called when it is “removed”. The Fragment might simply be torn down. The only time its state might be saved is when the hosting Activity saves its state.

We also saw how this could be a problem when you use nested fragments and a FragmentManager doesn’t save its backstack of fragment transactions. In this part, we’ll look at one possible solution to this problem.

Save state in onPause

This is the obvious solution to the problem. The Android docs also state this time and again: it is a best practice to proactively save state. Also, since onPause is the only callback that is guaranteed to be called, it makes sense to save your instance state here.

Having said that, it is easy to save view states, scroll positions and even entire arbitrary objects in onPause. But, how does one save a back stack of fragment transactions?

Enter Fragment.SavedState. You can ask the FragmentManager to save the state of a Fragment using saveFragmentInstanceState. The back stack being managed by a Fragment’s nested FragmentManager is included in the state saved by this method.

The Application sub-class

This post shows how you could use a sub-class of the Application class to save the state, but you might choose another mechanism to do so. The important thing is that the state has to be saved. We use a Map of strings as keys and the saved state as values in this example.

public class NestedFragApp extends Application {

    Map<String, Fragment.SavedState> savedStateMap;

    @Override
    public void onCreate() {
        savedStateMap = new HashMap<String, Fragment.SavedState>();
        super.onCreate();
    }

    public void setFragmentSavedState(String key, Fragment.SavedState state){
        savedStateMap.put(key, state);
    }

    public Fragment.SavedState getFragmentSavedState(String key){
        return savedStateMap.get(key);
    }

}

Explicitly saving Fragment state

Then, you save the state of the container fragment when it pauses as follows:

@Override
public void onPause() {
    super.onPause();
    ((NestedFragApp) getActivity().getApplication()).setFragmentSavedState(
            SAVED_STATE_KEY, getFragmentManager().saveFragmentInstanceState(this));
}

Initializing the fragment transaction

Finally, remember to check whether there is a saved state for this fragment before “initializing” the fragment transaction:

SavedState fragmentSavedState = ((NestedFragApp)getActivity().getApplication())
        .getFragmentSavedState(SAVED_STATE_KEY);
if(fragmentSavedState == null){
    if (savedInstanceState == null) {
        getChildFragmentManager().beginTransaction().replace(R.id.nested_fragment_container, 
                NestedFragmentOne.newInstance()).commit();
    } else {
        // use savedInstanceState here to restore state saved in onSaveInstance
    }
}

Note that there are two “saved states” here:

  1. The instance state saved in onSaveInstanceState, which is provided to you by the system via savedInstanceState.
  2. The state you explicitly saved in onPause, which you retrieve from the Application object as fragmentSavedState.

The flow you follow for initializing the fragment is as follows:

  • You first check to see if you had previously explicitly saved state. If true, then you don’t need to do anything.
  • If not, then you proceed to check if the system had saved state for you. If true, then you use the savedInstanceState to restore system-saved state.
  • Only if neither is true, then you initiate the fragment transaction.

Letting go of the saved state

One thing you need to be careful of is to not hold on to the saved fragment state any longer than necessary. For example, when the container Fragment is destroyed, you want to invalidate the back-stack associated with it as well. This sounds obvious but I overlooked it and ended up with strange behaviors.

The best way I found was to “forget” the saved state of a container fragment in its onDestroy:

@Override
public void onDestroy() {
    super.onDestroy();
    ((NestedFragApp) getActivity().getApplication()).setFragmentSavedState(
            SAVED_STATE_KEY, null);
}

With all these steps in place, the app now behaves as one would expect it to. Your position within a back-stack, even within a nested fragment, is remembered even when you navigate away and return to the top level fragment.

Here’s a video showing how the app now behaves:

The source code for the entire series is at github.

Conclusion

  • Consider pro-actively saving your Fragment states in onPause, particularly is the Fragment happens to nest other fragments inside of it.
  • Do not rely solely on the system saving state for you in onSaveInstanceState.
  • Use FragmentManager#saveFragmentInstanceState to save the Fragment state including the back-stack of nested fragment transactions for you.
  • Do not hold on to the saved state any longer than necessary.

This article looked at ActionBar tabs with a ViewPager, but this concept applies to other situations where one would use nested Fragments (Navigation Drawers for example).

Saturday, January 25, 2014

Nested Fragments and the Back Stack

This article is not about the back stack of activities that Android maintains for every task. That stuff has been written about adequately elsewhere. This post is about the back stack of fragment transactions maintained by the FragmentManager and how they relate to nested fragments.

Edit: Other posts in this series at

  1. Part 2
  2. Part 3

Heads-up: If you are using nested fragments, you need to use the support library, even if your app only targets API level 14 and above. This is because nested fragment support was added in API 17, and the feature was back-ported to the support library (revision 11 and later).

TL;DR

The gist of this post can be stated as follows:

There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.

This is from the docs (emphasis mine). Overlooking this can lead to bugs especially when you use nested fragments since the back stack of a child fragment manager could be reset when you least expect it. Remember - if the state of a Fragment is not saved, then by definition, the back stack of fragment transactions managed by that fragment’s child FragmentManager is not saved either.

The Problem

With the advent of fragments, more so nested fragments, the general advice one gets from the developer community is this:

Fragmentize all the everythings!

And with good reason too. Consider the following:

  • If you use ActionBar tabs, the content of each tab is implemented as a Fragment.
  • Each “page” in a ViewPager is often implemented as a Fragment.
  • In navigation drawers, the “content” of each navigation item is expected to be a Fragment.

What this translates to is that what would once be implemented as an Activity now needs to be implemented as a Fragment. This also means that a flow within that Activity, that might have been implemented using Fragments, now needs to be implemented using nested Fragments. Note that by “flow” I simply mean a sequence of screens to establish a particular task.

Now here’s the thing with flows: If a user “goes away” from a flow and later returns to it, it is expected that the user continues from the screen where they left off. Translated into Fragment terminology, this means that if a user navigates away and returns to a flow that is implemented using Fragments, its is expected that the user’s position in the backstack of fragment transactions is retained. However, this isn’t always the case.

Here is a video demonstrating the problem:

The video shows an Activity with three tabs. It is a modified version of an Activity created using the “New Activity” wizard in ADT or Android Studio and specifying “Fixed Tabs + Swipe” navigation. The modification is as follows:

  • The content of the first tab has been modified to make it a “Container” Fragment that in turn contains two nested fragments.
  • When the container fragment is first created, it shows a nested fragment asking you to enter your name.
  • On entering the name and Clicking “Next”, you are presented with another nested fragment asking you to enter your GitHub username.
  • The other two tabs are just simple Fragments - no nesting business there.

Now, notice what happens when I follow this sequence:

  1. Enter name, press Next. Then, enter a github username.
  2. Navigate to the tab titled “Section 2” and then back to “Section 1”.
  3. Navigate to the tab titled “Section 3” and then back to “Section 1”.

Uh! In step 3 above, the back stack was nuked. But hey, it didn’t happen in Step 2. Why so?

Explanation

This example uses a ViewPager. By default, a ViewPager has an “off screen limit” of 1. This means that in addition to the page being displayed, one adjacent page in each direction is kept in memory. So, when you navigate to “Section 2”, everything in “Section 1” is still intact in memory.

When you navigate to “Section 3”, the page corresponding to “Section 1” is torn down. More importantly, since at this point the Activity instance state is not being saved, the Fragment state isn’t saved either. This ties in with what we saw in the “TL;DR” section above. As a result, when you navigate back to “Section 1”, the nested fragment back stack is reset.

Rotation? Task Switching?

Try following this sequence of steps:

  1. Enter name, press Next. Then, enter a github username.
  2. Rotate the device; or switch to another app and return back to this app

Now you’ll see that the back stack is retained. This is because when you rotate the device or switch to another task, the Activity saves its instance state. As a consequence the container fragment does too.

Conclusion

Re-iterating what we started off this post with, keep in mind when you are using nested fragments that a Fragment is guaranteed to save state only when the containing Activity saves its instance state. At other times, the Fragment might simply be torn down.

The code for a sample app illustrating the problem is available at github. The next part of this series will explore ways to overcome this problem.

Saturday, June 8, 2013

Android ListViews: "Hybrid" Choice Behavior

The goal of this post is to use a ListView in a master/detail configuration where both of the following are possible:

  1. Touch a single item to open it.
  2. Long-tap multiple items to choose them and perform a common action on them.

Note that we wish both these to be possible simultaneously, i.e., even as one item is opened, we wish to allow multiple items (possibly including the item that is opened) to be chosen.

This behavior (with some minor variations) is seen in apps like GMail, Google Play Store and the Google I/O 2013 app.

The following screenshot shows what we want to achieve. It shows one opened item (Item 5) and two chosen items (Item 3 and Item 8)

This is what we want to achieve

A note on the terminology

Just to avoid confusion, let's sort of formalize the terminology related to the states an item in the list can be in.

An item is opened when the user is viewing the details about that item. In other words, the details of that item are being displayed in the DetailFragment. In dual-pane mode, there needs to be some visual indication in the ListView to let the user know which one of the items is currently opened.

When an item is chosen, the Contextual Action Bar appears and the user can perform some action on the item. When multiple items are chosen, only the contextual actions that apply to all of them are to be presented. There needs to be some visual indication in the ListView to let the user know which of the items are currently chosen. Needless to say, this indication needs to differ from the that used to indicate the opened item.

Implementation

You might notice that one can achieve the opened behavior using ListView's CHOICE_MODE_SINGLE and the chosen behavior using CHOICE_MODE_MULTIPLE_MODAL. However, it is while trying to combine them that things begin to get challenging, particularly in dual-pane mode. You get either one or the other, but never both. For example, if you use CHOICE_MODE_MULTIPLE_MODAL, then you lose the ability to visually indicate the currently opened item.

The solution I ended up with was to not rely on the CHOICE_MODE_MULTIPLE_MODAL, but rather simulate it myself. The high level steps are as follows:

  1. Create a custom ListAdapter that keeps track of the currently opened item and the currently chosen items
  2. In the getView() (or equivalent) method of your custom ListAdapter, examine the item at the supplied position. If it is the currently opened item, set its visual properties to indicate this. Ditto if it is one of the chosen items.
  3. Listen for clicks and long clicks on your ListView and update the adapter defined in step 1 accordingly- i.e., in your OnItemClickListener implementation, set the opened item and in OnItemLongClickListener, update the list of chosen items.
  4. OnItemLongClickListener is also where you need to start the action mode (getListView().startActionMode()) if it isn't started already.

HybridChoiceAdapter

Here are relevant portions of the code showing how the Adapter should be customized. This code is sparsely commented since I hope that it is self explanatory. Please look at the end of this post for the link to the complete github project.

    /* Keep track of currently opened item and chosen items */
    private Set<Integer> chosenItems = new HashSet<Integer>();
    private int openedItem = -1;

    //...snip...

    public void setItemChosen(int position, boolean chosen) {
        if (!chosen && isItemChosen(position)) {
            chosenItems.remove(position);
        } else if (chosen && !isItemChosen(position)) {
            chosenItems.add(position);
        }
    }

    public boolean isItemChosen(int position) {
        return chosenItems.contains(position);
    }

    public Set<Integer> getChosenItems() {
        return chosenItems;
    }

    public void setOpenedItem(int position) {
        this.openedItem = position;
    }

    public int getOpenedItem() {
        return this.openedItem;
    }

    public boolean isItemOpened(int position) {
        return this.openedItem == position;
    }

    public void clearChoices() {
        chosenItems.clear();
    }

    public void toggleItem(int position) {
        if (isItemChosen(position)) {
            chosenItems.remove(position);
        } else {
            chosenItems.add(position);
        }
    }

    public int getChosenItemsCount(){
        return this.chosenItems.size();
    }

The getView() method

At this point, we have set up the Adapter to keep track of the currently opened item and the chosen items too. We have also exposed methods to manipulate these values. Now, lets look at the code that updates the UI. It is rather simple - all we need to do is, set the background of the row view depending on the opened and chosen states of the current item. Note that an item can be both opened and chosen.

    @Override
    public final View getView(final int position, View convertView,
            ViewGroup parent) {
        View v = convertView;
        /*Normal procedure to inflate the row layout and set its properties goes here*/

        v.setBackgroundResource(0);
        if (isItemOpened(position)) {
            setViewAsOpened(v); //This method sets the appropriate background resource or drawable
        }

        if (isItemChosen(position)) {
            setViewAsChosen(v);//This method sets the appropriate background resource or drawable
        }

        return v;
    }

Listening for clicks on the ListView

In your Activity or Fragment, we listen for both clicks and long clicks and update the adapter accordingly. Again, only the relevant portions of the code are presented here - the full project is shared on github (linked at the end of this post). Here we use a ListAdapter that also implements OnItemLongClickListener.

    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);

        //When an item is clicked, set it as the opened item
        mAdapter.setOpenedItem(position);

        //At this point, clear all choices
        mAdapter.clearChoices();
        if(mActionMode != null){
            mActionMode.finish();
        }
        mAdapter.notifyDataSetChanged();

         // code to show the details fragment goes here
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

        //When an item is long clicked, toggle its chosen state
        //Also update the CAB title to reflect the change in number of chosen items
        mAdapter.toggleItem(position);
        updateActionMode();
        return true;
    }

    private void updateActionMode(){
        if(mActionMode == null){
            mActionMode = getListView().startActionMode(actionModeCallback);
        }

        mActionMode.setTitle(String.format("%d chosen", mAdapter.getChosenItems().size()));
        mAdapter.notifyDataSetChanged();
    }

The previous code snippet also includes step 4 from our high level overview. If the CAB is not already shown, we show it when an item is long clicked.

That mostly covers what we need to do to achieve our goal. There are a few other things that need to be taken care of (for example, clearing the choices whenever the CAB is dismissed - as a result of a contextual action being performed, or otherwise). You can examine the entire code in detail at the github repository.

Variations

There are subtle variations of what action the user has to take to choose an item. For example,

  • The old GMail app (v4.3) displayed check boxes for each row. So you could choose an item either by long-pressing it, or by tapping the check box.
  • In the new GMail app and the Google I/O 2013 app, when no item is chosen, you long-press an item to choose it. After that, even single clicking on other items chooses them. This is different from our implementation where a single-tap always opens an item.

You will need to modify the code for the click listeners if you want to go with one of these variations. The ListAdapter code itself should remain the same.

Turning this into a library?

Well, I gave this a thought too. Exposing the custom Adapter as a library is the easy part. What I couldn't decide upon is how to include the ListView listeners in a library. Developers might wish to extend ListActivity or ListFragment or simply include a ListView in their layouts. Catering to so many requirements is a tough ask (unless I want to provide custom base versions of all these classes ... plus their Sherlock counterparts!)

If anyone has any ideas on how this could be library-ized, please do drop a comment.

GitHub repositroy

The complete source code for this article is available as a sample project on GitHub here.

Saturday, March 9, 2013

Shift to Octopress Postponed

For some time now, I have been toying with the idea of migrating this blog to octopress. I'm already using Markdown to compose my posts, so this is a logical step for me. Also, the idea of a static site that I can take with me wherever I choose to host it appeals to me. Finally, there's the geek factor what with git-based publish workflows and SCSS/liquid customizations and what not. I had even chosen a theme - Fabric - to use as my base theme.

However, it looks like I'm going to have to postpone migration to Octopress. Here are some of the reasons:

Redirection

I'm not sure how to deal with links to my existing posts. I have seen examples of how to do this if you are self-hosting your current blog, or if you are using your own domain name. Neither of these apply to me - my current blog is hosted on blogger, with a .blogspot domain.

Comments

I'm already using Disqus for comments on my blog. I gather that it should be possible to migrate Disqus comments even if your domain changes. I just haven't figured out how.

Importing

I tried using some custom Ruby scripts to import my existing posts into Octopress. While it works, there are two problems I need to deal with:

  • Syntax Highlighting: In blogger, syntax highlighting is done dynamically by Javascript (I use google's prettify.js). While this can be used with static site generators, it is best to stick to introducing syntax highlighting at the post generation time. This is all fine for new posts, but for posts that I import from blogger, this needs additional tweaking. Basically the imported sources are just HTML with some YAML front-matter. I will need to convert it to markdown, add the syntax highlighting annotations and then generate the posts from it.

  • Permalinks: This goes back to the redirection I already mentioned. I also need to customize the permalinks of imported posts to make sure they play nice with redirection. Again, this is not a problem for newer posts. Only the imported posts need to be tweaked.

Looking Forward

I'm not saying that any of the above impossible (or even very difficult) to achieve. It is just that some amount of experimentation is involved. I feel that it would take up more time than I am willing to invest at this point to get things up and running.

This is not to say that I have shelved the idea of shifting to a static site generator altogether. On the contrary. This shift is surely happening. It has just been deferred.

The easiest approach would be to maintain my current blog at http://curioustechizen.blogspot.com/ and start the octopress blog afresh. No imports from blogger. No redirection. Only new posts at the new blog. This approach is not without its downsides though.

So, in a nutshell:

I will surely be migrating to a static site generator like octopress in the near future. But for now, I'm sticking with blogger.


Monday, February 18, 2013

Android Constants: Preference Keys, Actions, Extras and more

The content of this post may seem ... well .. trivial at first, but I have tripped over these so many times that I decided to write it up - at least to keep me reminded of it, if not for any other reason!

If you have written anything more than a HelloWorld app in Android, chances are you have had to work with a plethora of program elements that are represented as Strings. Consider this sampling:

  • Keys for SharedPreferences are Strings
  • Keys for Bundles are Strings
  • Intent extras are Bundles, and hence, if you want to include any extras or retrieve them from Intents, you use their String keys to work with them. Ditto with Fragment arguments
  • Intent and IntentFilter actions (and categories) are Strings themselves
  • . . .

I used to deal with these the lazy way: Declare the keys as public static where they are first used (or where they "belong" logically) and refer to them from wherever they are needed in the code. Examples of the class that is the logical owner might be:

  • The class that broadcasts an Intent
  • The class that creates or sends a non-broadcast Intent (this might be an Activity or Service for example)
  • The class that creates a SharedPreference for editing

However, I quickly found out that often it is not possible to cleanly define these keys as belonging to a particular class. Further, since you might end up with a handful of extras, qualifying the class name becomes tedious - more so since it is likely that Activities or Services can have quite long names. How readable is this snippet?

if(AbstractBaseLiveModeActivity.ACTION_LIVE_UPDATE.equals(intent.getAction())){
    Bundle extras = intent.getExtras();
    if(extras.containsKey(AbstractBaseLiveModeActivity.EXTRA_LIVE_UPDATE_TIMESTAMP)){
        long timestamp = extras.getLong(AbstractBaseLiveModeActivity.EXTRA_LIVE_UPDATE_TIMESTAMP);
        // Do something with timestamp here
    }
}

Constants Almighty

One common solution to this problem is to put everything into one "God" object called Constants or whatever, and prefix the constant names with EXTRA_, ACTION_ or other such descriptive characters to keep them distinct.

public class Constants{
    private Constants(){}

    public static final String ACTION_LIVE_UPDATE = "com.myawesomeapp.action.LIVE_UPDATE";
    // ...

    public static final String EXTRA_LIVE_UPDATE_TIMESTAMP = "com.myawesomeapp.extra.LIVE_UPDATE_TIMESTAMP";
    // ...

}

Now, we've solved the readability problem since we just qualify the constant names with Constant. So, all's well, right?

Wrong!

The problem with this approach is as the number of extras, actions and preference keys increases, the Constants class quickly becomes unmanageable. Also, having to use the ACTION_ and EXTRA_ prefixes hinders usability with some IDE's. For example, with Eclipse, even if you know that you want EXTRA_LIVE_UPDATE_TIMESTAMP, you are forced to type the first six characters without which the code assist will not be able to filter only the extras.

Try using Eclipse to find a particular action or extra from the Intent class if you want to see a real-world example of what I mean.

Split it up into distinct constant files

Here's what I do to keep my code free of such stutter. I simply split up the "God" Constants class into several smaller, easier-to-manage constants classes. Like so:

public class Extras{
    private Extras(){}

    private static String createExtra(String suffix){
        return Constants.NAMESPACE_PREFIX + ".extra."+suffix; //NAMESPACE_PREFIX could be "com.myawesomeapp"
    }

    public static final String LIVE_UPDATE_TIMESTAMP = createExtra("LIVE_UPDATE_TIMESTAMP");
    public static final String LIVE_UPDATE_VALUE = createExtra("LIVE_UPDATE_VALUE");
    public static final String FRIEND_ID = createExtra("FRIEND_ID");
    // ...

}

public class Broadcasts{
    private Broadcasts(){}

    private static String createBroadcast(String suffix){
        return Constants.NAMESPACE_PREFIX + ".broadcast."+suffix; //NAMESPACE_PREFIX could be "com.myawesomeapp"
    }

    public static final String LIVE_UPDATE = createBroadcast("LIVE_UPDATE");
    public static final String FRIEND_OFFLINE = createBroadcast("FRIEND_OFFLINE");
    // ...
}

public class Actions{
    private Actions(){}

    private static String createAction(String suffix){
        return Constants.NAMESPACE_PREFIX + ".action."+suffix; //NAMESPACE_PREFIX could be "com.myawesomeapp"
    }

    public static final String JOIN_CHAT = createAction("JOIN_CHAT");
    // ...
}

You could create classes for Categories, Preference Keys and so on. Note that I differentiate between Broadcasts and Actions because although they are both Intents, they are logically very different. Now, this code snippet changes to:

if(Broadcasts.LIVE_UPDATE.equals(intent.getAction())){
    Bundle extras = intent.getExtras();
    if(extras.containsKey(Extras.LIVE_UPDATE_TIMESTAMP)){
        long timestamp = extras.getLong(Extras.LIVE_UPDATE_TIMESTAMP);
        // Do something with timestamp here
    }
}

Which code snippet would your rather see, especially six months from now when you have to fix a bug? Also note that we've made it far more easy to find the exact action or extra that we want using our IDEs.

Wait, what about constants in XMLs?

Glad you asked. In android, many of these constants are used not only in Java code, but also from XML files.

  • Preference keys can be referenced in preferences XML files via the <PreferenceScreen> element.
  • Intents can be declared in AndroidManifest.xml. This means, the Intent action and categories can be referenced from the manifest.
  • BroadcastReceivers can be declared in AndroidManifest.xml. The <intent-filter> action and categories are referenced here.
  • . . .

This presents a problem since we end up duplicating the constants here. We cannot use our Broadcasts.LIVE_UPDATE constant in XML, so we tend to repeat the constant value:

<intent-filter>
    <action android:name="com.myawesomeapp.broadcast.LIVE_UPDATE"/>
</intent-filter>

This is not good. Any change to any constant involves updating it at multiple places. What's more, these issues are not caught at compile time and can be hard to debug.

Using String resources to avoid duplication

One way to avoid constant literal duplication issue explained in the previous section is to use string resources. You are already using string resources for a variety of strings in your Android app. (Wait, you aren't? I strongly suggest you start doing so right now). All you need to do is add the constants as additional string resources.

To keep things clean, you could keep these constants in their own file under values/ folder - for example constants.xml. In there, you could add

<resources>

    <!-- Broadcast Actions -->
    <string name="broadcast_live_update">com.myawesomeapp.broadcast.LIVE_UPDATE</string>
    <string name="broadcast_friend_offline">com.myawesomeapp.broadcast.FRIEND_OFFLINE</string>

    <!-- Intent Extras -->
    <string name="extra_live_update_timestamp">com.myawesomeapp.extra.LIVE_UPDATE_TIMESTAMP</string>
    <string name="extra_live_update_value">com.myawesomeapp.extra.LIVE_UPDATE_VALUE</string>
    <string name="extra_friend_id">com.myawesomeapp.extra.FRIEND_ID</string>

    <!-- Preference Keys -->
    <string name="pref_key_update_interval">UPDATE_INTERVAL</string>
    <string name="pref_key_theme">THEME</string>

</resources>

Then, you could access these values from XML as follows:

<intent-filter>
    <action android:name="@string/broadcast_live_update"/>
</intent-filter>

<Preference 
    android:key="@string/pref_key_update_interval"
    ... />

And so on. In Java code, you'd access these as:

if(getString(R.string.broadcast_live_update).equals(intent.getAction())){
    // ...
}

mSharedPref.getLong(getString(R.string.pref_key_update_interval));

Unfortunately, this solution has all the disadvantages I mentioned in an earlier section.

Your constants.xml will quickly become a monolithic clutter. This can be addressed by creating a separate XML file for each type of constant - like broadcasts.xml, pref_keys.xml etc. Even if you do that, you will still be accessing all the resources using @string/blah and R.string.blah.

Also, IDE content assist is still a problem. Your resource names will need to be prefixed with action_ or broadcast_ or pref_key_ etc and finding the key you need could be frustrating.

A workable strategy

Here's a strategy I follow to decide how I should declare these constants:

  • For preference keys, prefer string resources. This is because you are most likely to be building your Settings screens with XML anyway.
  • For all other key constants, prefer split constant files.
  • Only if you need to use these from XML, declare them as string resources.

Friday, February 1, 2013

Android: Passing an arbitrary object to a custom View

So, I came across a situation where I wanted to create a custom View in Android (let's call it MyAwesomeView). I had to work with a couple of constraints:

  1. I have to be able to pass in an additional object to MyAwesomeView.
  2. The MyAwesomeView should also be usable from XML.
  3. The MyAwesomeView should be distinct from the application itself - i.e., it should be possible to distribute the MyAwesomeView as a library.

To elaborate a bit on the "pass in an additional object" part: View provides three standard constructors using which you can pass in

  • a Context,
  • an AttributeSet and
  • an int representing the style.

I want to also pass in a BitmapCache object since MyAwesomeView uses lots of Bitmaps and I don't want to encounter the dreaded OutOfMemoryError that goes hand in hand with decoding large bitmaps in an Android app. MyAwesomeView decodes a bitmap only if it is not already present in the cache.

The second constraint makes things really difficult. It is possible to pass in additional "configuration" information to a View by creating custom attributes. However, this obviously cannot be used to pass in an object like a BitmapCache.

Augmenting the Context object with additional information

This solution I came across is as follows:

  • Define an interface BitmapCacheProvider with a single method provideBitmapCache();
  • Make your Activity class implement the interface defined in step 1. Override the interface method to return an appropriate BitmapCache object.
  • In the constructor of MyAwesomeView, check to see if the context object passed in to implements the BitmapCacheProvider interface. If it does - we're good. If not, then fail fast (or disable cacheing - whatever works for you).

In code, here's what this would look like:

/**
 * Interface to be implemented by the Context (Activity etc) in which `MyAwesomeView` runs
 */
public interface BitmapCacheProvider{
    BitmapCache provideBitmapCache();
}

/**
 * An example of an Activity that implements BitmapCacheProvider
 */

public class MyActivity extends Activity implements BitmapCacheProvider{
    //... Life-cycle methods of the Activity here

    @Override
    public BitmapCache provideBitmapCache(){
        //Get your instance of bitmapcache here - probably from your Application
        BitmapCache bitmapCache = ...;
        return bitmapCache;
    }
}

/**
 * Custom View that uses an additional object (BitmapCache) for its configuration.
 */
public class MyAwesomeView extends View{
    private BitmapCache mBitmapCache;

    public MyAwesomeView(Context context){
        init(context, null, 0);
    }

    public MyAwesomeView(Context context, AttributeSet attrs){
        init(context, attrs, 0);
    }

    public MyAwesomeView(Context context, AttributeSet attrs, int style){
        init(context, attrs, style);
    }

    private void init(Context context, AttributeSet attrs, int style){
        try{
            /*
             * Try casting the contex to BitmapCacheProvider. 
             * 
             * If the required interface is not implemented, 
             * it'll throw a ClassCastException
             */
            mBitmapCache = ((BitmapCacheProvider) context).provideBitmapCache();
        } catch(ClassCastException e){
             throw new ClassCastException(context.toString()
                    + " must implement BitmapCacheProvider");
        }

        //At this point, we have the BitmapCacheObject which we can use for further processing.
    }

}

Conclusion:

What we saw in this post was how it is possible to create a custom View in Android, that can take in an arbitrary object in its constructor - and still be usable from XML. Admittedly, it is a bit round-about, but it has its benefits. Here are a few other points worth considering if you are following this approach:

  • In this example, I just augmented the main Activity with the desired interface, but you might need to do this for other classes. Basically, the Context that is passed in to the custom View constructor must be enhanced to implement the interface. What this context is depends on how you are including the custom View.
  • You might argue that the BitmapCache should be part of the custom View and not passed in to it by the application. This depends on the use case. If you have multiple custom Views that require Bitmap cacheing (as is the case with my app), it probably makes sense for the app to maintain the cache. We might not want too maintain too many caches lest the cache overhead cancels out any benefits we derive from having the cache in the first place!