Upward Mobility: Android for iOS Developers, Part 2

Turning Hello World into something useful

When we last left our application, it was running on the emulator, but didn’t do much.  This week, we’ll add some more controls to our activity and wire up some functionality.

As a reminder, activities are roughly equivalent to view controllers in iOS. Right now, there’s not much in our activity, because all we have is a single label in our activity. As iOS developers, we’re used to never looking at our XIB files in the raw, because they’re pretty much human-unreadable. By contrast Android layout files (which end up in the res/layout folder in a standard Eclipse project) are both readable and intended to be edited. At the moment, here’s what ours looks like:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

</RelativeLayout>


It’s a basic layout with a text view in the upper left hand corner. To start, we can center the text view and bump it up against the top border. If you drag it around in the graphical layout, you’ll see the same kinds of guides as you do in Interface Builder, and it’s pretty easy to just drag the control where you want it, so that it ends up looking like this:

Screen Shot 2013-06-16 at 6.20.04 PMNow we can switch back to the raw XML view, and see how things have changed:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="@string/hello_world" />

There are now two new attributes, aligning to the parent top and horizontally centering the control. Now we can add a control that lets the user select just which world they are saying hello to. The right control for this is a spinner, which we can drop right under the text view. By default, the spinner will be called spinner1, but I’d like to give it a more informative name, so I go over to the Properties pane and change the id field to @+id/planetSpinner. When I do this, I immediately get a prompt:

Screen Shot 2013-06-16 at 6.27.45 PM

 

What is happening is that Eclipse is going to globally change that id, including in a bunch of autogenerated code. As a result, it wants to make sure that we want to do this. If you say yes, you can see that new id has shown up in the XML version of the layout:

    <Spinner
        android:id="@+id/planetSpinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="23dp" />

Now we’re ready to go into our MainActivity class and do something with this control, such as populate it with a list of planets. If we look at MainActivity.java, we can see the onCreate method:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

The setContentView call is what relates the Java class to the layout file. Once we’ve set the content view, we can get a handle on objects inside the layout and play with them. The first thing we need is an array of string that we can feed into the Spinner. In Android, this is usually done through a string resource file (similar to a string properties file in iOS). You’ll find a strings.xml file hiding in the res/values folder. Once we add our new string array, it will look like this:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Oreilly Demo One</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string-array name="planetNames">
        <item>Pern</item>
        <item>Barsoom</item>
        <item>Ringworld</item>            
        <item>Equestria</item>            
    </string-array>
</resources>

We now have four planets that our user can choose from. Before we go any further, make sure that you have saved both the string resource and layout files! When you do, a magic class file called R will be updated, creating ids automatically for these new entities we’ve added:

   public static final class array {
        public static final int planetNames=0x7f070000;
        public static final int pref_example_list_titles=0x7f070001;
        public static final int pref_example_list_values=0x7f070002;
        public static final int pref_sync_frequency_titles=0x7f070003;
        public static final int pref_sync_frequency_values=0x7f070004;
    }
.
.
.
    public static final class id {
        public static final int action_settings=0x7f0a0002;
        public static final int planetSpinner=0x7f0a0001;
        public static final int textView1=0x7f0a0000;
    }

The last step is to give those planet names to our new spinner, in our onCreate method:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Spinner spinner = (Spinner)findViewById(R.id.planetSpinner);
     ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
             R.array.planetNames, android.R.layout.simple_spinner_item);
     adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
     spinner.setAdapter(adapter);       

    }

Walking through the code, we first get a handle on the spinner by using the automatically generated id in the R class file. We create an array adapter, which is a special resource that is going to return spinner items from our planet names. Finally, we tell the spinner to use that adapter, and when we rerun the code in the emulator, we get our new drop-down.

Screen Shot 2013-06-16 at 8.47.40 PMPersonally, I think that this is a bit complicated for a simple drop-down, requiring me to create an adapter to provide a simple list of strings, and is representative of how Java-based platforms tend to over-architect things that should be easy. I’m sure the adapter idea is there so that you can put more complicated things such as images into your drop-down, but in iOS, you normally have a single way of doing something that covers the 95% use case, and a more involved way to do the complex stuff.  Why I can’t just hand a spinner an ArrayList of strings is beyond me. Let it handle the adapters and such for me, at least for the simple cases.

In any event, editorializing aside, next week we’ll find out how to listen for changes on that spinner and use it to drive actions in our activity.

tags: , , , ,