Taste of Android :: Part-13


Bhaskar S 08/17/2013


Android Application Development - XII

Continuing the Taste of Android series, in this part, we will cover how to setup user menu options, customize Android display components (such as rounded corners, etc) using Android drawable shapes, provide support for user preference settings, and use database to persist information.

Let us create a new Android application named DroidDailyTodo which is a simple to-do list utility.

We will not go step-by-step to show the various screens since we already did that in Part-2 for the DroidTipCalculator application.

First we will need to download two icons - one that will be used in the image button to add new to-do tasks and the second that will be used in the About menu option. You can download tons of open source friendly icons from the site IconArchive and save them in the directory res/drawable-mdpi.

Modify the contents of the dimens.xml file to look like the one shown in the listing 13.1 below:

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

<resources>

<!-- Main -->

<dimen name="zero_width">0dp</dimen>
<dimen name="title_text_size">25sp</dimen>
<dimen name="title_padding">30dp</dimen>

<!-- Custom About Dialog -->

<dimen name="table_padding">15dp</dimen>
<dimen name="space_padding">50dp</dimen>
<dimen name="version_text_size">12sp</dimen>
<dimen name="name_text_size">20sp</dimen>
<dimen name="polarsparc_text_size">12sp</dimen>

<!-- Custom List Item -->

<dimen name="corner_radius">5dp</dimen>
<dimen name="item_padding">5dp</dimen>
<dimen name="border_width">5dp</dimen>
<dimen name="list_text_size">10sp</dimen>
<dimen name="list_padding">10dp</dimen>

<!-- Create To Do Activity -->

<dimen name="desc_text_size">25sp</dimen>
<dimen name="view_padding">25dp</dimen>

</resources>

Next, modify the contents of the strings.xml file to look like the one shown in the listing 13.2 below:

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

<resources>

<!-- Main -->

<string name="app_name">DroidDailyTodo</string>
<string name="description">Add To Do</string>
<string name="title">Simple Daily To Do</string>
<string name="action_name">com.polarsparc.android.CREATE_TODO</string>

<!-- Menu Items -->

<string name="action_settings">Settings</string>
<string name="action_about">About</string>

<!-- Custom About Dialog -->

<string name="about_desc">Daily To Do</string>
<string name="about_version">Version 1.0</string>
<string name="about_name">Simple Daily To Do</string>
<string name="about_polarsparc">Bought to you by PolarSPARC</string>

<!-- Create To Do Activity -->

<string name="enter_title">Enter Title</string>
<string name="create_todo">Create</string>
<string name="cancel_todo">Cancel</string>

<!-- Preferences Activity -->

<string name="pref_title">Preference Settings</string>
<string name="pref_delete_key">deleteKey</string>
<string name="pref_delete_title">Alert Delete</string>
<string name="pref_delete_summary">Alert on Delete of To Do on completion</string>
<string name="pref_theme_key">themeKey</string>
<string name="pref_theme_title">Select Theme</string>
<string name="pref_theme_summary">Color Theme for To Do List Entry</string>

<string-array name="theme_list">
<item name="1">Blue</item>
<item name="2">Brown</item>
<item name="3">Green</item>
</string-array>

<string-array name="theme_list_values">
<item name="1">1</item>
<item name="2">2</item>
<item name="3">3</item>
</string-array>
</resources>

Create a resource file for storing color definitions under res/values called colors.xml to look like the one shown in the listing 13.3 below:

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

<resources>
<!-- Main -->

<color name="title_text_color">#472400</color>

<!-- Custom About Dialog -->

<color name="about_back_color">#522900</color>
<color name="about_text_color">#FFFFFF</color>

<!-- Custom List Item -->

<color name="brown_border_color">#472400</color>
<color name="blue_border_color">#00248F</color>
<color name="green_border_color">#004700</color>
<color name="lite_brown_background">#FFCC99</color>
<color name="lite_blue_background">#B8DBFF</color>
<color name="lite_green_background">#A3FFA3</color>
<color name="list_text_color">#472400</color>

<!-- Create To Do Activity -->

<color name="create_back_color">#FFCC99</color>
</resources>

This application will have user menu options. In Android, there are two types of menus - Option Menus and Context Menus

The Context Menus is associated with any visual Android component such as the EditText and is activated when the user performs a long click on the visual component. We will not be using Context Menus in this application.

