Author Archive

Get Started Developing For Android With Eclipse, Reloaded

Advertisement in Get Started Developing For Android With Eclipse, Reloaded
 in Get Started Developing For Android With Eclipse, Reloaded  in Get Started Developing For Android With Eclipse, Reloaded  in Get Started Developing For Android With Eclipse, Reloaded

In the first part of this tutorial series, we built a simple brew timer application using Android and Eclipse. In this second part, we’ll continue developing the application by adding extra functionality. In doing this, you’ll be introduced to some important and powerful features of the Android SDK, including Persistent data storage, Activities and Intent as well as Shared user preferences.

To follow this tutorial, you’ll need the code from the previous article. If you want to get started right away, grab the code from GitHub and check out the tutorial_part_1 tag using this:

1 Starting Point in Get Started Developing For Android With Eclipse, Reloaded

$ git clone git://github.com/cblunt/BrewClock.git
$ cd BrewClock
$ git checkout tutorial_part_1

Once you’ve checked out the code on GitHub, you’ll need to import the project into Eclipse:

  1. Launch Eclipse and choose File → Import…
  2. In the Import window, select “Existing Projects into Workspace� and click “Next.�
  3. On the next screen, click “Browse,� and select the project folder that you cloned from GitHub.
  4. Click “Finish� to import your project into Eclipse.

After importing the project into Eclipse, you might receive a warning message:

Android required .class compatibility set to 5.0.
Please fix project properties.

If this is the case, right-click on the newly imported “BrewClock� project in the “Project Explorer,� choose “Fix Project Properties,� and then restart Eclipse.

Getting Started With Data Storage

Currently, BrewClock lets users set a specific time for brewing their favorite cups of tea. This is great, but what if they regularly drink a variety of teas, each with their own different brewing times? At the moment, users have to remember brewing times for all their favorite teas! This doesn’t make for a great user experience. So, in this tutorial we’ll develop functionality to let users store brewing times for their favorite teas and then choose from that list of teas when they make a brew.

To do this, we’ll take advantage of Android’s rich data-storage API. Android offers several ways to store data, two of which we’ll cover in this article. The first, more powerful option, uses the SQLite database engine to store data for our application.

SQLite is a popular and lightweight SQL database engine that saves data in a single file. It is often used in desktop and embedded applications, where running a client-server SQL engine (such as MySQL or PostgreSQL) isn’t feasible.

Every application installed on an Android device can save and use any number of SQLite database files (subject to storage capacity), which the system will manage automatically. An application’s databases are private and so cannot be accessed by any other applications. (Data can be shared through the ContentProvider class, but we won’t cover content providers in this tutorial.) Database files persist when the application is upgraded and are deleted when the application is uninstalled.

We’ll use a simple SQLite database in BrewClock to maintain a list of teas and their appropriate brewing times. Here’s an overview of how our database schema will look:

+-------------------------------------+
| Table: teas                         |
+------------+------------------------+
| Column     | Description            |
+------------+------------------------+
| _ID        | integer, autoincrement |
| name       | text, not null         |
| brew_time  | integer, not null      |
+------------+------------------------+

If you’ve worked with SQL before, this should look fairly familiar. The database table has three columns: a unique identifier (_ID), name and brewing time. We’ll use the APIs provided by Android to create the database table in our code. The system will take care of creating the database file in the right location for our application.

Abstracting the Database

To ensure the database code is easy to maintain, we’ll abstract all the code for handling database creation, inserts and queries into a separate class, TeaData. This should be fairly familiar if you’re used to the model-view-controller approach. All the database code is kept in a separate class from our BrewClockActivity. The Activity can then just instantiate a new TeaData instance (which will connect to the database) and do what it needs to do. Working in this way enables us to easily change the database in one place without having to change anything in any other parts of our application that deal with the database.

Create a new class called TeaData in the BrewClock project by going to File → New → Class. Ensure that TeaData extends the android.database.sqlite.SQLiteOpenHelper class and that you check the box for “Constructors from superclass.â€�

2 Create Teadata Class1 in Get Started Developing For Android With Eclipse, Reloaded

The TeaData class will automatically handle the creation and versioning of a SQLite database for your application. We’ll also add methods to give other parts of our code an interface to the database.

Add two constants to TeaData to store the name and version of the database, the table’s name and the names of columns in that table. We’ll use the Android-provided constant BaseColumns._ID for the table’s unique id column:

// src/com/example/brewclock/TeaData.java
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.provider.BaseColumns;

public class TeaData extends SQLiteOpenHelper {
  private static final String DATABASE_NAME = "teas.db";
  private static final int DATABASE_VERSION = 1;

  public static final String TABLE_NAME = "teas";

  public static final String _ID = BaseColumns._ID;
  public static final String NAME = "name";
  public static final String BREW_TIME = "brew_time";

  // …
}

Add a constructor to TeaData that calls its parent method, supplying our database name and version. Android will automatically handle opening the database (and creating it if it does not exist).

