In this tutorial we are going to build a simple todo app that is able to store simple todos in a database. The user is able to add new todos or delete old ones by clicking on a todo. For this tutorial we won’t use maven to keep it simple – if maven integration is desired – take a look at this tutorial.
Steps
-
Create a new android project using the Android SDK and your IDE
-
Create some packages com.hascode.android.activity and com.hascode.android.persistence
-
Create the layout in res/layout/main.xml – the main elements: a listview for the todos-list, a textbox and a button to enter new data.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/widget31" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TableRow android:id="@+id/row" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_below="@+id/tasklist" android:layout_alignParentLeft="true" > <EditText android:id="@+id/etNewTask" android:layout_width="200px" android:layout_height="wrap_content" android:text="" android:textSize="18sp" > </EditText> <Button android:id="@+id/btNewTask" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@+string/add_button_name" > </Button> </TableRow> <ListView android:id="@+id/tasklist" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" > </ListView> </RelativeLayout>
-
Externalize strings in the strings.xml - e.g. the button text or the application’s name
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">SimpleTodos</string> <string name="add_button_name">Add new todo</string> </resources>
-
Create a new activity in com.hascode.android.activity named SimpleTodoActivity. Logging activity is mapped at the defined Tag in APP_TAG.
package com.hascode.android.activity; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import com.hascode.android.R; import com.hascode.android.persistence.TaskProvider; public class SimpleTodoActivity extends Activity { public static final String APP_TAG = "com.hascode.android.simple-todos"; private ListView taskView; private Button btNewTask; private EditText etNewTask; private TaskProvider provider; /* * (non-Javadoc) * * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.main); provider = new TaskProvider(this); taskView = (ListView) findViewById(R.id.tasklist); btNewTask = (Button) findViewById(R.id.btNewTask); etNewTask = (EditText) findViewById(R.id.etNewTask); btNewTask.setOnClickListener(handleNewTaskEvent); renderTodos(); } /** * renders the task list */ private void renderTodos() { } }
-
Create the event handling for the button and add the following Listener to the class:
private OnClickListener handleNewTaskEvent = new OnClickListener() { @Override public void onClick(View view) { Log.d(APP_TAG, "add task click received"); provider.addTask(etNewTask.getText().toString()); renderTodos(); } };
-
Create an adapter class for database access in com.hascode.android.persistence named TodoProvider with methods for creating/deletion of database entries .. please note that the iterator cursor.next() does not exist anymore in the Cursor class .. new api .. new luck …
package com.hascode.android.persistence; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import com.hascode.android.activity.SimpleTodoActivity; public class TodoProvider { private static final String DB_NAME = "tasks"; private static final String TABLE_NAME = "tasks"; private static final int DB_VERSION = 1; private static final String DB_CREATE_QUERY = "CREATE TABLE " + TABLE_NAME + " (id integer primary key autoincrement, title text not null);"; private SQLiteDatabase storage; private SQLiteOpenHelper helper; public TodoProvider(Context ctx) { helper = new SQLiteOpenHelper(ctx, DB_NAME, null, DB_VERSION) { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DB_CREATE_QUERY); } }; storage = helper.getWritableDatabase(); } public List<String> findAll() { Log.d(SimpleTodoActivity.APP_TAG, "findAll triggered"); List<String> tasks = new ArrayList<String>(); Cursor c = storage.query(TABLE_NAME, new String[] { "title" }, null, null, null, null, null); if (c != null) { c.moveToFirst(); while (c.isAfterLast() == false) { tasks.add(c.getString(0)); c.moveToNext(); } c.close(); } return tasks; } public void addTask(String title) { ContentValues data = new ContentValues(); data.put("title", title); storage.insert(TABLE_NAME, null, data); } public void deleteTask(String title) { storage.delete(TABLE_NAME, "title='" + title + "'", null); } public void deleteTask(long id) { storage.delete(TABLE_NAME, "id=" + id, null); } }
-
Connect the activity to the database – implement renderTodos in the Activity and add an event listener for clicks on the list items. Adapters do the job of adding items to the listview. Please do not use setClickListener for the ListView – use setOnItemClickListener instead!
/** * renders the task list */ private void renderTodos() { List<String> todos = provider.findAll(); if (!todos.isEmpty()) { Log.d(APP_TAG, String.format("%d beans found", beans.size())); // render the list taskView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, todos .toArray(new String[] {}))); // dumb item deletion onclick taskView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d(APP_TAG, String.format( "item with id: %d and position: %d", id, position)); TextView v = (TextView) view; provider.deleteTask(v.getText().toString()); renderTodos(); } }); } else { Log.d(APP_TAG, "no tasks found"); } }
-
Sign your app as described in this article and deploy it if you wish to test it on your phone – currently I am using the new HTC Desire :) .If you’re using Eclipse and the Android Tools for Eclipse: Project > Android Tools > Export Signed Application Package __
Export and sign the Android Application -
Finally the app looks like this on my emulator:
Running the application on the emulator
Source download
-
You’re able to fetch the source code from this tutorial from GitHub
-
Alternatively if you have got Mercurial installed, just clone the repository using the following command
git clone http://github.com/hascode/android-simple-tasks.git
Troubleshooting
-
Examining the database with the Android Debugging Bridge – first list your connected devices:
>> adb devices List of devices attached emulator-5554 device
-
Connect to your device:
adb -s emulator-5554 shell
-
Connect to the database .. the path is /data/data/<your-app-package-name>/<your-database-name>.db
# sqlite3 /data/data/com.hascode.android/databases/tasks.db SQLite version 3.5.9 Enter ".help" for instructions
-
Look what you’ve got: Use the commands .databases and .tables to receive information about existing databases and tables
sqlite> .databases seq name file --- --------------- ---------------------------------------------------------- 0 main /data/data/com.hascode.android/databases/tasks.db
-
View detailed log information via logcat in the ADB console – you should define a filter to avoid information overflow
# logcat W/ActivityManager( 51): Launch timeout has expired, giving up wake lock! W/ActivityManager( 51): Activity idle timeout for HistoryRecord{43d60f08 com.hascode.android/.activity.SimpleTaskActivity} D/dalvikvm( 51): GC freed 12535 objects / 605968 bytes in 128ms I/Process ( 204): Sending signal. PID: 204 SIG: 9 I/ActivityManager( 51): Process com.hascode.android (pid 204) has died. I/UsageStats( 51): Unexpected resume of com.android.launcher while already resumed in com.hascode.android D/com.hascode.android.simple-task( 182): item with id: 3 and position: 3 clicked D/com.hascode.android.simple-task( 182): item with id: 2 and position: 2 clicked D/com.hascode.android.simple-task( 182): item with id: 1 and position: 1 clicked D/com.hascode.android.simple-task( 182): item with id: 1 and position: 1 clicked