The Option Menus is usually associated with the main activity and appears in the Android Action Bar. The Android Action Bar is right below the top Status Bar and contains the application name. If an application has option menus, they will appear as three vertical dots in the Action Bar.

The figure 13.1 below shows the Action Bar and the three vertical dots, clicking on which will display the options menu:

Options Menu Image
Figure-13.1

One can either create an Option Menu programatically in the main activity or define the menu options in an external xml resource file and then inflate it in the main activity. It is much easier to define and manage an application menu in an external xml resource file and that is exactly what we will do in this application.

Create a resource file for storing the application option menu under res/menu called todo_list.xml to look like the one shown in the listing 13.4 below:

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

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

<item
android:id="@+id/action_settings"
android:title="@string/action_settings"/>

<item
android:id="@+id/action_about"
android:title="@string/action_about"/>

</menu>

From the xml resource file in listing 13.4 above, we gather the following:

For our application, we have defined an option menu with two items. One called Settings for the user preference settings and the other called About to display information about our application.

Later on we will see how this resource is leverage to inflate the application menu in the main activity.

Typical real-world applications often include user settings or preferences that allow users to control the behavior of the application such as the look and feel, etc. This application will allow the user to set preferences for two aspects - one to control the color of to-do task list and the other to control if an alert box needs to be displayed when the users completes a task by checking a to-do task.

For the user preference settings interface, one can either create an activity screen with the various Android View components or define the preference settings screen using an external xml resource file and have Android use it to interact with the user. In order to accomplish this, we need to use different sub-classes of the Android framework class Preference instead of the regular Android View sub-classes.

Note that the user preferences need to be remembered so the next time the user starts the application, the application needs to use the appropriate user settings. How do we do this ???

Enter the Android SharedPreferences. By using SharedPreferences, user preferences are saved in an xml file as name-value pairs that can be read when the application starts again.

For the user preference settings interface, let us create an xml resource file under res/xml called pref_settings.xml to look like the one shown in the listing 13.5 below:

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

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

<PreferenceCategory android:title="@string/pref_title" >
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/pref_delete_key"
android:summary="@string/pref_delete_summary"
android:title="@string/pref_delete_title"
/>

<ListPreference
android:key="@string/pref_theme_key"
android:entries="@array/theme_list"
android:summary="@string/pref_theme_summary"
android:entryValues="@array/theme_list_values"
android:title="@string/pref_theme_title"
/>
</PreferenceCategory>

</PreferenceScreen>

From the xml resource file in listing 13.5 above, we gather the following:

All the user interface components (Button, EditText, ListView, etc) we have explored so far used the default look and feel. Wouldn't it be cool if these user components had a border with rounded edges ??? How do we do this ???

Enter the Android Shape Drawables. A Shape Drawable is an xml resource file that defines a shape with borders and colors.

Create a xml resource file for defining a blue rectangular shape with rounder corners under res/drawable-mdpi called custom_blue_shape.xml to look like the one shown in the listing 13.6 below:

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

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<!-- Rounded corner -->

<corners android:radius="@dimen/corner_radius" />

<!-- Padding space -->

<padding android:left="@dimen/item_padding"
android:top="@dimen/item_padding"
android:right="@dimen/item_padding"
android:bottom="@dimen/item_padding" />

<!-- Background color -->

<solid android:color="@color/lite_blue_background" />

<!-- Border style -->

<stroke android:width="@dimen/border_width"
android:color="@color/blue_border_color" />

</shape>

From the xml resource file in listing 13.6 above, we gather the following:

We will create two additional xml resource files for defining a brown and green rectangular shapes with rounder corners under res/drawable-mdpi called custom_brown_shape.xml and custom_green_shape.xml.

The contents of custom_brown_shape.xml will look like the one shown in the listing 13.7 below:

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

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<!-- Rounded corner -->

<corners android:radius="@dimen/corner_radius" />

<!-- Padding space -->

<padding android:left="@dimen/item_padding"
android:top="@dimen/item_padding"
android:right="@dimen/item_padding"
android:bottom="@dimen/item_padding" />

<!-- Background color -->

<solid android:color="@color/lite_brown_background" />

<!-- Border style -->

<stroke android:width="@dimen/border_width"
android:color="@color/brown_border_color" />

</shape>

The contents of custom_green_shape.xml will look like the one shown in the listing 13.8 below:

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

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >

<!-- Rounded corner -->

<corners android:radius="@dimen/corner_radius" />

<!-- Padding space -->

