Android – Layout reuse made simple
The Android library is constantly growing. New widgets are built, and existing ones get deprecated. We’ll see how we can make XML layouts versioning more maintainable and more OO using the <include/>
element. For that we’ll take as an example the ActionBarSherlock SearchView.
Google just recently announced the release of Android KitKat (android OS version 4.4). With it come new tools, new widgets, some eye candy and some bug fixes.
Thanks to the success of the Nexus and Galaxy phones, Android Jelly Bean is now the predominant OS on consumer mobile devices. However, Gingerbread (Android OS version 2.3, dating back from 2010) still represents 33% of the Android OS market share. That is a significant enough percentage to strongly encourage companies to develop applications compatible with older versions of Android.
What options do you have when a widget does not exist on prior versions of the targeted OS?
- Wait for Google to release the latest and greatest compatibility library.
- Build your own backwards-compatible widget from scratch.
- Clone the desired code for the widget and adapt it to make the copy backwards compatible.
- Decide not to include the widget in prior OS versions of the application.
- Find a 3rd-party backwards-compatible version of the widget.
Jake Wharton’s ActionBarSherlock (ABS) is a library meant to provide backwards compatibility functionality for the Action Bar (The navigation bar). Many popular applications use ABS, including for instance Foursquare, SoundHound and Stitcher.
ABS does most of the heavy lifting, providing consistency across versions. The objects however, despite a similar functionality, are not of the same type. This means dealing both with handling different instances in the code, and using the proper objects in the layouts.
The SearchView example:
Let’s take the out of the box Android SearchView (available since Honeycomb – API 11), and the ABS SearchView which allows you to have the same functionality with previous versions of Android (down to API 7).
Note that the ABS SearchView (ABS versions >= 4.3) widget specifically emulates the SearchView implementation of Android Ice Cream Sandwich (API 14).
When writing the layout, you may have:
[code lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SearchView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="text"/>
</RelativeLayout>
[/code]
Since we didn’t specify the namespace, the layout compiler will inflate the XML file using an instance of android.widget.SearchView. This will create problems when running on older devices where you’ll want to leverage the ABS SearchView from the package com.actionbarsherlock.widget. And so to have the application behave properly, we need two layouts: one for pre-ICS and one for post-ICS.
In our case, it looks simple; but what if we have a larger layout? With a lot of configuration parameters?
Maintainability/modification of the layout can become a serious issue. A better solution would be if we only had to rewrite the section containing the SearchView element. That part of the layout could change depending on the API version.
Thankfully, Android provides an inheritance mechanism for layouts. You can include layouts in other layouts using the <include/>
tag (more info here).
Using <include/>
we can write our base layout, and instead of directly using the <SearchView/>
element, we replace it with an <include/>
and let the built-in Android resource versioning do it’s magic.
We’ll need two new layout files:
res/layout/searchview.xml:
[code lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<com.actionbarsherlock.widget.SearchView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
[/code]
res/layout-v14/searchview.xml:
[code lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<SearchView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
[/code]
and our base layout res/layout/base_layout.xml becomes:
[code lang=”xml”]
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="text"
android:layout="@layout/searchview"/>
</RelativeLayout>
[/code]
With this we don’t have to maintain two base layouts files anymore, and we can almost forget about those two we just created.
The attributes you set in the <include/>
element will override the properties set in the SearchView elements of the searchview.xml layouts. This means that you can keep the configuration of the SearchView in your base layout.
This pattern also works with the menu. You can set the android:actionLayout property to @layout/searchview and the proper view will be used according to which device is being used.
As I mentioned earlier, in your Java code you will still need to deal with the two different types of objects; checking the build version number when implementing the functionality associated with the widget.
Object Oriented design:
The capability to reuse snippets of code is a very powerful and efficient tool for programing in an Object Oriented pattern. The relative ease with which it is done should be a good incentive for programmers to build reusable & self-contained widgets.
The SearchView is only an example. You can also use includes in skeleton code to provide a basic head/body/footer layout; with different sub layouts for different screen sizes or densities.
More…
ViewStubs are worth looking into. They provide the similar functionality of being used as placeholders in a layout; the main difference being that the ViewStub layout is not inflated automatically with it’s parent. That has to be done programatically.