// src/com/example/brewclock/TeaData.java
public TeaData(Context context) {
  super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

We’ll need to override the onCreate method to execute a string of SQL commands that create the database table for our tea. Android will handle this method for us, calling onCreate when the database file is first created.

On subsequent launches, Android checks the version of the database against the DATABASE_VERSION number we supplied to the constructor. If the version has changed, Android will call the onUpgrade method, which is where you would write any code to modify the database structure. In this tutorial, we’ll just ask Android to drop and recreate the database.

So, add the following code to onCreate:

// src/com/example/brewclock/TeaData.java
@Override
public void onCreate(SQLiteDatabase db) {
  // CREATE TABLE teas (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, brew_time INTEGER);
  String sql =
    "CREATE TABLE " + TABLE_NAME + " ("
      + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
      + NAME + " TEXT NOT NULL, "
      + BREW_TIME + " INTEGER"
      + ");";

  db.execSQL(sql);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
  onCreate(db);
}

Next, we’ll add a new method to TeaData that lets us easily add new tea records to the database. We’ll supply the method with a name and brewing time for the tea to be added. Rather than forcing us to write out the raw SQL to do this, Android supplies a set of classes for inserting records into the database. First, we create a set of ContentValues, pushing the relevant values into that set.

With an instance of ContentValues, we simply supply the column name and the value to insert. Android takes care of creating and running the appropriate SQL. Using Android’s database classes ensures that the writes are safe, and if the data storage mechanism changes in a future Android release, our code will still work.

Add a new method, insert(), to the TeaData class:

// src/com/example/brewclock/TeaData.java
public void insert(String name, int brewTime) {
  SQLiteDatabase db = getWritableDatabase();

  ContentValues values = new ContentValues();
  values.put(NAME, name);
  values.put(BREW_TIME, brewTime);

  db.insertOrThrow(TABLE_NAME, null, values);
}

Retrieving Data

With the ability to save data into the database, we’ll also need a way to get it back out. Android provides the Cursor interface for doing just this. A Cursor represents the results of running a SQL query against the database, and it maintains a pointer to one row within that result set. This pointer can be moved forwards and backwards through the results, returning the values from each column. It can help to visualize this:


SQL Query: SELECT * from teas LIMIT 3;

+-----------------------------------+
|  _ID  |  name       |  brew_time  |
+-----------------------------------+
|    1  |  Earl Grey  |          3  |
|    2  |  Green      |          1  | <= Cursor
|    3  |  Assam      |          5  |
+-------+-------------+-------------+

In this example, the Cursor is pointing at the second row in the result set (Green tea). We could move the Cursor back a row to represent the first row (Earl Grey) by calling cursor.moveToPrevious(), or move forward to the Assam row with moveToNext(). To fetch the name of the tea that the Cursor is pointing out, we would call cursor.getString(1), where 1 is the column index of the column we wish to retrieve (note that the index is zero-based, so column 0 is the first column, 1 the second column and so on).

Now that you know about Cursors, add a method that creates a Cursor object that returns all the teas in our database. Add an all method to TeaData:

// src/com/example/brewclock/TeaData.java
public Cursor all(Activity activity) {
  String[] from = { _ID, NAME, BREW_TIME };
  String order = NAME;

  SQLiteDatabase db = getReadableDatabase();
  Cursor cursor = db.query(TABLE_NAME, from, null, null, null, null, order);
  activity.startManagingCursor(cursor);

  return cursor;
}

Let’s go over this method in detail, because it looks a little strange at first. Again, rather than writing raw SQL to query the database, we make use of Android’s database interface methods.

First, we need to tell Android which columns from our database we’re interested in. To do this, we create an array of strings—each one of the column identifiers that we defined at the top of TeaData. We’ll also set the column that we want to order the results by and store it in the order string.

Next, we create a read-only connection to the database using getReadableDatabase(), and with that connection, we tell Android to run a query using the query() method. The query() method takes a set of parameters that Android internally converts into a SQL query. Again, Android’s abstraction layer ensures that our application code will likely continue to work, even if the underlying data storage changes in a future version of Android.

Because we just want to return every tea in the database, we don’t apply any joins, filters or groups (i.e. WHERE, JOIN, and GROUP BY clauses in SQL) to the method. The from and order variables tell the query what columns to return on the database and the order in which they are retrieved. We use the SQLiteDatabase.query() method as an interface to the database.

Last, we ask the supplied Activity (in this case, our BrewClockActivity) to manage the Cursor. Usually, a Cursor must be manually refreshed to reload any new data, so if we added a new tea to our database, we would have to remember to refresh our Cursor. Instead, Android can take care of this for us, recreating the results whenever the Activity is suspended and resumed, by calling startManagingCursor().

Finally, we’ll add another utility method to return the number of records in the table. Once again, Android provides a handy utility to do this for us in the DatabaseUtils class:

Add the following method, count, to your TeaData class:

// src/com/example/brewclock/TeaData.java
  public long count() {
    SQLiteDatabase db = getReadableDatabase();
    return DatabaseUtils.queryNumEntries(db, TABLE_NAME);
  }

Save the TeaData class, and fix any missing imports using Eclipse (Source → Organize Imports). With our data class finished, it’s time to change BrewClock’s interface to make use of the database!

Modify BrewClock’s Interface to Allow Tea Selection

The purpose of storing preset teas and brew times is to let the user quickly select their favorite tea from the presets. To facilitate this, we’ll add a Spinner (analogous to a pop-up menu in desktop interfaces) to the main BrewClock interface, populated with the list of teas from TeaData.

As in the previous tutorial, use Eclipse’s layout editor to add the Spinner to BrewClock’s main interface layout XML file. Add the following code just below the LinearLayout for the brew count label (around line 24). Remember, you can switch to the “Code View� tab along the bottom of the window if Eclipse opens the visual layout editor.

<!-- /res/layout/main.xml -->

<!-- Tea Selection -->
<LinearLayout
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">

  <Spinner
    android:id="@+id/tea_spinner"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

In the BrewClockActivity class, add a member variable to reference the Spinner, and connect it to the interface using findViewById:

// src/com/example/brewclock/BrewClockActivity.java
protected Spinner teaSpinner;
protected TeaData teaData;

// …

public void onCreate(Bundle savedInstanceState) {
  // …
  teaData = new TeaData(this);
  teaSpinner = (Spinner) findViewById(R.id.tea_spinner);
}

Try running your application to make sure the new interface works correctly. You should see a blank pop-up menu (or Spinner) just below the brew count. If you tap the spinner, Android handles displaying a pop-up menu so that you can choose an option for the spinner. At the moment, the menu is empty, so we’ll remedy that by binding the Spinner to our tea database.

3 Blank Spinner in Get Started Developing For Android With Eclipse, Reloaded

Data Binding

When Android retrieves data from a database, it returns a Cursor object. The Cursor represents a set of results from the database and can be moved through the results to retrieve values. We can easily bind these results to a view (in this case, the Spinner) using a set of classes provided by Android called “Adapters.� Adapters do all the hard work of fetching database results from a Cursor and displaying them in the interface.

Remember that our TeaData.all() method already returns a Cursor populated with the contents of our teas table. Using that Cursor, all we need to do is create a SimpleCursorAdapter to bind its data to our teaSpinner, and Android will take care of populating the spinner’s options.

Connect the Cursor returned by teaData.all() to the Spinner by creating a SimpleCursorAdapter:

// com/example/brewclock/BrewClockActivity.java

public void onCreate(Bundle savedInstanceState) {
  // …
  Cursor cursor = teaData.all(this);

  SimpleCursorAdapter teaCursorAdapter = new SimpleCursorAdapter(
    this,
    android.R.layout.simple_spinner_item,
    cursor,
    new String[] { TeaData.NAME },
    new int[] { android.R.id.text1 }
  );

  teaSpinner.setAdapter(teaCursorAdapter);
  teaCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}

Notice that we’ve made use of Android’s built-in android.R object. This provides some generic default resources for your application, such as simple views and layouts. In this case, we’ve used android.R.layout.simple_spinner_item, which is a simple text label layout.

If you run the application again, you’ll see that the spinner is still empty! Even though we’ve connected the spinner to our database, there are no records in the database to display.

Let’s give the user a choice of teas by adding some default records to the database in BrewClock’s constructor. To avoid duplicate entries, we’ll add only the default teas if the database is empty. We can make use of TeaData’s count() method to check if this is the case.

Add code to create a default set of teas if the database is empty. Add this line just above the code to fetch the teas from teaData:

// com/example/brewclock/BrewClockActivity.java
public void onCreate(Bundle savedInstanceState) {
  // …

  // Add some default tea data! (Adjust to your preference :)
  if(teaData.count() == 0) {
    teaData.insert("Earl Grey", 3);
    teaData.insert("Assam", 3);
    teaData.insert("Jasmine Green", 1);
    teaData.insert("Darjeeling", 2);
  }

  // Code from the previous step:
  Cursor cursor = teaData.all(this);

  // …
}

Now run the application again. You’ll now see that your tea Spinner has the first tea selected. Tapping on the Spinner lets you select one of the teas from your database!

4 Populated Spinner in Get Started Developing For Android With Eclipse, Reloaded

Congratulations! You’ve successfully connected your interface to a data source. This is one of the most important aspects of any software application. As you’ve seen, Android makes this task fairly easy, but it is extremely powerful. Using cursors and adapters, you can take virtually any data source (from a simple array of strings to a complex relational database query) and bind it to any type of view: a spinner, a list view or even an iTunes-like cover-flow gallery!

Although now would be a good time for a brew, our work isn’t over yet. While you can choose different teas from the Spinner, making a selection doesn’t do anything. We need to find out which tea the user has selected and update the brew time accordingly.

Read Selected Tea, and Update Brew Time

To determine which tea the user has selected from our database, BrewClockActivity needs to listen for an event. Similar to the OnClickListener event that is triggered by button presses, we’ll implement the OnItemSelectedListener. Events in this listener are triggered when the user makes a selection from a view, such as our Spinner.

Enable the onItemSelectedListener in BrewClockActivity by adding it to the class declaration. Remember to implement the interface methods onItemSelected() and onNothingSelected():

// src/com/example/brewclock/BrewClockActivity.java
public class BrewClockActivity extends Activity implements OnClickListener, OnItemSelectedListener {
  // …
  public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
    if(spinner == teaSpinner) {
      // Update the brew time with the selected tea’s brewtime
      Cursor cursor = (Cursor) spinner.getSelectedItem();
      setBrewTime(cursor.getInt(2));
    }
  }

  public void onNothingSelected(AdapterView<?> adapterView) {
    // Do nothing
  }
}

