teaching machines

CS 491 Lecture 8 – Rattler

Agenda

  • Start an application for composing ringtones
  • Write a ListActivity
  • Get a taste of Android’s SQLite API
  • Share data globally throughout an application
  • Insert and select from a database

RTTTL

Nokia put together a specification for a mini-language for encoding ringtones. It’s called RTTTL. The language is fairly straightforward. You’ve got three parts separated by colons:

name:control:music

The name is any <=10-character string. Music is a comma-separated series of musical atoms of the form #NO, where:

  • # represents the duration of the note and is 1, 2, 4, 8, 16, 32, or missing
  • N is the letter of the note, A-G, or P for a rest
  • O is the octave and is 4-7 or missing

The control section is of the form d=#,o=##,b=###, where # is the duration used when unspecified in the music, ## is the octave used when unspecified, and ### is the beats per minute. A nice little language. We want to write an interface that enables composition of ringtones which will ultimately land in this format.

Let’s not worry about the actual interface yet. Let’s start with producing a ringtone-document framework. Our very basic needs in many apps are to be able to create new documents and edit old ones. Let’s make a new project.

ListActivity

The ListActivity is one of the greatest contributions to the Android ecosystem. We can wrap up our list of menu items in an Adapter, which is the bridge between our elements and their views, and the ListActivity can manage all interaction from there.

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
// add elements
setListAdapter(adapter);
getListView().setOnItemClickListener(this);
Sharing data across Activitys

By default, it calls toString() on each of our elements and presents them in a simple TextView. We’ll customize this eventually.

Let’s insert a File > New to get the ball rolling:

adapter.add("Add new song");

For the rest of the list, we want to enumerate all the songs that have been added so far. When we click on them, we’ll open an editor for the song. When we click on the first one, we’ll create a new one and then edit. We’ll need some long-term storage mechanism to make this happen. Files? We could. Database? Heck, yeah!

Databases and SQLite

First, tell me everything you know about databases. What should our database look like for a simple set of songs?

Okay, let’s make a database for storing songs. There’s a lot of API here that can be abstracted, so let’s make a new class to manage the database work. First, let’s toss in some constants:

public class SongsDatabase {
  private static String DB_NAME = "songs.db";
  private static int DB_VERSION = 1;
 
  private static String TABLE_SONG = "song";
  private static String COLUMN_SONG_ID = "_id";
  private static String COLUMN_SONG_NAME = "name";
  private static String COLUMN_SONG_MUSIC = "music";

  ...
}

For opening our database, Android provides a SQLiteOpenHelper class that we can extend. It will manage the creation of the database and any future upgrades:

private class SongsOpenHelper extends SQLiteOpenHelper {
  public SongsOpenHelper(Context context,
                         String name,
                         int version) {
    super(context, name, null, version);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
    String query = "CREATE TABLE " + TABLE_SONG + " (" +
                   "  " + COLUMN_SONG_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                   "  " + COLUMN_SONG_NAME + " TEXT NOT NULL, " +
                   "  " + COLUMN_SONG_MUSIC + " TEXT NOT NULL" +
                   ")";
    db.execSQL(query);
  }

  @Override
  public void onUpgrade(SQLiteDatabase db,
                        int oldVersion,
                        int newVersion) {
  }
}

Now we can make our database object:

private SQLiteDatabase db;
 
public SongsDatabase(Context context) {
  SQLiteOpenHelper dbOpenHelper = new SongsOpenHelper(context, DB_NAME, DB_VERSION);
  db = dbOpenHelper.getWritableDatabase();
}

If the version passed in here is different from the version of the existing database, then onUpgrade will get called. Otherwise, onCreate. That onUpgrade method looks like it could be quite a mess. Another Cartesian product.

Inserting a new song

I like to make a class corresponding to every table in my database. Let’s write a Song class that reflects the song record structure.

Now, we can a new song when the user hits the first menu item:

@Override
public void onItemClick(AdapterView<?> parent,
                        View view,
                        int position,
                        long id) {
  if (position == 0) {
    addNewSong();
  }
}

Our addNewSong method can grab the title from the user:

public void addNewSong() {
  AlertDialog.Builder dialog = new AlertDialog.Builder(this);
  dialog.setTitle("New song");
  final EditText titleBox = new EditText(this);
  titleBox.setHint("title");
  dialog.setView(titleBox);
  dialog.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog,
                        int which) {
      Song song = new Song(titleBox.getText().toString(), "");
      ...
    }
  });
  dialog.show();
}

Adding it to the database isn’t terrible. We issue an insert statement. Then we can add the new song to the adapter.

Now, let’s start editing this new song!

Haiku

Some mothers scrapbook
mine prefers to database
“This will scale,” she says

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *