Understand Styles in Android - Part 1. What it is for and how to used it


In android, view hierarchy usually are created via layout xml file. In the file, to instantiate a TextView we write:

1
2
3
4
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 1" />

Usually, we need to specify the visual of the TextView, so we keep adding attributes to the TextView element:

1
2
3
4
5
6
7
8
9
10
11
12
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 1"
android:background="@drawable/category_indicator_background"
android:gravity="center"
android:maxLines="1"
android:paddingBottom="12dp"
android:paddingLeft="22dp"
android:paddingRight="22dp"
android:paddingTop="12dp"
android:textSize="12sp" />

Usually to fully customize visual representation of a view needs a lot of attributes and resources. Such as in this example. we added background, gravity, maxLines, padding and textSize, which is a lot of code.

And if we want to create another TextView with exactly same visual representation, we need to copy all the values again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 1"
android:background="@drawable/category_indicator_background"
android:gravity="center"
android:maxLines="1"
android:paddingBottom="12dp"
android:paddingLeft="22dp"
android:paddingRight="22dp"
android:paddingTop="12dp"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 2"
android:background="@drawable/category_indicator_background"
android:gravity="center"
android:maxLines="1"
android:paddingBottom="12dp"
android:paddingLeft="22dp"
android:paddingRight="22dp"
android:paddingTop="12dp"
android:textSize="12sp" />

Obviously, in this piece of code, there are a lot of duplications. We need to compare all the values to figure out the 2 TextViews have the same visual. If we want to change the style, we need to update 2 TextViews. And the last, if we want to create the 3rd TextView or even more ones, we need copy the code again and again, which makes the issue become more troublsome.

In a short word, the code has bad readability, bad maintainability, bad reusability. In the book Refactor, we know that code redundancy is bad smell. To mitigate the issue, we need to extract the shared code into another “unit”, and replace all the occurrences with the reference.

In Android layout xml, the extract “unit”, which represents the shared attributes, are called Style. After introduced Style, we have:

1
2
3
4
5
6
7
8
9
10
11
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 1"
style="@style/TextView.Customized"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample 2"
style="@style/TextView.Customized"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
<resources>
<style name="TextView.Customized">
<item name="android:gravity">center</item>
<item name="android:background">@drawable/category_indicator_background</item>
<item name="android:paddingLeft">22dp</item>
<item name="android:paddingRight">22dp</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:textAppearance">@style/CategoryIndicator.Text</item>
<item name="android:textSize">12sp</item>
<item name="android:maxLines">1</item>
</style>
</resources>

Well, this is the basics about the Style: why we need it and how it is used.

Dynamically inflates UI in Android App

There is a fascinating idea that inflates UI according to an android layout xml downloaded from server. According to the Android API, it looks quite feasible.

One of LayoutInflate.inflate method overloads accept Layout Xml as XmlPullParser.

And XmlPullParser can wrap around an input stream, so as consequence, following code seems to be working:

Inflate view on the fly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DynamicView extends FrameLayout {
public DynamicView(Context context, InputStream layoutData) throws XmlPullParserException {
super(context);
createView(context, layoutData);
}
private void createView(Context context, InputStream layoutData) throws XmlPullParserException {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser parser = factory.newPullParser();
parser.setInput(layoutData, "UTF-8");
inflater.inflate(parser, this, true);
}
}

The code looks great, compiling looks fine, but when the code excuted, an exception is thrown by the inflater.

According to the LayoutInflater document, this approach won’t work(at least for now, it won’t).

For performance reasons, view inflation relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not currently possible to use LayoutInflater with an XmlPullParser over a plain XML file at runtime.

Actually, Android compiler compiles the layout xml files into binary xml block, which has convert attributes into some special format. And in Android SDK, the LayoutInflater uses XmlResourceParser instead of plain XmlPullParser, which is created by XmlBlock.

XmlBlock is an internal class used in Resources to cache the binary xml document.