Here we check whether the spinner that triggered the onItemSelected event was BrewClock’s teaSpinner. If so, we retrieve a Cursor object that represents the selected record. This is all handled for us by the SimpleCursorAdapter that connects teaData to the Spinner. Android knows which query populates the Spinner and which item the user has selected. It uses these to return the single row from the database, representing the user’s selected tea.

Cursor’s getInt() method takes the index of the column we want to retrieve. Remember that when we built our Cursor in teaData.all(), the columns we read were _ID, NAME and BREW_TIME. Assuming we chose Jasmine tea in teaSpinner, the Cursor returned by our selection would be pointing at that record in the database.

We then ask the Cursor to retrieve the value from column 2 (using getInt(2)), which in this query is our BREW_TIME column. This value is supplied to our existing setBrewTime() method, which updates the interface to show the selected tea’s brewing time.

Finally, we need to tell the teaSpinner that BrewClockActivity is listening for OnItemSelected events. Add the following line to BrewClockActivity’s onCreate method:

// src/com/example/brewclock/BrewClockActivity.java
public void onCreate() {
  // …
  teaSpinner.setOnItemSelectedListener(this);
}

That should do it! Run your application again, and try selecting different teas from the Spinner. Each time you select a tea, its brew time will be shown on the countdown clock. The rest of our code already handles counting down from the current brew time, so we now have a fully working brew timer, with a list of preset teas.

You can, of course, go back into the code and add more preset teas to the database to suit your tastes. But what if we released BrewClock to the market? Every time someone wanted to add a new tea to the database, we’d need to manually update the database, and republish the application; everyone would need to update, and everybody would have the same list of teas. That sounds pretty inflexible, and a lot of work for us!

5 Default Teas in Get Started Developing For Android With Eclipse, Reloaded

It would be much better if the user had some way to add their own teas and preferences to the database. We’ll tackle that next…

Introducing Activities

Each screen in your application and its associated code is an Activity. Every time you go from one screen to another, Android creates a new Activity. In reality, although an application may comprise any number of screens/activities, Android treats them as separate entities. Activities work together to form a cohesive experience because Android lets you easily pass data between them.

In this final section, you’ll add a new Activity (AddTeaActivity) to your application and register it with the Android system. You’ll then pass data from the original BrewClockActivity to this new Activity.

First, though, we need to give the user a way to switch to the new Activity. We’ll do this using an options menu.

Options Menus

Options menus are the pop-up menus that appear when the user hits the “Menu� key on their device. Android handles the creation and display of options menus automatically; you just need to tell it what options to display and what to do when an option is chosen by the user.

However, rather than hard-coding our labels into the menu itself, we’ll make use of Android string resources. String resources let you maintain all the human-readable strings and labels for your application in one file, calling them within your code. This means there’s only one place in your code where you need to change strings in the future.

In the project explorer, navigate to “res/values� and you will see that a strings.xml file already exists. This was created by Eclipse when we first created the project, and it is used to store any strings of text that we want to use throughout the application.

Open strings.xml by double clicking on it, and switch to the XML view by clicking the strings.xml tab along the bottom of the window.

Add the following line within the <resources>…</resources> element:

<!-- res/values/strings.xml -->
  <resources>
    <!-- … -->
    <string name="add_tea_label">Add Tea</string>
  </resources>

Here you’ve defined a string, add_tea_label, and its associated text. We can use add_tea_label to reference the string throughout the application’s code. If the label needs to change for some reason in the future, you’ll need to change it only once in this file.

Next, let’s create a new file to define our options menu. Just like strings and layouts, menus are defined in an XML file, so we’ll start by creating a new XML file in Eclipse:

Create a new Android XML file in Eclipse by choosing File → New → Other, and then select “Android XML File.â€�

Select a resource type of “Menu,� and save the file as main.xml. Eclipse will automatically create a folder, res/menu, where your menu XML files will be stored.

7 New Menu Xml in Get Started Developing For Android With Eclipse, Reloaded

Open the res/menus/main.xml file, and switch to XML view by clicking the “main.xml� tab along the bottom of the window.

Add a new menu item, add_tea.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/add_tea" android:title="@string/add_tea_label" />
</menu>

Notice the android:title attribute is set to @string/add_tea_label. This tells Android to look up add_tea_label in our strings.xml file and return the associated label. In this case, our menu item will have a label “Add Tea.�

Next, we’ll tell our Activity to display the options menu when the user hits the “Menu� key on their device.

Back in BrewClockActivity.java, override the onCreateOptionsMenu method to tell Android to load our menu when the user presses the “Menu� button:

// src/com/example/brewclock/BrewClockActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.main, menu);

  return true;
}

When the user presses the “Menu� button on their device, Android will now call onCreateOptionsMenu. In this method, we create a MenuInflater, which loads a menu resource from your application’s package. Just like the buttons and text fields that make up your application’s layout, the main.xml resource is available via the global R object, so we use that to supply the MenuInflater with our menu resource.

To test the menu, save and run the application in the Android emulator. While it’s running, press the “Menu� button, and you’ll see the options menu pop up with an “Add Tea� option.

8 Add Teas Options Menu in Get Started Developing For Android With Eclipse, Reloaded

If you tap the “Add Tea� option, Android automatically detects the tap and closes the menu. In the background, Android will notify the application that the option was tapped.

Handling Menu Taps

When the user taps the “Add Teaâ€� menu option, we want to display a new Activity so that they can enter the details of the tea to be added. Start by creating that new Activity by selecting File → New → Class.

9 New Activity Settings in Get Started Developing For Android With Eclipse, Reloaded

Name the new class AddTeaActivity, and make sure it inherits from the android.app.Activity class. It should also be in the com.example.brewclock package:

// src/com/example/brewclock/AddTeaActivity.java
package com.example.brewclock;

import android.app.Activity;
import android.os.Bundle;

public class AddTeaActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }
}

This simple, blank Activity won’t do anything yet, but it gives us enough to finish our options menu.

Add the onOptionsItemSelected override method to BrewClockActivity. This is the method that Android calls when you tap on a MenuItem (notice it receives the tapped MenuItem in the item parameter):