<padding android:left="@dimen/item_padding"
android:top="@dimen/item_padding"
android:right="@dimen/item_padding"
android:bottom="@dimen/item_padding" />

<!-- Background color -->

<solid android:color="@color/lite_green_background" />

<!-- Border style -->

<stroke android:width="@dimen/border_width"
android:color="@color/green_border_color" />

</shape>

We will have four layout definitions files - the first is for the main to-do list screen, the second is for each row of the to-do list, the third is for the screen to create a to-do entry, and the last is for displaying the about information.

The layout file for the main to-do list screen is named activity_daily_todo.xml, the layout file for the to-do list row is named todo_list_item.xml, the layout file for creating a to-do entry is named activity_create_todo.xml, and the layout file for displaying the about information is named dialog_about.xml.

The contents of the activity_daily_todo.xml layout file will look like the one shown in the listing 13.9 below:

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

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="@dimen/list_padding" >

<TableRow>
<TextView
android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_width="@dimen/zero_width"
android:layout_weight="0.6"
android:padding="@dimen/list_padding"
android:textColor="@color/list_text_color"
android:textSize="@dimen/list_text_size"
android:textStyle="bold"
android:text=""
/>

<TextView
android:id="@+id/due_time"
android:layout_height="wrap_content"
android:layout_width="@dimen/zero_width"
android:layout_weight="0.25"
android:padding="@dimen/list_padding"
android:textColor="@color/list_text_color"
android:textSize="@dimen/list_text_size"
android:textStyle="bold"
android:text=""
/>

<CheckBox
android:id="@+id/completed"
android:layout_height="wrap_content"
android:layout_width="@dimen/zero_width"
android:layout_weight="0.15"
android:padding="@dimen/list_padding"
/>
</TableRow>

</TableLayout>

The contents of the todo_list_item.xml layout file will look like the one shown in the listing 13.10 below:

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

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="@dimen/list_padding" >

<TableRow>
<TextView
android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_weight="0.6"
android:padding="@dimen/list_padding"
android:textColor="@color/list_text_color"
android:textSize="@dimen/list_text_size"
android:textStyle="bold"
android:text=""
/>

<TextView
android:id="@+id/due_time"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:padding="@dimen/list_padding"
android:textColor="@color/list_text_color"
android:textSize="@dimen/list_text_size"
android:textStyle="bold"
android:text=""
/>

<CheckBox
android:id="@+id/completed"
android:layout_height="wrap_content"
android:layout_weight="0.15"
android:padding="@dimen/list_padding"
/>
</TableRow>

</TableLayout>

The contents of the activity_create_todo.xml layout file will look like the one shown in the listing 13.11 below:

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/create_back_color"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/view_padding"
android:orientation="horizontal" >
<TextView
android:layout_width="@dimen/zero_width"
android:layout_height="wrap_content"
android:layout_weight="0.37"
android:layout_gravity="center"
android:textColor="@color/title_text_color"
android:textSize="@dimen/desc_text_size"
android:textStyle="bold"
android:text="@string/enter_title"
/>

<EditText
android:id="@+id/title_txt"
android:background="@drawable/custom_brown_shape"
android:layout_width="@dimen/zero_width"
android:layout_height="wrap_content"
android:layout_weight="0.63"
android:maxLength="28"
android:text=""
/>
</LinearLayout>

<TimePicker
android:id="@+id/time_due"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/view_padding"
/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/create_todo"
android:background="@drawable/custom_brown_shape"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/create_todo"
/>

<Button
android:id="@+id/cancel_todo"
android:background="@drawable/custom_brown_shape"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel_todo"
/>
</LinearLayout>

</LinearLayout>

In the listing 13.11 above, notice the use of the attribute android:background which is set to @drawable/custom_brown_shape. This is the drawable shape we defined in listing 13.7 above.

Previewing the activity_create_todo.xml layout file in the Graphical Mode in Eclipse will look like the one shown in the figure 13.2 below:

Create Todo Layout Image
Figure-13.2

The contents of the dialog_about.xml layout file will look like the one shown in the listing 13.12 below:

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

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/space_padding"
android:background="@color/about_back_color" >

<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/table_padding" >

<ImageView
android:layout_width="match_parent"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/about_desc"
android:src="@drawable/ic_todo_list" />

</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/table_padding" >

