Thursday, March 3, 2011

Custom styles in Android libraries

About nine months ago, Android added library support. It allows you to define multiple Android projects where (at build time) it combines them all into one. These libraries have proven very useful in my work because there's a lot of common code between applications. In some cases, I'm even reusing entire Activities.

But this brings up a problem - how do you restyle an Activity in a library? Say you've got a custom date picker in your library which you want to customize to each application's look and feel. You could subclass the library's Activity and (in code) change the style, but then you miss out on all the advantages of resource qualifiers. You could try subclassing but using your own XML, but then you're tightly coupling your code to the library and will likely end up duplicating code.

The answer is to use attributes and styles/themes. Chances are you've already worked with styles and themes before (if you haven't, you should read up on them), but attributes aren't commonly needed when you're working on a one-off application. Attributes are a tad under-documented, but with attributes, you can define references which are filled in later by styles.

Attributes are one level of abstraction beyond the normal use of XML references in Android. Let's take the simple example of a background: the most direct way to set a background is directly in code (android:background="#FF0000"). Instead of putting the background in the code, you can create a color resource (<color name="red">#FF0000</color>) then reference that (android:background="@color/red"). Going one level of abstraction higher is to create an attribute(<attr name="red" format="color" />), which doesn't directly define the color but can be set by a style or theme (android:background="?style/red").

A high-level layout of the solution looks like this:

1. Define attributes for what XML values you want to customize.

2. Link to those attributes in the library's XML.

3. In the application using the library, create a style which defines values for those attributes.

4. Apply that style as a theme for the application.

Here's a specific example. Suppose we want the background of an Activity to be black in one application and white in another.

1. Create a file in /values/ called "attrs.xml" and add an attribute for the background color:

<resources>
<attr name="myBackground" format="reference|color" />
</resources>

The "format" field defines what sort of values can be entered for this attribute. There's no official documentation, but people have written about possible values.

2. Now let's create a simple layout in /layout/:

<LinearLayout android:layout_height="fill_parent" android:layout_width="fill_parent"
android:background="?attr/myBackground" />

Note how you use the "?" instead of "@" when referencing an attribute.

3. Now switch away from the library to the application using it. Inside, we create a "styles.xml" in /values/, and create a style to fill in the values for the attributes we defined in the library:

<resources>
<style name="LibraryTheme">
<item name="myBackground">@android:color/black</item>
</style>
<resources>

4. Now in AndroidManifest.xml, we set the overall application theme:

<application android:icon="@drawable/icon" android:label="My App"
android:theme="@style/LibraryTheme">

If you wanted to set another theme for the application already, use style parenting to lump both styles together.

Ta-da! Now you've written a layout in a library that can be customized in each application that uses it. You can repeat steps #3 and #4 for any number of applications, setting the background to a different color for each.

There is only one caveat about this method, which is that you need to define all the attributes in each application that uses the library. If you miss an attribute, your app will blow up in your face when the app tries to use it. What's worse, the stack trace when this occurs for missing attributes is complete gibberish and unrelated to the actual problem, making it difficult to debug.

This technique can be used for any attributes from a library. Use your imagination; at this point I've just been using it to style layouts, but you can apply attributes to just anything you can define in XML.

3 comments:

  1. nice! we have yet to pull our stuff out into libraries, but hopefully soon!

    ReplyDelete
  2. Thanks, you saved my day!
    I had the same train of thought (styling in code, subclassing widgets, somehow overriding styles, but how exactly?), was going to ask on SO, and then came across this. Thanks again!

    ReplyDelete
  3. Great Work ! It takes me a day to this! Thanks a lot !

    ReplyDelete