2 articles Tag tips

Example of custom states in Android components

A while ago, I built a form based app with lots of input fields and all the things that come with that, like validation and error messages.

In one of the situations, if a section is invalid, the field labels should turn red. Easy right? Here’s the example layout:

And a naive way to switch the label colors when there’s an error:

But this is kinda ugly. We need to get “not-error” color from each component, store it, switch the color to red and if the error is fixed, restore the colors again. There’s too much code that deals with updating the view in here and it only gets worse if we add more labels.

Using selectors

You’ve probably worked with selectors before. In this particular case we’ll be using a ColorStateList selector to change the color of the TextView. First, here’s the selector:

This is just like any other ColorStateList xml. You place it in the res/color/ folder of your project. This selector has one thing extra though: the app namespace and the app:state_error state. In effect this selector says:

  • If the state is error the color used is color_error as defined in color.xml
  • If the state is pressed, for example if the TextView is clickable and you click on it, the color used is color_pressed
  • By default the color is default_color

By know you probably see where I’m going…If we can change the state of the TextView to app:state_error then the color should be set by the Android framework to the color_error that is defined. But how do we do that?

First we have to actually define the new state in res/values/attrs.xml:

Then we should actually apply that state. We can do that in two ways:

  1. Extend TextView
  2. Extend the LinearLayout containing the TextViews

In this case we’ll extend the LinearLayout like this:

This code should be easy to follow. We add an extra property mError to keep track of the error state. At line 3 a constant is defined containing an array of states that are set when mError is true. Then it’s just a matter of overriding onCreateDrawableState() which takes an int called extraSpace. Since potentially we’d like to add one extra state if the mError flag is set, the code calls super.onCreateDrawableState() with extraSpace + 1. Then, depending on the mError flag the error state is added to the list of states.

The final layout using this new component looks like this:

Things to notice in this layout:

  • In stead of LinearLayout, ErrorStateLinearLayout is used as the main container
  • Each TextView has android:duplicateParentState=”true” meaning that the TextView will inherit any states from the parent view group.
  • Each TextView has it’s textColor set to error_color which would be the color state selector

Now we can finally update the view when there’s an error like this:

Much cleaner code, less bugs, yay for us!

Conclusion

By leveraging core Android principles, in this case using selectors, we can simplify our code a lot. By moving things that are related to the view out to xml resources not only our code gets cleaner, but we could also use the resources system to customize the ui based on the device configuration.

PS: ViewGroup has android:addStatesFromChildren which is equally useful!

Comment on this post on Google+

Correct Intent to compose an email on Android

Starting a compose window to write an email in your favorite email client on Android is easy.
It amazes me however that when you search for it on stackoverflow most first hitters are suggesting to use an intent with the mime type set to text/plain (or worse) See http://stackoverflow.com/questions/6450097/how-to-send-email-by-clicking-text-view-links/6450131#6450131http://stackoverflow.com/questions/7551600/how-can-i-send-an-email-through-my-exchange-activesync-mail-account or here http://stackoverflow.com/questions/10546252/unable-to-send-email-from-android-application-on-real-device for example.Being an intent, that code expresses that you like to perform a SEND action on ANYTHING that can handle text/plain or whatever mime type. So apps like Dropbox etc will rightfully also pop up.What you really want is to use a mailto: uri in the data, since that expresses that you want to….MAIL TO SOMEBODY. So you set up the intent like this:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("mailto:hugo@googleplus.com"));
// you can add extra's like subject, text, etc

I’ve been using this for ages (since Android 1.5) and it should work fine. Of course your favorite mail app should register for the mailto scheme, but I’m sure any decent mail app will.

I did find an answer on SO that actually mentions this, not all hope is lost: http://stackoverflow.com/questions/2007540/how-to-send-html-email

Don’t use text/plain if you want to send an email. A droid will die every time you do :(