// src/com/example/brewclock/BrewClockActivity.java
@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch(item.getItemId()) {
    case R.id.add_tea:
      Intent intent = new Intent(this, AddTeaActivity.class);
      startActivity(intent);
      return true;

    default:
      return super.onOptionsItemSelected(item);
  }
}

With this code, we’ve told Android that when the “Add Tea� menu item is tapped, we want to start a new Activity; in this case, AddTeaActivity. However, rather than directly creating an instance of AddTeaActivity, notice that we’ve used an Intent. Intents are a powerful feature of the Android framework: they bind Activities together to make up an application and allow data to be passed between them.

Intents even let your application take advantage of any Activities within other applications that the user has installed. For example, when the user asks to display a picture from a gallery, Android automatically displays a dialogue to the user allowing them to pick the application that displays the image. Any applications that are registered to handle image display will be shown in the dialogue.

Intents are a powerful and complex topic, so it’s worth reading about them in detail in the official Android SDK documentation.

Let’s try running our application to test out the new “Add Tea� screen.

Run your project, tap the “Menu� button and then tap “Add Tea.�

Instead of seeing your “Add Tea� Activity as expected, you’ll be presented with a dialogue that is all too common for Android developers:

10 Crash in Get Started Developing For Android With Eclipse, Reloaded

Although we created the Intent and told it to start our AddTeaActivity Activity, the application crashed because we haven’t yet registered it within Android. The system doesn’t know where to find the Activity we’re trying to run (remember that Intents can start Activities from any application installed on the device). Let’s remedy this by registering our Activity within the application manifest file.

Open your application’s manifest file, AndroidManifest.xml in Eclipse, and switch to the code view by selecting the “AndroidManifest.xml� tab along the bottom of the window.

The application’s manifest file is where you define global settings and information about your application. You’ll see that it already declares .BrewClockActivity as the Activity to run when the application is launched.

Within <application>, add a new <activity> node to describe the “Add Tea� Activity. Use the same add_tea_label string that we declared earlier in strings.xml for the Activity’s title:

<!-- AndroidManifest.xml -->
<application …>
  …
  <activity android:name=".AddTeaActivity" android:label="@string/add_tea_label" />
</application>

Save the manifest file before running BrewClock again. This time, when you open the menu and tap “Add Tea,� Android will start the AddTeaActivity. Hit the “Back� button to go back to the main screen.

With the Activities hooked together, it’s time to build an interface for adding tea!

Building The Tea Editor Interface

Building the interface to add a tea is very similar to how we built the main BrewClock interface in the previous tutorial. Start by creating a new layout file, and then add the appropriate XML, as below.

Alternatively, you could use Android’s recently improved layout editor in Eclipse to build a suitable interface. Create a new XML file in which to define the layout. Go to File → New, then select “Android XML File,â€� and select a “Layoutâ€� type. Name the file add_tea.xml.

11 New Layout Xml in Get Started Developing For Android With Eclipse, Reloaded

Replace the contents of add_tea.xml with the following layout:

<!-- res/layouts/add_tea.xml -->
<?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"
  android:orientation="vertical"
  android:padding="10dip">

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

  <EditText
    android:id="@+id/tea_name"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>

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

  <SeekBar
    android:id="@+id/brew_time_seekbar"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:progress="2"
    android:max="9" />

  <TextView
    android:id="@+id/brew_time_value"
    android:text="3 m"
    android:textSize="20dip"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal" />
</LinearLayout>

We’ll also need to add some new strings to strings.xml for the labels used in this interface:

<!-- res/values/strings.xml -->
<resources>
  <!-- … -->
  <string name="tea_name_label">Tea Name</string>

  <string name="brew_time_label">Brew Time</string>
</resources>

In this layout, we’ve added a new type of interface widget, the SeekBar. This lets the user easily specify a brew time by dragging a thumb from left to right. The range of values that the SeekBar produces always runs from zero (0) to the value of android:max.

In this interface, we’ve used a scale of 0 to 9, which we will map to brew times of 1 to 10 minutes (brewing for 0 minutes would be a waste of good tea!). First, though, we need to make sure that AddTeaActivity loads our new interface:

Add the following line of code to the Activity’s onCreate() method that loads and displays the add_tea layout file:

// src/com/example/brewclock/AddTeaActivity.java
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.add_tea);
}

Now test your application by running it, pressing the “Menu� button and tapping “Add Tea� from the menu.

12 Add Tea Interface in Get Started Developing For Android With Eclipse, Reloaded

You’ll see your new interface on the “Add Tea� screen. You can enter text and slide the SeekBar left and right. But as you’d expect, nothing works yet because we haven’t hooked up any code.

Declare some properties in AddTeaActivity to reference our interface elements:

// src/com/example/brewclock/AddTeaActivity.java
public class AddTeaActivity {
  // …

  /** Properties **/
  protected EditText teaName;
  protected SeekBar brewTimeSeekBar;
  protected TextView brewTimeLabel;

  // …

Next, connect those properties to your interface:

public void onCreate(Bundle savedInstanceState) {
  // …
  // Connect interface elements to properties
  teaName = (EditText) findViewById(R.id.tea_name);
  brewTimeSeekBar = (SeekBar) findViewById(R.id.brew_time_seekbar);
  brewTimeLabel = (TextView) findViewById(R.id.brew_time_value);
}

The interface is fairly simple, and the only events we need to listen for are changes to the SeekBar. When the user moves the SeekBar thumb left or right, our application will need to read the new value and update the label below with the selected brew time. We’ll use a Listener to detect when the SeekBar is changed:

Add an onSeekBarChangedListener interface to the AddTeaActivity class declaration, and add the required methods:

// src/com/example/brewclock/AddTeaActivity.java
public class AddTeaActivity
extends Activity
implements OnSeekBarChangeListener {
  // …

  public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    // TODO Detect change in progress
  }

  public void onStartTrackingTouch(SeekBar seekBar) {}

  public void onStopTrackingTouch(SeekBar seekBar) {}
}

The only event we’re interested in is onProgressChanged, so we need to add the code below to that method to update the brew time label with the selected value. Remember that our SeekBar values range from 0 to 9, so we’ll add 1 to the supplied value so that it makes more sense to the user:

Add the following code to onProgressChanged() in AddTeaActivity.java:

// src/com/example/brewclock/AddTeaActivity.java
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  if(seekBar == brewTimeSeekBar) {
    // Update the brew time label with the chosen value.
    brewTimeLabel.setText((progress + 1) + " m");
  }
}

Set the SeekBar’s listener to be our AddTeaActivity in onCreate:

// src/com/example/brewclock/AddTeaActivity.java
public void onCreate(Bundle savedInstanceState) {
  // …

  // Setup Listeners
  brewTimeSeekBar.setOnSeekBarChangeListener(this);
}

Now when run the application and slide the SeekBar left to right, the brew time label will be updated with the correct value:

13 Seekbar in Get Started Developing For Android With Eclipse, Reloaded

Saving Tea

With a working interface for adding teas, all that’s left is to give the user the option to save their new tea to the database. We’ll also add a little validation to the interface so that the user cannot save an empty tea to the database!

Start by opening strings.xml in the editor and adding some new labels for our application:

<!-- res/values/strings.xml -->
<string name="save_tea_label">Save Tea</string>
<string name="invalid_tea_title">Tea could not be saved.</string>