<TextView
android:layout_width="match_parent"
android:gravity="center_horizontal"
android:text="@string/about_version"
android:textColor="@color/about_text_color"
android:textSize="@dimen/version_text_size"
android:textStyle="bold"
android:typeface="sans" />

</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/table_padding" >

<TextView
android:layout_width="match_parent"
android:gravity="center_horizontal"
android:text="@string/about_name"
android:textColor="@color/about_text_color"
android:textSize="@dimen/name_text_size"
android:textStyle="bold"
android:typeface="sans" />

</TableRow>

<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/table_padding" >

<TextView
android:layout_width="match_parent"
android:gravity="center_horizontal"
android:text="@string/about_polarsparc"
android:textColor="@color/about_text_color"
android:textSize="@dimen/polarsparc_text_size"
android:textStyle="italic"
android:typeface="sans" />

</TableRow>

</TableLayout>

Previewing the dialog_about.xml layout file in the Graphical Mode in Eclipse will look like the one shown in the figure 13.3 below:

Create Todo Layout Image
Figure-13.3

We will need an object to encapsulate the user preference information. The preference information is encapsulated in the class PreferenceState and the contents of the java source file PreferenceState.java will look like the one shown in the listing 13.13 below:

Listing-13.13
package com.polarsparc.android.droiddailytodo;

public class PreferenceState {
private boolean alertFlag = false;

private int themeIndex = 0;

public boolean isAlertFlag() {
return alertFlag;
}

public void setAlertFlag(boolean alertFlag) {
this.alertFlag = alertFlag;
}

public int getThemeIndex() {
return themeIndex;
}

public void setThemeIndex(int themeIndex) {
this.themeIndex = themeIndex;
}
}

We will need an object to encapsulate each to-do task entry such as the title and the due time (hour and minute). This information is encapsulated in the class TodoTask. The contents of the java source file TodoTask.java will look like the one shown in the listing 13.14 below:

Listing-13.14
package com.polarsparc.android.droiddailytodo;

