Getting Ready

The first and I think most important goal of this tutorial is to work!

I was frustrated each time, I tried one of the tutorials on the android website because 9/10 times they did not work. Google might be very good with search engines, but when it comes to writing tutorials they suck. Sorry... The other thing I really did not like was, that they just let you copy and paste code and then add only a small description. I won't do it that way. We will build this app together. Let's go!

The complete source code of this tutorial can be found here.


Defining the models

Before we start hacking code, we shortly think about what we want to achieve. This should be about our own little book collection. A very, very simple book collection. So what are the basic specs? I would say a book should have a title and also an author. Sounds good. And just to complete the picture these authors should have names. That's enough (I can say so, because I am the author). Hey and with this little spec in mind, we can go and write our model classes. First the one, for the author.

public class Author extends Model {
    
    protected CharField mName;
    
    public Author() {
        super();
        
        mName = new CharField(80);
    }
    
}

This now might look somewhat different from what you would normally write in Java. Probably you would have chosen String for the name field and set it's visibility to private. Unfortunately androrm then wouldn't be able to

  1. see the field and
  2. know how to write it to the database.

If you use the field types provided by androrm we have a different situation. What I've done here is

  1. set the visibility to protected, so that the Model class can access this field
  2. created a zero-argument constructor, that can be called to initialize all fields
  3. called super() in order to also set-up everything in the Model superclass and
  4. initialized my field providing a parameter for the maximum length.

Next up: the book model.

public class Book extends Model {
    
    protected CharField mTitle;
    protected ForeignKeyField<Author> mAuthor;
    
    public Book() {
        super();
        
        mTitle = new CharField(80);
        mAuthor = new ForeignKeyField<Author>(Author.class);
    }
    
}

In the Book class we're doing basically the same. But we also define a ForeignKeyField, that links the books to authors. This field is everything, that androrm needs, to handle this relation for you.


Getting ready for queries

Actually you don't have to add anything in order to get your models ready for queries. The Modelclass already defines the objects() method, but I would say it is handy and also good style, to implement this method in every class. Normally you would have to hand in a Context instance and the Class object of your model. You can easily get rid of the second parameter, by implementing your objects() method, that will automatically fill in the class for you. On the author class this looks like...

public class Author extends Model {
    
    public static final QuerySet<Author> objects(Context context) {
        return objects(context, Author.class);
    }
    
    protected CharField mName;
    
    public Author() {
        super();
        
        mName = new CharField(80);
    }
    
}

Not much work, but looks way better in the code. I think you can figure out yourself, how to do this for the Book class. We'll get to the details of the objects() method and QuerySets later.


Tabs, tabs and tabs

This is, how our activity should look like, when it's finished. So as you see, we have to create three tabs and add some content to them. Luckily android gives us the TabActivity.

As you might also have noted I was too lazy, to add three different pictures for the tabs and just used the same one for all of them. If you want to be as lazy, as I, then go ahead and save these two images.

Put these two files into the res/drawable folder of your android project. If there is no plain drawable folder, create it, as we will need it anyway.

In the same folder now also create a file called tab_button.xml. This will be the standard layout file, we use for tabs. The first time I created an xml file in the drawable folder I was like WTF? What the heck is that supposed to be. But actually it is a quite smart mechanism. You can define different items, that will be used under different conditions. For the tabs that means, that we only want to see the button_selected image, if the tab is actually selected.

In android you do this by defining states. When looking up the file, it will compare the current state with the one given in the file and stop on the first match. You don't have to define all states at all times, but only these, you care about. In our case, we want to stop the lookup, if our state is selected and the tab is also selected. This is why the item for the selected state will come first in the file.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/button_selected"
          android:state_selected="true" />

    <item android:drawable="@drawable/button_deselected" />

</selector>

So now, that you have created the drawable for your tabs, you have to create the actual tab activity. I won't describe that here in detail, as this tutorial is actually not too bad (check here). Make sure, you create three activities, called

  • AddBook.java
  • BrowseAuthors.java
  • BrowseBooks.java

that we will use as our different views. For now all these activities can just inherit from Activity. We'll change this later on. Also do not forget to add all of them to the AndroidManifest.xml! I forget this all the time and am wondering about the exception I'm getting.

The code for the plain TabActivity looks like this:

public class Tabs extends TabActivity {
    
    /** Called when the activity is first created. **/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // always call this before you do something else
        // in order to have the correct database layout
        syncDB();
        
        createTab(BrowseBooks.class, "Browse Books", R.drawable.tab_button);
        createTab(BrowseAuthors.class, "Browse Authors", R.drawable.tab_button);
        createTab(AddBook.class, "Add Book", R.drawable.tab_button);
        
        getTabHost().setCurrentTab(0);
    }
    
    /** Creates a new tab with the given activity class as content **/
    private void createTab(Class<? extends Activity> activity, 
            String tabAlias,
            int drawable) {
        
        Intent intent = new Intent().setClass(this, activity);
        
        Resources res = getResources();
        TabHost tabHost = getTabHost();
        
        TabHost.TabSpec spec = tabHost
            .newTabSpec(tabAlias.toLowerCase().replace(" ", "_"))   // this is optional
            .setIndicator(tabAlias, res.getDrawable(drawable))      // sets the drawable
            .setContent(intent);                                    // should be self-explaning
        
        tabHost.addTab(spec);
    }
    
    /** Will set up the database definition **/
    private void syncDB() {
        List<Class<? extends Model>> models = new ArrayList<Class<? extends Model>>();
        models.add(Author.class);
        models.add(Book.class);
        
        DatabaseAdapter.setDatabaseName("books_db");
        DatabaseAdapter adapter = new DatabaseAdapter(getApplicationContext());
        adapter.setModels(models);
    }
}

The only thing, that's still missing is the correct layout in the main.xml file. I think it should be very similar to the one in the google tutorial, but I'll post it here anyway.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- 
        each tab activity has to contain one tab host
        this is the overall container for the tabs bar
        and it's contents
    -->
    <TabHost
        android:id="@android:id/tabhost"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">
    
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
        
            <!-- 
                these are the actual tabs or at least their
                container
            -->
            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content" />
                
            <!--
                last but not least the container for
                the activities or views of each tab
            -->
            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content" />
        
        </LinearLayout>
        
    </TabHost>

</LinearLayout>

It's really important, that you choose the exact same ids for the objects, as android searches exactly for these, when it handles a TabActivity. Only that way methods like getTabHost() that we are using in the activity class can work.

One side note If you encounter the problem, that you can't read the text, because android chooses white text, for a white background and grey text for the grey background, then this is not your fault. On some phone, there seems to be an error, that produces this exact behavior. To overcome the problem add a value below 5 as targetSdkVersion in your AndroidManifest.xml file.

<uses-sdk
    android:minSdkVersion="7"
    android:targetSdkVersion="4" />

I'm not to sure, what exactly happens. I think some compatibility features are being disabled, if you specify targetSdkVersion, but at least, you got nice looking tabs afterwards.


That was the first part of our tutorial. I hope, you had no trouble following the steps. If you struggled at some point please let me know, so that I can change it to the better. You should now have you models set-up and a plain tab activity, with currently no contents, but nice tabs.

To create some content, continue to the next step of the tutorial, to learn how to create a form and validate its data.

androrm on github

Django is a registered trademark of the Django Foundation. Android is a registered trademark of Google Inc.