<string name="invalid_tea_no_name">Enter a name for your tea.</string>

Just like before, we’ll need to create a new options menu for AddTeaActivity so that the user can save their favorite tea:

Create a new XML file, add_tea.xml, in the res/menus folder by choosing File → New and then Other → Android XML File. Remember to select “Menuâ€� as the resource type.

Add an item to the new menu for saving the tea:

<!-- res/menus/add_tea.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:title="@string/save_tea_label" android:id="@+id/save_tea" />
</menu>

Back in AddTeaActivity, add the override methods for onCreateOptionsMenu and onOptionsItemSelected, just like you did in BrewClockActivity. However, this time, you’ll supply the add_tea.xml resource file to the MenuInflater:

// src/com/example/brewclock/AddTeaActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.add_tea, menu);

  return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch(item.getItemId()) {
    case R.id.save_tea:
      saveTea();

    default:
      return super.onOptionsItemSelected(item);
  }
}

Next, we’ll add a new method, saveTea(), to handle saving the tea. The saveTea method first reads the name and brew time values chosen by the user, validates them and (if all is well) saves them to the database:

// src/com/example/brewclock/AddTeaActivity.java
public boolean saveTea() {
  // Read values from the interface
  String teaNameText = teaName.getText().toString();
  int brewTimeValue = brewTimeSeekBar.getProgress() + 1;

  // Validate a name has been entered for the tea
  if(teaNameText.length() < 2) {
    AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    dialog.setTitle(R.string.invalid_tea_title);
    dialog.setMessage(R.string.invalid_tea_no_name);
    dialog.show();

    return false;
  }

  // The tea is valid, so connect to the tea database and insert the tea
  TeaData teaData = new TeaData(this);
  teaData.insert(teaNameText, brewTimeValue);
  teaData.close();

  return true;
}

This is quite a hefty chunk of code, so let’s go over the logic.

First, we read the values of the EditText teaName and the SeekBar brewTimeSeekBar (remembering to add 1 to the value to ensure a brew time of between 1 and 10 minutes). Next, we validate that a name has been entered that is two or more characters (this is really simple validation; you might want to experiment doing something more elaborate, such as using regular expressions).

If the tea name is not valid, we need to let the user know. We make use of one of Android’s helper classes, AlertDialog.Builder, which gives us a handy shortcut for creating and displaying a modal dialog window. After setting the title and error message (using our string resources), the dialogue is displayed by calling its show() method. This dialogue is modal, so the user will have to dismiss it by pressing the “Back� key. At this point, we don’t want to save any data, so just return false out of the method.

If the tea is valid, we create a new temporary connection to our tea database using the TeaData class. This demonstrates the advantage of abstracting your database access to a separate class: you can access it from anywhere in the application!

After calling teaData.insert() to add our tea to the database, we no longer need this database connection, so we close it before returning true to indicate that the save was successful.

Try this out by running the application in the emulator, pressing “Menu� and tapping “Add Tea.� Once on the “Add Tea� screen, try saving an empty tea by pressing “Menu� again and tapping “Save Tea.� With your validation in place, you’ll be presented with an error message:

14 Invalid Tea in Get Started Developing For Android With Eclipse, Reloaded

Next, try entering a name for your tea, choosing a suitable brew time, and choosing “Save Tea� from the menu again. This time, you won’t see an error message. In fact, you’ll see nothing at all.

Improving the User Experience

While functional, this isn’t a great user experience. The user doesn’t know that their tea has been successfully saved. In fact, the only way to check is to go back from the “Add Tea� Activity and check the list of teas. Not great. Letting the user know that their tea was successfully saved would be much better. Let’s show a message on the screen when a tea has been added successfully.

We want the message to be passive, or non-modal, so using an AlertDialog like before won’t help. Instead, we’ll make use of another popular Android feature, the Toast.

Toasts display a short message near the bottom of the screen but do not interrupt the user. They’re often used for non-critical notifications and status updates.

Start by adding a new string to the strings.xml resource file. Notice the %s in the string? We’ll use this in the next step to interpolate the name of the saved tea into the message!

<!-- res/values/strings.xml -->
<string name="save_tea_success">%s tea has been saved.</string>

Modify the code in onOptionsItemSelected to create and show a Toast pop-up if the result of saveTea() is true. The second parameter uses of getString() interpolate the name of our tea into the Toast message. Finally, we clear the “Tea Name� text so that the user can quickly add more teas!

// src/com/example/brewclock/AddTeaActivity.java
// …
switch(item.getItemId()) {
 case R.id.save_tea:
   if(saveTea()) {
     Toast.makeText(this, getString(R.string.save_tea_success, teaName.getText().toString()), Toast.LENGTH_SHORT).show();
     teaName.setText("");
   }
// …

Now re-run your application and try adding and saving a new tea. You’ll see a nice Toast pop up to let you know the tea has been saved. The getString() method interpolates the name of the tea that was saved into the XML string, where we placed the %s.

16 Valid Save in Get Started Developing For Android With Eclipse, Reloaded

Click the “Back� button to return to the application’s main screen, and tap the tea spinner. The new teas you added in the database now show up as options in the spinner!

User Preferences

BrewClock is now fully functional. Users can add their favorite teas and the respective brewing times to the database, and they can quickly select them to start a new brew. Any teas added to BrewClock are saved in the database, so even if we quit the application and come back to it later, our list of teas is still available.

One thing you might notice when restarting BrewClock, though, is that the brew counter is reset to 0. This makes keeping track of our daily tea intake (a vital statistic!) difficult. As a final exercise, let’s save the total brew count to the device.

Rather than adding another table to our teas database, we’ll make use of Android’s “Shared Preferences,� a simple database that Android provides to your application for storing simple data (strings, numbers, etc.), such as high scores in games and user preferences.

Start by adding a couple of constants to the top of BrewClockActivity.java. These will store the name of your shared preferences file and the name of the key we’ll use to access the brew count. Android takes care of saving and persisting our shared preferences file.

// src/com/example/brewclock/BrewClockActivity.java
protected static final String SHARED_PREFS_NAME = "brew_count_preferences";
protected static final String BREW_COUNT_SHARED_PREF = "brew_count";

Next, we’ll need to make some changes to the code so that we can read and write the brew count to the user preferences, rather than relying on an initial value in our code. In BrewClockActivity’s onCreate method, change the lines around setBrewCount(0) to the following:

// src/com/example/brewclock/BrewClockActivity.java
public void onCreate() {
  // … 

  // Set the initial brew values
  SharedPreferences sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
  brewCount = sharedPreferences.getInt(BREW_COUNT_SHARED_PREF, 0);
  setBrewCount(brewCount);

  // …
}

Here we’re retrieving an instance of the application’s shared preferences using SharedPreferences, and asking for the value of the brew_count key (identified by the BREW_COUNT_SHARED_PREF constant that was declared earlier). If a value is found, it will be returned; if not, we’ll use the default value in the second parameter of getInt (in this case, 0).

Now that we can retrieve the stored value of brew count, we need to ensure its value is saved to SharedPreferences whenever the count is updated.

Add the following code to setBrewCount in BrewClockActivity:

// src/com/example/brewclock/BrewClockActivity.java
 public void setBrewCount(int count) {
   brewCount = count;
   brewCountLabel.setText(String.valueOf(brewCount));

   // Update the brewCount and write the value to the shared preferences.
   SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE).edit();
   editor.putInt(BREW_COUNT_SHARED_PREF, brewCount);
   editor.commit();
 }

