Lab 3: MyRuns3: Database

This is the third in a set of thematic labs to build a continuous sensing app that can automatically determine human activity such as walking and running. In the this lab, we will start using Database in our application. Android system uses ContentProvider as the supporting database. Android provides API to access database directly, but in most of the case, you will write a wrapper class for all database operations, which fits to the idea of Object Oriented design.

Demo apk and code skeleton

We have given out the following code in this lab:

Note, you are free to use these files and code or follow your own design.

Important note on your implementation

Please note that you should use your code for earlier labs and not the solutions we hand out. The goal is that you incrementally add new functionality to your existing app.

It is completely fine to look at the solution and refactor your code if needed. Please state in your README.txt file that the current lab is your own code and not taken from the solution. Without this statement we cannot grade your assignment.

Start to record: StartTabFragment, ManualInputActivity and MyRunsDialogFragment

MyRuns enables you to select multiple types (GPS, Automatic, Manual input) as inputs to keep track of the exercise you took everyday. For this lab, we just implement the manual input as a start. The user can take a note of their exercise stats and save it.

As shown in the screenshot above, you need two Spinner widgets for the layout in the StartTabFragment because we want to create a drop-down list for both the input type option and activity type option. Also you need to implement the setOnClickListener for the "Start" button. Once clicked, the app should fire the ManualInputActivity which lets you write down your exercise stats.

The above screenshot shows the pop-up dialog when you clicked the "Comment" option to write down your comments about this exercise. You need to implement all the related dialogs in the MyRunsDialogFragment. After the user is done with the input in dialog, the ManualInputActivity will store the inputs temporarily in the private ExerciseEntry. When you click the "Save" button, you need to save the temporary data of the exercise stats into the database.

MyRuns's Data Storage: DBHelper, HistoryProvider, ExerciseEntryHelper and ExerciseEntry

I mentioned my three-layer theory in the lecture 15. For this lab, we code in this style. The database therefore contains a single table that stores all the exercises. We store the user's exercise entries into the table, where each row is a entry. Each column is one property of the entry (e.g., KEY_DISTANCE, KEY_CALORIES). Below is a diagram that I made to illustrate the design architecture of this lab. You can better learn the data storage structure from it.

Let's start from bottom to the top.

Checkpoint: The lowest level of code is DBHelper. This class extends SQLiteOpenHelper to create and upgrade a database in your app. The DBHelper calls the static methods of the HistoryTable helper class.

        public void onCreate(SQLiteDatabase db) {



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

            HistoryTable.onUpgrade(db, oldVersion, newVersion);

The HistoryProvider class extends ContentProvider to store data. Your HistoryProvider needs to implement four standard database operations: update(), insert(), delete(), and query() method. These methods map more or less directly to the SQLiteDatabase interface. I give out the code of query() and update() for you to get a sense of how to implement delete() and insert(). Let's take a look at the update() function:

    //Update row(s) in a content URI.

    public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
        int uriType = sURIMatcher.match(uri);
        SQLiteDatabase sqlDB = database.getWritableDatabase();
        int rowsUpdated = 0;
        switch (uriType) {
        case ENTRIES_DIR:
        rowsUpdated = sqlDB.update(HistoryTable.TABLE_NAME_ENTRIES, 

        case ENTRIES_ID:
        String id = uri.getLastPathSegment();
        if (TextUtils.isEmpty(selection)) {
            rowsUpdated = sqlDB.update(HistoryTable.TABLE_NAME_ENTRIES,
            HistoryTable.KEY_ROWID + "=" + id,
        } else {
                rowsUpdated = sqlDB.update(HistoryTable.TABLE_NAME_ENTRIES, 
                HistoryTable.KEY_ROWID + "=" + id 
                + " and " 
                + selection,


          throw new IllegalArgumentException("Unknown URI: " + uri);

        getContext().getContentResolver().notifyChange(uri, null);
        return rowsUpdated;

The HistoryProvider implements its database operation functions by calling the standard SQLite functions. In this particular case, HistoryProvider update the entries by calling sqlDB.update(). After the SQLite update it calls notifyChange(uri, null) to notify the change to the whole application.

Once you create a ContentProvider class in java code, you need to declare it publicly. So don't forget to register your ContentProvider in you AndroidManifest.xml file.

        android:authorities="edu.dartmouth.cs.myruns3.historyprovider" >

Checkpoint: The ExerciseEntryHelper is in the data storage layer. It deals with the database but it saves data to the database in a higher level. Only two functions are defined here: insertToDB() and deleteEntryInDB(). Take insertToDB() for an example, it converts the private ExerciseEntry to the ContentValues, and save the ContentValues into ContentProvider. To interact with ContentProvider, we use the previous database operation insert(). It is defined in the HistoryProvider. To get the ContentProvider resource in the application, you use the ContentResolver object in your application's Context to communicate with the provider as a client.

        Uri uri = context.getContentResolver().insert(HistoryProvider.CONTENT_URI,value);
        return Long.valueOf(uri.getLastPathSegment());

HistoryTabFragment: LoaderManager and mActivityEntryCursor

Checkpoint: HistoryTabFragment is in the Application layer. It uses ActivityEntryCursorAdapter that we customized to visualize the data in a format of list. To initialize the mActivityEntryCursor, simply call:

        mActivityEntryCursor = getActivity().getContentResolver().query(

LoaderManager helps the HistoryTabFragment to (1) load data on a separate thread, (2) monitor the underlying data source for updates, re-querying when changes are detected, and (3) refresh the cursor and update the cursor adapter more smoothly.

Your task is to implement three LoaderManager callbacks(onCreateLoader(), onLoadFinished() and onLoaderReset()) to guarantee the latest Cursor is loaded to the UI thread. To initialize the loader,

        LoaderManager lm = getLoaderManager();
        lm.initLoader(LOADER_ID, null, mCallbacks);

The final step of this lab is to implement the DisplayEntryActivity to allow user to view all the data he/she saved for one exercise entry. Also, the user can delete the whole entry by clicking the "Delete" button on the upper right side of the layout (see below). So two jobs for this activity: one is to display all the columns from the exercise entry to the list of TextView, the other is to set a Click Listener for the option menu to call the deleteEntryInDB function of the ExerciseEntryHelper.

Files explained

The source files that you have to code up breakdown into 2 activities, 3 fragments and 2 database helpers.

-- activities: displays one saved entry in detail, and allows the user delete the whole entry. lets the user choose one type of activity to start manually record your activity information.

-- fragments: displays all the saved entries in abstract. let you choose one type of activity to start recording. this fragment configures all the Dialogs that you need to record your activity information.  

-- database helpers: store your manual input in your customized ContentProvider. Store the ExerciseEntry into ContentProvider.

The other files that complete the design includes the following -- you need to code up all these files.

-- layout:
history.xml: this is the layout file for the HistoryTabFragment.
display_entry.xml: this is the layout file for the DisplayEntryActivity. display the entry in detail.
start.xml: this is the layout file for the StartTabFragment, two Spinner widgets here. 
manual_input.xml: this is the layout file for the ManualInputActivity.

-- values:
strings.xml: any additional string resources, etc. here

-- manifest:
AndroidManifest.xml: has to be updated to include new activities and contentprovider

Extra Credit

Find it in the skeleton's comment.


See webpage for submission information.