public class TodoTask {
private int hour;
private int minute;

private long id; // Database key

private String title;

public int getHour() {
return hour;
}

public void setHour(int hour) {
this.hour = hour;
}

public int getMinute() {
return minute;
}

public void setMinute(int minute) {
this.minute = minute;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}

In our simple to-do application, we will be adding new to-do task(s) and once the task(s) are completed remove them. We will need an object to manage these operations of adding, removing, or fetching TodoTask instance(s) from a list in memory. These operations are encapsulated in the class TodoTaskManager. The contents of the java source file TodoTaskManager.java will look like the one shown in the listing 13.15 below:

Listing-13.15
package com.polarsparc.android.droiddailytodo;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ArrayList;

public class TodoTaskManager {
private List<TodoTask> tasks = new ArrayList<TodoTask>();

private TodoTaskComparator comparator = new TodoTaskComparator();

public List<TodoTask> getTodoTaskList() {
return tasks;
}

public void addAllTodoTasks(List<TodoTask> list) {
tasks.addAll(list);
}

public TodoTask getTodoTask(int index) {
return tasks.get(index);
}

public TodoTask addTodoTask(String title, int hour, int minute) {
TodoTask entry = null;

if (title != null && title.isEmpty() == false) {
entry = new TodoTask();
entry.setTitle(title);
entry.setHour(hour);
entry.setMinute(minute);

tasks.add(entry);

Collections.sort(tasks, comparator);
}

return entry;
}

public TodoTask removeTodoTask(int index) {
return tasks.remove(index);
}

// ----- Inner Class(es) -----

class TodoTaskComparator implements Comparator<TodoTask> {
public int compare(TodoTask one, TodoTask two) {
if (one.getHour() < two.getHour()) {
return -1;
}
if (one.getHour() > two.getHour()) {
return 1;
}
if (one.getMinute() < two.getMinute()) {
return -1;
}
if (one.getMinute() > two.getMinute()) {
return 1;
}
return 0;
}
}
}

We dont want to lose our to-do tasks in memory in case we move to another application or close the application. We want to persist them in a permanent store - enter SQLite. SQLite is an open-source lightweight embedded database that is included in Android. We need an object to perform persistence operations of adding, removing, or fetching TodoTask instance(s) from the database. These operations are encapsulated in the class TodoDatabaseManager. The contents of the java source file TodoDatabaseManager.java will look like the one shown in the listing 13.16 below:

Listing-13.16
package com.polarsparc.android.droiddailytodo;

import java.util.List;
import java.util.ArrayList;

import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class TodoDatabaseManager extends SQLiteOpenHelper {

private static String TAG = "TodoDatabaseManager";

private static final int DATABASE_VERSION = 1;

private static final String DATABASE_NAME = "TodoTasks.db";

private static final String TODO_TASKS_TABLE = "todo_tasks";

private static final String TODO_ID = "id";
private static final String TODO_TITLE = "title";
private static final String TODO_DUE_HOUR = "due_hour";
private static final String TODO_DUE_MINUTE = "due_minute";

private static final String CREATE_TODO_TABLE = "CREATE TABLE " +
TODO_TASKS_TABLE + "(" +
TODO_ID + " INTEGER PRIMARY KEY," +
TODO_TITLE + " TEXT," +
TODO_DUE_HOUR + " INTEGER, " +
TODO_DUE_MINUTE + " INTEGER" +
")";

private static final String DROP_TODO_TABLE = "DROP TABLE IF EXISTS " +
TODO_TASKS_TABLE;

private static final String QUERY_TODO_TABLE = "SELECT * FROM " +
TODO_TASKS_TABLE;

public TodoDatabaseManager(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TODO_TABLE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DROP_TODO_TABLE);
onCreate(db);
}

public boolean insertTodoEntry(TodoTask entry) {
SQLiteDatabase db = this.getWritableDatabase();

ContentValues record = new ContentValues();
record.put(TODO_TITLE, entry.getTitle());
record.put(TODO_DUE_HOUR, entry.getHour());
record.put(TODO_DUE_MINUTE, entry.getMinute());

long id = -1;
if ((id = db.insert(TODO_TASKS_TABLE, null, record)) > 0) {
db.close();

entry.setId(id);

Log.i(TAG, "insertTodoEntry(): Successfully added entry for " +
entry.getTitle() +
" <" + entry.getId() + ">");

return true;
}

return false;
}

public void deleteTodoEntry(TodoTask entry) {
SQLiteDatabase db = this.getWritableDatabase();

db.delete(TODO_TASKS_TABLE, TODO_ID + " = ?",
new String[] { String.valueOf(entry.getId()) });
db.close();

Log.i(TAG, "deleteTodoEntry(): Successfully deleted entry for " +
entry.getTitle() + " <" +
entry.getId() + ">");
}

public List<TodoTask> fetchAllTodoEntries() {
List<TodoTask> list = new ArrayList<TodoTask>();

SQLiteDatabase db = this.getWritableDatabase();

Cursor cursor = db.rawQuery(QUERY_TODO_TABLE, null);

if (cursor.moveToFirst()) {
do {
TodoTask entry = new TodoTask();

entry.setId(cursor.getLong(0));
entry.setTitle(cursor.getString(1));
entry.setHour(cursor.getInt(2));
entry.setMinute(cursor.getInt(3));

list.add(entry);

} while (cursor.moveToNext());
}

cursor.close();

db.close();

Log.i(TAG, "fetchAllTodoEntries(): List size: " + list.size());

return list;
}

}

From the code listing 13.16 above, we gather the following:

In listing 13.5 above, we laid out the contents of the user preferences file pref_settings.xml.

We mentioned that it uses sub-classes of the Android class Preference which is different from the regular Android View sub-classes. This means we will need a way to display the user settings interface. Hence the need for a custom activity class that extends the Android framework class android.preference.PreferenceActivity.

In this application, we create the custom class named DailyTodoPreferences for this purpose.

The contents of the java source file DailyTodoPreferences.java will look like the one shown in the listing 13.17 below:

Listing-13.17
package com.polarsparc.android.droiddailytodo;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class DailyTodoPreferences extends PreferenceActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.pref_settings);
}

}

In the listing 13.11 above, we show the contents of the layout for the to-do task creation screen. The corresponding class to launch this activity is encapsulated in the class CreateTodoActivity and the contents of the java source file CreateTodoActivity.java will look like the one shown in the listing 13.18 below:

Listing-13.18
package com.polarsparc.android.droiddailytodo;

import java.util.Calendar;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TimePicker;

public class CreateTodoActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_todo);

// Due Time
final TimePicker picker = (TimePicker) findViewById(R.id.time_due);
picker.setIs24HourView(true);
picker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
@Override
public void onTimeChanged(TimePicker view, int hour, int minute) {
Calendar cal = Calendar.getInstance();

int curHour = cal.get(Calendar.HOUR_OF_DAY);
int curMinute = cal.get(Calendar.MINUTE);

if (hour >= curHour && minute > curMinute) {
picker.setCurrentHour(hour);
picker.setCurrentMinute(minute);
}
}
});