Shared preferences are never saved directly. Instead, we make use of Android’s SharedPreferences.Editor class. Calling edit() on SharedPreferences returns an editor instance, which can then be used to set values in our preferences. When the values are ready to be saved to the shared preferences file, we just call commit().

With our application’s code all wrapped up, it’s time to test everything!

Run the application on the emulator, and time a few brews (this is the perfect excuse to go and make a well-deserved tea or two!), and then quit the application. Try running another application that is installed on the emulator to ensure BrewClock is terminated. Remember that Android doesn’t terminate an Activity until it starts to run out of memory.

When you next run the application, you’ll see that your previous brew count is maintained, and all your existing teas are saved!

Summary

Congratulations! You’ve built a fully working Android application that makes use of a number of core components of the Android SDK. In this tutorial, you have seen how to:

  • Create a simple SQLite database to store your application’s data;
  • Make use of Android’s database classes and write a custom class to abstract the data access;
  • Add option menus to your application;
  • Create and register new Activities within your application and bind them together into a coherent interface using Intents;
  • Store and retrieve simple user data and settings using the built-in “Shared Preferencesâ€� database.

Data storage and persistence is an important topic, no matter what type of application you’re building. From utilities and business tools to 3-D games, nearly every application will need to make use of the data tools provided by Android.

17 Brew Up in Get Started Developing For Android With Eclipse, Reloaded

Activities

BrewClock is now on its way to being a fully functional application. However, we could still implement a few more features to improve the user experience. For example, you might like to develop your skills by trying any of the following:

  • Checking for duplicate tea name entries before saving a tea,
  • Adding a menu option to reset the brew counter to 0,
  • Storing the last-chosen brew time in a shared preference so that the application defaults to that value when restarted,
  • Adding an option for the user to delete teas from the database.

Solutions for the Activities will be included in a future branch on the GitHub repository, where you’ll find the full source-code listings. You can download the working tutorial code by switching your copy of the code to the tutorial_2 branch:

# If you’ve not already cloned the repository,
# you’ll need to do that first:
# $ git clone git://github.com/cblunt/BrewClock.git
# $ cd BrewClock
$ git checkout tutorial_2

I hope you’ve enjoyed working through this tutorial and that it helps you in designing and building your great Android applications. Please let me know how you get on in the comments below, or feel free to drop me an email.

Thanks to Anselm for his suggestions and feedback!

(al)


© Chris Blunt for Smashing Magazine, 2011. | Permalink | Post a comment | Smashing Shop | Smashing Network | About Us
Post tags: , ,


Get Started Developing for Android with Eclipse

Smashing-magazine-advertisement in Get Started Developing for Android with EclipseSpacer in Get Started Developing for Android with Eclipse
 in Get Started Developing for Android with Eclipse  in Get Started Developing for Android with Eclipse  in Get Started Developing for Android with Eclipse

There’s a lot to get excited about in mobile application development today. With increasingly sophisticated hardware, tablet PCs and a variety of software platforms (Symbian OS, iOS, WebOS, Windows Phone 7…), the landscape for mobile developers is full of opportunities — and a little complex as well.

So much choice can be overwhelming when you just want to get started building mobile applications. Which platform should you choose? What programming language should you learn? What kit do you need for your planned project? In this tutorial, you’ll learn how to start writing applications for Android, the open-source mobile operating system popularized by Google.

[Offtopic: by the way, did you already get your copy of the Smashing Book?]

Why Develop for Android?

Android is an open-source platform based on the Linux kernel, and is installed on thousands of devices from a wide range of manufacturers. Android exposes your application to all sorts of hardware that you’ll find in modern mobile devices — digital compasses, video cameras, GPS, orientation sensors, and more.

Android’s free development tools make it possible for you to start writing software at little or no cost. When you’re ready to show off your application to the world, you can publish it to Google’s Android Market. Publishing to Android Market incurs a one-off registration fee (US $25 at the time of writing) and, unlike Apple’s App Store which famously reviews each submission, makes your application available for customers to download and buy after a quick review process — unless the application is blatantly illegal.

Here are a few other advantages Android offers you as a developer:

  • The Android SDK is available for Windows, Mac and Linux, so you don’t need to pay for new hardware to start writing applications.
  • An SDK built on Java. If you’re familiar with the Java programming language, you’re already halfway there.
  • By distributing your application on Android Market, it’s available to hundreds of thousands of users instantly. You’re not just limited to one store, because there are alternatives, too. For instance, you can release your application on your own blog. Amazon have recently been rumoured to be preparing their own Android app store also.
  • As well as the technical SDK documentation, new resources are being published for Android developers as the platform gains popularity among both users and developers.

Enough with the talk — let’s get started developing for Android!

Installing Eclipse and the Android SDK

The recommended environment for developing Android applications is Eclipse with the Android Development Toolkit (ADT) plugin installed. I’ll summarize the process here. If you need more detail, Google’s own developer pages do a good job of explaining the installation and configuration process.

  • Download the Android SDK for your platform (Windows, Mac OS X, or Linux).
  • Extract the downloaded file to somewhere memorable on your hard drive (on Linux, I use /opt/local/).
  • If you don’t already have Eclipse installed, download and install the Eclipse IDE for Java Developers package. For programming, Google recommends using Eclipse 3.5 (Galileo).
  • Run Eclipse and choose Help->Install New Software.
  • Click Add in the Available Software window.
  • Enter Android Development Tools in the Name field, and https://dl-ssl.google.com/android/eclipse/ in the Location field.
  • Click OK and check Developer Tools in the list of available software. This will install the Android Development Tools and DDMS, Android’s debugging tool.
  • Install in Get Started Developing for Android with Eclipse
    Large image

  • Click Next and Finish to install the plugin. You’ll need to restart Eclipse once everything is installed.
  • When Eclipse restarts, choose Window->Preferences and you should see Android listed in the categories.
  • You now need to tell Eclipse where you’ve installed the Android SDK. Click Android and then Browse to select the location where you extracted the SDK files. For example, /opt/local/android-sdk.
    Prefs in Get Started Developing for Android with Eclipse
    Large view
  • Click OK to have Eclipse save the location of your SDK.

Targeting Android Platforms

Before you can start writing applications for Android, you need to download the SDK platforms for the Android devices for which you want to develop apps. Each platform has a different version of the Android SDK that may be installed on users’ devices. For versions of Android 1.5 and above, there are two platforms available: Android Open Source Project and Google.

The Android Open Source Project platforms are open source, but do not include Google’s proprietary extensions such as Google Maps. If you choose not to use the Google APIs, Google’s mapping functionality won’t be available to your application. Unless you have a specific reason not to, I’d recommended you to target one of the Google platforms, as this will allow you to take advantage of Google’s proprietary extensions.

  • Choose Window->Android SDK and AVD Manager.
  • Click Available Packages in the left column and check the repository to show a list of the available Android platforms.
  • You can choose which platforms to download from the list, or leave everything checked to download all the available platforms. When you’re done, click Install Selected and follow the installation instructions.

    Sdk in Get Started Developing for Android with Eclipse
    Large image