And there is no way to create XmlResourceParser or other classes to inject custom behavior into this process. Personally assume that it is pretty much related to the Android Resource and Theming mechanism, there are quite a number cross references between resources. To make it works in a efficient way, Android Runtime did a lot work, such as cache and pre-processing. To override this behavior require quite a number of work and need to aware of potential performance issue. Since the inflating could happen quite often during navigation.

As a not that fansy alternative, UI based on HTML hosted in WebView could be considered.

Multiple Project Summary Reporting Standard - cctray xml feed

CCTray.xml is an RSS-like CI server build status xml feed, which is originally developed for CruiseControl.net.
ThoughtWorks declared it in a standard called “Multiple Project Summary Reporting Standard”, which now have become some kind of unofficial standard of CI server feed that is widely supported by all kind of popular CI servers.

You can find the feed as described below:

And according to cc_dashboard, there are some exceptions that are not included in the document.

  • An additional “Pending” activity
  • An additional “Unknown” status. I’ve seen Unknown reported by CruiseControl.rb when project builds are serialized (“Configuration.serialize_builds = true” set in .cruise/site_config.rb) and one build is waiting for another build to finish. I’ve seen Unknown reported by Hudson when a project is disabled.

The following is a patched version of Multiple Project Summary Reporting Standard.

Multiple Project Summary Reporting Standard

Introduction

Various Continuous Integration monitoring / reporting tools exist. Examples are:

These tools work by polling Continuous Integration servers for summary information and presenting it appropriately to users.

If a Continuous Integration server can offer a standard summary format, and a reporting tool can consume the same, then we get interoperability between reporting tools and CI Servers.

Description

Summary information will be available as a plain XML string retrievable through an http GET request.

The format of the XML will be as follows:

Summary

A single node, the document root, which contains 0 or many node.

Each may have the following attributes:






































































































name description type required
name The name of the project string yes
activity The current state of the project string enum : Sleeping, Building, CheckingModifications
yes
lastBuildStatus A brief description of the last build string enum : Pending, Success, Failure, Exception, Unknown yes
lastBuildLabel A referential name for the last build string no
lastBuildTime When the last build occurred DateTime yes
nextBuildTime When the next build is scheduled to occur (or when the next check to see whether a build should be performed is scheduled to occur) DateTime no
webUrl A URL for where more detail can be found about this project string (URL) yes

Clients that consume this XML should not rely on any optional attribute being present, and should degrade their functionality gracefully.

Example

CCTray.xml Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Projects>
<Project
name="SvnTest"
activity="Sleeping"
lastBuildStatus="Exception"
lastBuildLabel="8"
lastBuildTime="2005-09-28T10:30:34.6362160+01:00"
nextBuildTime="2005-10-04T14:31:52.4509248+01:00"
webUrl="http://mrtickle/ccnet/"/>
<Project
name="HelloWorld"
activity="Sleeping"
lastBuildStatus="Success"
lastBuildLabel="13"
lastBuildTime="2005-09-15T17:33:07.6447696+01:00"
nextBuildTime="2005-10-04T14:31:51.7799600+01:00"
webUrl="http://mrtickle/ccnet/"/>
</Projects>

Schema

cctray.xml Schema
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Projects">
<xs:complexType>
<xs:sequence>
<xs:element name="Project" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="activity" use="required">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="Sleeping" />
<xs:enumeration value="Building" />
<xs:enumeration value="CheckingModifications" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="lastBuildStatus" use="required">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="pending"/>
<xs:enumeration value="Exception"/>
<xs:enumeration value="Success"/>
<xs:enumeration value="Failure"/>
<xs:enumeration value="Unknown"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="lastBuildLabel" type="xs:NMTOKEN" use="required" />
<xs:attribute name="lastBuildTime" type="xs:dateTime" use="required" />
<xs:attribute name="nextBuildTime" type="xs:dateTime" use="optional" />
<xs:attribute name="webUrl" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>