// Create button
final Button create = (Button) findViewById(R.id.create_todo);
create.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Title
final EditText title = (EditText) findViewById(R.id.title_txt);

// Due Time
final TimePicker picker = (TimePicker) findViewById(R.id.time_due);

if (title.getText().length() > 0) {
Intent intent = getIntent();
intent.putExtra("todoTitle", title.getText().toString());
intent.putExtra("pickedHour", picker.getCurrentHour());
intent.putExtra("pickedMinute", picker.getCurrentMinute());

setResult(Activity.RESULT_OK, intent);

// Close this activty
finish();
}
}
});

// Cancel button
final Button cancel = (Button) findViewById(R.id.cancel_todo);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Close this activty
finish();
}
});
}

}

In the listing 13.9 above, we show the contains of the main to-do application user interface.

As we create new to-do tasks, they will be displayed in a sorted order by due time (hour and minute) in a ListView of the main to-do application screen.

The TodoTaskListAdapter class is a custom ArrayAdapter that provides the list of to-do tasks to the ListView of the activity activity_daily_todo.xml.

The contents of the java source file TodoTaskListAdapter.java will look like the one shown in the listing 13.19 below:

Listing-13.19
package com.polarsparc.android.droiddailytodo;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;

public class TodoTaskListAdapter extends ArrayAdapter<TodoTask> {

private String format = "Due @ %02d:%02d";

private Context context;

private PreferenceState state;

private TodoTaskManager manager;

private TodoDatabaseManager database;

public TodoTaskListAdapter(Context context,
PreferenceState state,
TodoTaskManager manager,
TodoDatabaseManager database) {
super(context, R.layout.todo_list_item, manager.getTodoTaskList());
this.context = context;
this.state = state;
this.manager = manager;
this.database = database;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = inflater.inflate(R.layout.todo_list_item, parent, false);
}

switch (state.getThemeIndex()) {
case 1:
convertView.setBackgroundResource(R.drawable.custom_blue_shape);
break;
case 2:
convertView.setBackgroundResource(R.drawable.custom_brown_shape);
break;
case 3:
convertView.setBackgroundResource(R.drawable.custom_green_shape);
break;
}

final int index = position;

final TodoTask task = manager.getTodoTask(index);

final String title = task.getTitle();
final String time = String.format(format, task.getHour(), task.getMinute());

// Title
TextView todoTitle = (TextView) convertView.findViewById(R.id.title);
todoTitle.setText(title);
todoTitle.requestLayout(); // Very important

// Due Time
TextView dueTime = (TextView) convertView.findViewById(R.id.due_time);
dueTime.setText(time);
dueTime.requestLayout(); // Very important

// Completed
CheckBox completed = (CheckBox) convertView.findViewById(R.id.completed);
completed.setChecked(false);
completed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (((CheckBox)view).isChecked()) {
if (state.isAlertFlag()) {
AlertDialog alert = new AlertDialog.Builder(context).create();
alert.setMessage("Completed To Do Task - " + title);
alert.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
TodoTask entry = manager.removeTodoTask(index);
database.deleteTodoEntry(entry);
notifyDataSetChanged();
}
});
alert.show();
}
else {
TodoTask entry = manager.removeTodoTask(index);
database.deleteTodoEntry(entry);
notifyDataSetChanged();
Toast.makeText(context, "Completed To Do Task - " +
title, Toast.LENGTH_LONG).show();
}
}
}
});

return convertView;
}

}

As can be gathered from the listing 13.19 above, we pass in a reference to an instance of PreferenceState, an instance of TodoTaskManager, and an instance of TodoDatabaseManager to the class TodoTaskListAdapter.

We need TodoTaskManager and TodoDatabaseManager to delete the to-do task(s) from both the in-memory list and the database when the user clicks on the checkbox.

We need PreferenceState for two things: one to use the appropriate color theme for the list items (blue, brown, or green) and two to determine if we need to display an alert box when the user clicks on the checkbox.

In the listing 13.9 above, we show the contents of the layout for the main to-do list screen. The corresponding class to launch this activity is encapsulated in the class MainDailyTodoActivity and the contents of the java source file MainDailyTodoActivity.java will look like the one shown in the listing 13.20 below:

Listing-13.20
package com.polarsparc.android.droiddailytodo;

import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ListView;
import android.util.Log;

public class MainDailyTodoActivity extends Activity {

private static String TAG = "MainDailyTodoActivity";

final int TODO_PREFERENCE = 1;
final int CREATE_TODO = 2;

private PreferenceState state;

private TodoTaskManager manager;

private TodoDatabaseManager database;

private ArrayAdapter<TodoTask> adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_daily_todo);

state = new PreferenceState();

final String deleteKey = getResources().getString(R.string.pref_delete_key);
final String themeKey = getResources().getString(R.string.pref_theme_key);

Log.i(TAG, "MainDailyTodoActivity - onCreate(): deleteKey = " + deleteKey);
Log.i(TAG, "MainDailyTodoActivity - onCreate(): themeKey = " + themeKey);

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

state.setAlertFlag(preferences.getBoolean(deleteKey, false));
state.setThemeIndex(Integer.valueOf(preferences.getString(themeKey, "0")));

Log.i(TAG, "MainDailyTodoActivity - onCreate(): alertFlag = " +
state.isAlertFlag());
Log.i(TAG, "MainDailyTodoActivity - onCreate(): themeIndex = " +
state.getThemeIndex());

manager = new TodoTaskManager();

database = new TodoDatabaseManager(this);

manager.addAllTodoTasks(database.fetchAllTodoEntries());

adapter = new TodoTaskListAdapter(this, state, manager, database);

final ImageButton imagebutton = (ImageButton) findViewById(R.id.add_todo);
imagebutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String action = getResources().getString(R.string.action_name);

Intent intent = new Intent(action);

startActivityForResult(intent, CREATE_TODO);
}
});

final ListView listview = (ListView) findViewById(R.id.list);
listview.setAdapter(adapter);
}

/*
* Callback method when create todo activity returns a result
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TODO_PREFERENCE) {
final String deleteKey = getResources().getString(R.string.pref_delete_key);
final String themeKey = getResources().getString(R.string.pref_theme_key);

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

state.setAlertFlag(preferences.getBoolean(deleteKey, false));
state.setThemeIndex(Integer.valueOf(preferences.getString(themeKey, "0")));

Log.i(TAG, "MainDailyTodoActivity - onActivityResult(): alertFlag = " +
state.isAlertFlag());
Log.i(TAG, "MainDailyTodoActivity - onActivityResult(): themeIndex = " +
state.getThemeIndex());

adapter.notifyDataSetChanged();
}
else if (requestCode == CREATE_TODO && resultCode == Activity.RESULT_OK) {
int hour = data.getIntExtra("pickedHour", -1);
int minute = data.getIntExtra("pickedMinute", -1);

String title = data.getStringExtra("todoTitle");

Log.i(TAG, "MainDailyTodoActivity - onActivityResult(): hour = " + hour);
Log.i(TAG, "MainDailyTodoActivity - onActivityResult(): minute = " + minute);
Log.i(TAG, "MainDailyTodoActivity - onActivityResult(): title = " + title);

if (hour >= 0 && minute >= 0 && title != null && title.isEmpty() == false) {
TodoTask entry = manager.addTodoTask(title, hour, minute);
if (entry != null) {
database.insertTodoEntry(entry);
adapter.notifyDataSetChanged();
}
}
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.todo_list, menu);

return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
{
Intent intent = new Intent(this, DailyTodoPreferences.class);

startActivityForResult(intent, TODO_PREFERENCE);

return true;
}

case R.id.action_about:
{
LayoutInflater inflater = LayoutInflater.from(this);

View view = inflater.inflate(R.layout.dialog_about, null);

AlertDialog alert = new AlertDialog.Builder(this).create();
alert.setView(view);
alert.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Do nothing
}
});
alert.show();

return true;
}
}

return false;
}
}

From the code listing 13.20 above, we gather the following:

Finally, modify the contents of the AndroidManifest.xml file to look like the one shown in the listing 13.21 below:

Listing-13.21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.polarsparc.android.droiddailytodo"
android:versionCode="1"
android:versionName="1.0" >

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

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/AppTheme" >

<activity
android:name="com.polarsparc.android.droiddailytodo.MainDailyTodoActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name="com.polarsparc.android.droiddailytodo.CreateTodoActivity"
android:label="Create Todo" >
<intent-filter>
<action android:name="com.polarsparc.android.CREATE_TODO" />

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<activity
android:name="com.polarsparc.android.droiddailytodo.DailyTodoPreferences"
/>

</application>

</manifest>

We are now ready to test our DroidDailyTodo application on the virtual Android device we created in Part-1. We will create a Run Configuration for DroidDailyTodo as we did in Part-2 for DroidTipCalculator.

Once the run configuration for DroidDailyTodo is ready, we will Run the application and the application will come to life as shown in the following figure 13.4 below:

Main To-do App Image
Figure-13.4

Clicking on the image with the Green Plus will launch the activity activity_create_todo.xml as shown in the following figure 13.5 below:

Create To-do Task Image
Figure-13.5

Key in a to-do task title and select a desired due time (hour and minute). Then click on the Create button as shown in the following figure 13.6 below:

New To-do Task Image
Figure-13.6

This will bring us back to the main screen as shown in the following figure 13.7 below:

Main To-do List Image
Figure-13.7

We will go ahead and create one more to-do task and in the end we be in the main screen as shown in the following figure 13.8 below:

Main To-do List Image
Figure-13.8

Click on the options menu (three vertical dots) in the action bar as shown in the following figure 13.9 below:

Click Menu Image
Figure-13.9

This action will display the options menu as shown in the following figure 13.10 below:

Options Menu Image
Figure-13.10

Click on the Settings menu item and this action will display the user preferences screen as shown in the following figure 13.11 below:

User Settings Image
Figure-13.11

Click on the Select Theme settings and this action will display the select theme settings screen as shown in the following figure 13.12 below:

Select Theme Image
Figure-13.12

Click on the Blue theme and this action will bring us back to the screen shown in figure 13.12 above. Click on the Android Back Button as shown in the following figure 13.13 below:

Android Back Image
Figure-13.13

This action will bring us back to the main screen as shown in the following figure 13.14 below:

Main Blue Theme Image
Figure-13.14

Notice the color of the to-do task entries now - it uses the drawable shape custom_blue_shape.xml as the theme.

When we click on the checkbox to complete a to-do task, they are removed both from the screen and from the database and a Toast messagee is displayed as shown in the following figure 13.15 below:

Delete Toast Image
Figure-13.15

Let us now go back to the Settings and this time click on the Alert Delete settings menu item and this action will bring us to the screen as shown in the following figure 13.16 below:

Alert Delete Image
Figure-13.16

Lets us go back to the main screen and now when we click on the checkbox to complete a to-do task, this action will display an alert dialog before removing the to-do entry from the screen and from the database as shown in the following figure 13.17 below:

Delete Alert Image
Figure-13.17

Clicking on the About menu item will display an information alert dialog screen as shown in the following figure 13.18 below:

About Dialog Image
Figure-13.18

Additional Information

Where's My Data

One may be curious where Android may be saving the user preference settings and the database.

In Eclipse, one is typically in the Java perspective. You will also find the DDMS perspective. Click on the DDMS perspective. Then click on the File Explorer tab. We will see the Devices pane on the left and the Directory pane on the right. We will be in a screen that will look as shown in the figure 13.19 below:

DDMS Image
Figure-13.19

From the Devices pane on the left, select com.polarsparc.android.droiddailytodo. From the Directory pane on the right, expand the directory data and inside it expand the directory data as shown in the following figure 13.20 below:

Data Data Image
Figure-13.20

Scroll down the Directory pane on the right and expand the directory com.polarsparc.android.droiddailytodo. Inside it, we will see a directory for databases and another for shared_prefs. These are the directories where we see our database and the user preference file as shown in the figure 13.21 below:

Files Image
Figure-13.21

Disappearing Act

Did you closely observe the code listing for the class TodoTaskListAdapter in listing 13.19 above ?

Pay attention to the method call requestLayout() after setting the text for the two TextViews - todoTitle and dueTime.

If we did not call requestLayout(), we would encounter a very strange behavior - only the first character seemed to be displayed in each column of each row as we added new to-do tasks as shown in the following figure 13.22 below:

Disappearing Text Image
Figure-13.22

References

Taste of Android :: Part-1

Taste of Android :: Part-2

Taste of Android :: Part-3

Taste of Android :: Part-4

Taste of Android :: Part-5

Taste of Android :: Part-6

Taste of Android :: Part-7

Taste of Android :: Part-8

Taste of Android :: Part-9

Taste of Android :: Part-10

Taste of Android :: Part-11

Taste of Android :: Part-12