Once everything has been successfully downloaded, you’re ready to start developing for Android.

Creating a New Android Project

Eclipse’s New Project Wizard can create a new Android application for you, generating files and code that are ready to run right out of the box. It’s a quick way to see something working, and a good starting point from which to develop your own applications:

  • Choose File->New->Project…
  • Choose Android Project
  • In the New Project dialog, enter the following settings:
    Project Name: BrewClock
    Build Target: Google Inc. 1.6 (Api Level 4)
    Application Name: BrewClock
    Package Name: com.example.brewclock
    Create Activity: BrewClockActivity
    Min SDK Version: 4

    Eclipse New Project Settings in Get Started Developing for Android with Eclipse

After clicking Finish, Eclipse will create a new Android project that’s ready to run. Notice you told Eclipse to generate an Activity called BrewClockActivity? This is the code that Android actually uses to run your application. The generated code will display a simple ‘Hello World’ style message when the application runs.

Packages

The package name is an identifier for your application. When the time comes and you are willing to publish on Android Market, it’s exactly this identifier that will be used to track your application for updates, so it’s important to make sure it’s unique. Although we’re using the com.example.brewclock namespace here, for a real application it’s best to choose something like com.yourcompanyname.yourapplication.

SDK Versions

The Min SDK Version is the earliest version of Android on which your application will run. With each new release of Android, the SDK adds and changes methods. By choosing an SDK version, Android (and the Android Market) knows that your application will only run on devices with a version of Android later or equal than the specified version.

Running Your Application

Now let’s try running the application in Eclipse. As this is the first run, Eclipse will ask what type of project you are working on:

  • Choose Run->Run or press Ctrl+F11.
  • Choose Android Application and click OK.

Eclipse will now try to run the application on an Android device. At the moment, though, you don’t have any Android devices running, so the run will fail and you’ll be asked to create a new Android Virtual Device (AVD).

Eclipse No Avd in Get Started Developing for Android with Eclipse

Android Virtual Devices

An Android Virtual Device (AVD) is an emulator that simulates a real-world Android device, such as a mobile phone or Tablet PC. You can use AVDs to test how your application performs on a wide variety of Android devices, without having to buy every gadget on the market.

You can create as many AVDs as you like, each set up with different versions of the Android Platform. For each AVD you create, you can configure various hardware properties such as whether it has a physical keyboard, GPS support, the camera resolution, and so on.

Before you can run your application, you need to create your first AVD running the target SDK platform (Google APIs 1.6).

Let’s do that now:

  • If you haven’t tried to run your application yet, click Run now (or hit Ctrl+F11)
  • When the target device warning pops up, click Yes to create a new AVD.
  • Click New in the Android SDK and AVD Manager dialog that appears.
  • Enter the following settings for the AVD:
    Name: Android_1.6
    Target: Google APIs (Google Inc.) - API Level 4
    SD Card Size: 16 MiB
    Skin Built In: Default (HVGA)
  • Click Create AVD to have Android build your new AVD.
  • Close the Android SDK and AVD Manager dialog.

Sdk Manager New Avd in Get Started Developing for Android with Eclipse

Running the Code

Try running your application again (Ctrl+F11). Eclipse will now build your project and launch the new AVD. Remember, the AVD emulates a complete Android system, so you’ll even need to sit through the slow boot process just like a real device. For this reason, once the AVD is up and running, it’s best not to close it down until you’ve finished developing for the day.

When the emulator has booted, Eclipse automatically installs and runs your application:

App Running-550-e1287474474253 in Get Started Developing for Android with Eclipse
Large image

Building Your First Android Application

Testing generated code is all well and good, but you want to start building a real application. For this, we’ll step through a simple design process and build an application that you can deploy to your Android device.

Most developers (myself included) like a constant supply of good tea or coffee. In the next section of this article you’ll build a simple tea counter application to track how many cups of tea (brews) the user has drunk, and let them set a timer for brewing each cup.

You can download the complete code for this tutorial on GitHub.

Designing the User Interface

One of the first steps to building any Android application is to design and build the user interface. Here’s a quick sketch of how the application’s interface will look:

Design Sketch in Get Started Developing for Android with Eclipse
Large image

The user will be able to set a brew time in minutes using the + and - buttons. When they click Start, a countdown will start for the specified number of minutes. Unless the user cancels the brew by tapping the button again, the brew count will be increased when the countdown timer reaches 0.

Building the Interface

Android user interfaces, or layouts, which are described in XML documents, can be found in the res/layouts folder. The template code that Eclipse generated already has a simple layout declared in res/layouts/main.xml which you may have seen previously while the application was running on the emulator.

Eclipse has a graphical layout designer that lets you build the interface by ‘dragging’ and ‘dropping’ controls around the screen. However, I often find it easier to write the interface in XML and use the graphical layout to preview the results.

Let’s do this now by changing main.xml to match the design sketch above:

  • Open res/layouts/main.xml in Eclipse by double-clicking it in the Package Explorer.
  • Click the main.xml tab along the bottom of the screen to switch to XML view.

Now change the content of main.xml to:

# /res/layouts/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <LinearLayout
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="10dip">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="20dip"
      android:text="Brews: " />
    <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="None"
      android:gravity="right"
      android:textSize="20dip"
      android:id="@+id/brew_count_label" />
  </LinearLayout>
  <LinearLayout
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:gravity="center"
    android:padding="10dip">
    <Button
      android:id="@+id/brew_time_down"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="-"
      android:textSize="40dip" />
    <TextView
      android:id="@+id/brew_time"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="0:00"
      android:textSize="40dip"
      android:padding="10dip" />
    <Button
      android:id="@+id/brew_time_up"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="+"
      android:textSize="40dip" />
  </LinearLayout>
  <Button
    android:id="@+id/brew_start"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:text="Start" />
</LinearLayout>

As you can see, Android’s XML layout files are verbose, but allow you to control virtually every aspect of elements on the screen.

One of the most important interface elements in Android are Layout containers, such as the LinearLayout used in this example. These elements are invisible to the user but act as layout containers for other elements such as Buttons and TextViews.

There are several types of layout views, each of which is used to build different types of layout. As well as the LinearLayout and AbsoluteLayout, the TableLayout allows the use of complex grid-based interfaces. You can find out more about Layouts in the Common Layout Objects section of the API documents.

Linking Your Layout With Code

After saving your layout, try running your application in the emulator again by pressing Ctrl+F11, or clicking the Run icon in Eclipse. Now instead of the ‘Hello World’ message you saw earlier, you’ll see Android now displays your application’s new interface.

If you click any of the buttons, they’ll highlight as expected, but don’t do anything yet. Let’s remedy that by writing some code behind the interface layout:

# /src/com/example/brewclock/BrewClockActivity.java
...
import android.widget.Button;
import android.widget.TextView;

public class BrewClockActivity extends Activity {
  /** Properties **/
  protected Button brewAddTime;
  protected Button brewDecreaseTime;
  protected Button startBrew;
  protected TextView brewCountLabel;
  protected TextView brewTimeLabel;

  ...
 }

Next, we’ll change the call to onCreate. This is the method that gets called whenever Android starts your application. In the code that Eclipse generated, onCreate sets the activity’s view to be R.layout.main. It’s that line of code that tells Android to decode our layout XML document and display it to the user.

The Resource Object

In Android, R is a special object that is automatically generated to allow access to your project’s resources (layouts, strings, menus, icons…) from within the code. Each resource is given an id. In the layout file above, these are the @+id XML attributes. We’ll use those attributes to connect the Buttons and TextViews in our layout to the code:

# /src/com/example/brewclock/BrewClockActivity.java
...
public class BrewClockActivity extends Activity {
  ...
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Connect interface elements to properties
    brewAddTime = (Button) findViewById(R.id.brew_time_up);
    brewDecreaseTime = (Button) findViewById(R.id.brew_time_down);
    startBrew = (Button) findViewById(R.id.brew_start);
    brewCountLabel = (TextView) findViewById(R.id.brew_count_label);
    brewTimeLabel = (TextView) findViewById(R.id.brew_time);
  }
}

Listening For Events

In order to detect when the user taps one of our buttons, we need to implement a listener. You may be familiar with listeners or callbacks from other event-driven platforms, such as Javascript/jQuery events or Rails’ callbacks.

Android provides a similar mechanism by providing Listener interfaces, such as OnClickListener, that define methods to be triggered when an event occurs. Implementing the OnClickListener interface will notify your application when the user taps the screen, and on which button they tapped. You also need to tell each button about the ClickListener so that it knows which listener to notify:

# /src/com/example/brewclock/BrewClockActivity.java
...
// Be sure not to import
// `android.content.dialoginterface.OnClickListener`.
import android.view.View.OnClickListener; 

public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  public void onCreate(Bundle savedInstanceState) {
    ...
    // Setup ClickListeners
    brewAddTime.setOnClickListener(this);
    brewDecreaseTime.setOnClickListener(this);
    startBrew.setOnClickListener(this);
  }
  ...
  public void onClick(View v) {
    // TODO: Add code to handle button taps
  }
}

Next we’ll add code that handles each of our button presses. We’ll also add four new properties to the Activity that will let the user set and track the brewing time, how many brews have been made, and whether the timer is currently running.

# /src/com/example/brewclock/BrewClockActivity.java
...
public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  protected int brewTime = 3;
  protected CountDownTimer brewCountDownTimer;
  protected int brewCount = 0;
  protected boolean isBrewing = false;
  ...
  public void onClick(View v) {
    if(v == brewAddTime)
      setBrewTime(brewTime + 1);
    else if(v == brewDecreaseTime)
      setBrewTime(brewTime -1);
    else if(v == startBrew) {
      if(isBrewing)
        stopBrew();
      else
        startBrew();
    }
  }
}

Notice we’re using the CountDownTimer class provided by Android. This lets you easily create and start a simple countdown, and be notified at regular intervals whilst the countdown is running. You’ll use this in the startBrew method below.

The following methods are all model logic that handles setting the brew time, starting and stopping the brew and maintaining a count of brews made. We’ll also initialize the brewTime and brewCount properties in onCreate.

It would be good practice to move this code to a separate model class, but for simplicity we’ll add the code to our BrewClockActivity:

# /src/com/example/brewclock/BrewClockActivity.java
...
public class BrewClockActivity extends Activity
  implements OnClickListener {
  ...
  public void onCreate(Bundle savedInstanceState) {
    ...
    // Set the initial brew values
    setBrewCount(0);
    setBrewTime(3);
  }

  /**
   * Set an absolute value for the number of minutes to brew.
   * Has no effect if a brew is currently running.
   * @param minutes The number of minutes to brew.
   */
  public void setBrewTime(int minutes) {
    if(isBrewing)
      return;

    brewTime = minutes;

    if(brewTime < 1)
      brewTime = 1;

    brewTimeLabel.setText(String.valueOf(brewTime) + "m");
  }

  /**
   * Set the number of brews that have been made, and update
   * the interface.
   * @param count The new number of brews
   */
  public void setBrewCount(int count) {
    brewCount = count;
    brewCountLabel.setText(String.valueOf(brewCount));
  }

  /**
   * Start the brew timer
   */
  public void startBrew() {
    // Create a new CountDownTimer to track the brew time
    brewCountDownTimer = new CountDownTimer(brewTime * 60 * 1000, 1000) {
      @Override
      public void onTick(long millisUntilFinished) {
        brewTimeLabel.setText(String.valueOf(millisUntilFinished / 1000) + "s");
      }

      @Override
      public void onFinish() {
        isBrewing = false;
        setBrewCount(brewCount + 1);

        brewTimeLabel.setText("Brew Up!");
        startBrew.setText("Start");
      }
    };

    brewCountDownTimer.start();
    startBrew.setText("Stop");
    isBrewing = true;
  }

  /**
   * Stop the brew timer
   */
  public void stopBrew() {
    if(brewCountDownTimer != null)
      brewCountDownTimer.cancel();

    isBrewing = false;
    startBrew.setText("Start");
  }
  ...
}

The only parts of this code specific to Android are setting the display labels using the setText method. In startBrew, we create and start a CountDownTimer to start counting down every second until a brew is finished. Notice that we define CountDownTimer's listeners (onTick and onFinish) inline. onTick will be called every 1000 milliseconds (1 second) the timer counts down, whilst onFinish is called when the timer reaches zero.

Avoiding Hard-Coded Text in your Code

To keep this tutorial code simple, I’ve intentionally written label strings directly in the code (e.g. "Brew Up!", "Start", "Stop"). Generally, this isn’t good practice, as it makes finding and changing those strings harder in large projects.

Android provides a neat way to keep your text strings separate from code with the R object. R lets you define all your application’s strings in an xml file (res/values/strings.xml) which you can then access in code by reference. For example:

# /res/values/strings.xml
<string name="brew_up_label">Brew Up!</string>
...

# /res/com/example/brewclock/BrewClockActivity.java
...
brewLabel.setText(R.string.brew_up_label);
...

Now if you wanted to change Brew Up! to something else, you would only need to change it once in the strings.xml file. Your application starts to span dozens of code files which keeps all your strings in one place and makes a lot of sense!

Trying BrewClock

With the code complete, it’s time to try out the application. Hit Run or Ctrl+F11 to start BrewClock in the emulator. All being well, you’ll see the interface set up and ready to time your tea brewing! Try setting different brew times, and pressing Start to watch the countdown.

App Finished-550-e1287474491689 in Get Started Developing for Android with Eclipse
Large image

Summary

In this short introduction to Android, you’ve installed the Android SDK and Eclipse Android Development Tools (ADT) plugin. You’ve set up an emulator, or virtual device that can test your applications. You’ve also built a working Android application which has highlighted a number of key concepts that you’ll use when developing your own Android applications.

Hopefully, this has whet your appetite for building mobile applications, and experimenting in this exciting field. Android offers a great way to start writing applications for a range of current and upcoming mobile devices. If you’ve built or are working on your own mobile app, be sure to let us know about it in the comments!

(ik), (vf)


© Chris Blunt for Smashing Magazine, 2010. | Permalink | Post a comment | Add to del.icio.us | Digg this | Stumble on StumbleUpon! | Tweet it! | Submit to Reddit | Forum Smashing Magazine
Post tags: ,


  •   
  • Copyright © 1996-2010 BlogmyQuery - BMQ. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress