Android: List view from Database with Cursor Adapter


Latest:

I updated the code and solved the issues regarding running on lower API android.
I have tested on android version 2.1 (api level 7)

How to use:

1st screen is an empty list view.
Click on Enter Data button on first screen
2nd screen is a form view. add data to the text boxes and click add button
this data will be saved to database and take you to the first screen

Key Points

Cursor adapter is specialized adapter for a list view, when the data comes from database.

res/layout/main.xml

it has a list view, currently empty.
this list view will be populated with data coming from database


<?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" >

    <ListView android:id="@+id/list_data"
              android:layout_height="match_parent"
              android:layout_width="match_parent"
              android:layout_weight="1"/>

    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:layout_marginTop="5dp"
            android:text="Enter Data"
            android:onClick="onClickEnterData"/>

</LinearLayout>

res/layout/enter_data.xml

two text boxes for entering data and the data will be saved in database


<?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" >

    <EditText android:id="@+id/et_person_name"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginTop="5dip"
              android:singleLine="true"
              android:hint="Enter Name"
              android:inputType="textPersonName"
              android:imeOptions="actionNext"/>

    <EditText android:id="@+id/et_person_pin"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginTop="5dip"
              android:singleLine="true"
              android:hint="Enter PIN"
              android:inputType="numberDecimal"
              android:imeOptions="actionDone"/>

    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="Add"
            android:onClick="onClickAdd"/>

</LinearLayout>

res/layout/single_row_item.xml

this xml is responsible for how each item in list view will be looked


<?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">

    <TextView android:id="@+id/tv_person_name"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="Sample Data"
              android:textSize="15sp"
              android:paddingBottom="5dp" />

    <TextView android:id="@+id/tv_person_pin"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="Sample Data 2"
              android:textSize="15sp"
              android:paddingBottom="5dp" />

</LinearLayout>

CustomCursorAdapter.java


package com.example;

import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;

public class CustomCursorAdapter extends CursorAdapter {

    public CustomCursorAdapter(Context context, Cursor c) {
        super(context, c);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        // when the view will be created for first time,
        // we need to tell the adapters, how each item will look
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View retView = inflater.inflate(R.layout.single_row_item, parent, false);

        return retView;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        // here we are setting our data
        // that means, take the data from the cursor and put it in views

        TextView textViewPersonName = (TextView) view.findViewById(R.id.tv_person_name);
        textViewPersonName.setText(cursor.getString(cursor.getColumnIndex(cursor.getColumnName(1))));

        TextView textViewPersonPIN = (TextView) view.findViewById(R.id.tv_person_pin);
        textViewPersonPIN.setText(cursor.getString(cursor.getColumnIndex(cursor.getColumnName(2))));
    }
}

EnterDataActivity.java


package com.example;

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

public class EnterDataActivity extends Activity {

    EditText editTextPersonName;
    EditText editTextPersionPIN;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.enter_data);

        editTextPersonName = (EditText) findViewById(R.id.et_person_name);
        editTextPersionPIN = (EditText) findViewById(R.id.et_person_pin);
    }

    public void onClickAdd (View btnAdd) {

        String personName = editTextPersonName.getText().toString();
        String personPIN = editTextPersionPIN.getText().toString();

        if ( personName.length() != 0 && personPIN.length() != 0 ) {

            Intent newIntent = getIntent();
            newIntent.putExtra("tag_person_name", personName);
            newIntent.putExtra("tag_person_pin", personPIN);

            this.setResult(RESULT_OK, newIntent);

            finish();
        }
    }
}

MyActivity.java


package com.example;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;

public class MyActivity extends Activity {
	 
    private CustomCursorAdapter customAdapter;
    private PersonDatabaseHelper databaseHelper;
    private static final int ENTER_DATA_REQUEST_CODE = 1;
    private ListView listView;
    
    private static final String TAG = MyActivity.class.getSimpleName();
 
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        databaseHelper = new PersonDatabaseHelper(this);
 
        listView = (ListView) findViewById(R.id.list_data);
        listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				Log.d(TAG, "clicked on item: " + position);
			}
		});
 
        // Database query can be a time consuming task ..
        // so its safe to call database query in another thread
        // Handler, will handle this stuff for you <img src="http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif?m=1129645325g" alt=":)" class="wp-smiley">
 
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                customAdapter = new CustomCursorAdapter(MyActivity.this, databaseHelper.getAllData());
                listView.setAdapter(customAdapter);
            }
        });
    }
 
    public void onClickEnterData(View btnAdd) {
 
        startActivityForResult(new Intent(this, EnterDataActivity.class), ENTER_DATA_REQUEST_CODE);
 
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == ENTER_DATA_REQUEST_CODE && resultCode == RESULT_OK) {

            databaseHelper.insertData(data.getExtras().getString("tag_person_name"), data.getExtras().getString("tag_person_pin"));

            customAdapter.changeCursor(databaseHelper.getAllData());
        }
    }
}

PersonDatabaseHelper.java

package com.example;

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;

public class PersonDatabaseHelper {

    private static final String TAG = PersonDatabaseHelper.class.getSimpleName();

    // database configuration
    // if you want the onUpgrade to run then change the database_version
    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "mydatabase.db";

    // table configuration
    private static final String TABLE_NAME = "person_table";         // Table name
    private static final String PERSON_TABLE_COLUMN_ID = "_id";     // a column named "_id" is required for cursor
    private static final String PERSON_TABLE_COLUMN_NAME = "person_name";
    private static final String PERSON_TABLE_COLUMN_PIN = "person_pin";

    private DatabaseOpenHelper openHelper;
    private SQLiteDatabase database;

    // this is a wrapper class. that means, from outside world, anyone will communicate with PersonDatabaseHelper,
	// but under the hood actually DatabaseOpenHelper class will perform database CRUD operations 
    public PersonDatabaseHelper(Context aContext) {
		
        openHelper = new DatabaseOpenHelper(aContext);
        database = openHelper.getWritableDatabase();
    }

    public void insertData (String aPersonName, String aPersonPin) {

        // we are using ContentValues to avoid sql format errors

        ContentValues contentValues = new ContentValues();

        contentValues.put(PERSON_TABLE_COLUMN_NAME, aPersonName);
        contentValues.put(PERSON_TABLE_COLUMN_PIN, aPersonPin);

        database.insert(TABLE_NAME, null, contentValues);
    }

    public Cursor getAllData () {

        String buildSQL = "SELECT * FROM " + TABLE_NAME;

        Log.d(TAG, "getAllData SQL: " + buildSQL);

        return database.rawQuery(buildSQL, null);
    }

	// this DatabaseOpenHelper class will actually be used to perform database related operation 
	
    private class DatabaseOpenHelper extends SQLiteOpenHelper {

        public DatabaseOpenHelper(Context aContext) {
            super(aContext, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase sqLiteDatabase) {
            // Create your tables here

            String buildSQL = "CREATE TABLE " + TABLE_NAME + "( " + PERSON_TABLE_COLUMN_ID + " INTEGER PRIMARY KEY, " +
                    PERSON_TABLE_COLUMN_NAME + " TEXT, " + PERSON_TABLE_COLUMN_PIN + " TEXT )";

            Log.d(TAG, "onCreate SQL: " + buildSQL);

            sqLiteDatabase.execSQL(buildSQL);
        }

        @Override
        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
            // Database schema upgrade code goes here

            String buildSQL = "DROP TABLE IF EXISTS " + TABLE_NAME;

            Log.d(TAG, "onUpgrade SQL: " + buildSQL);

            sqLiteDatabase.execSQL(buildSQL);       // drop previous table

            onCreate(sqLiteDatabase);               // create the table from the beginning
        }
    }
}

AndroidManifest.xml

there is nothing special in this AndroidManifest file. still i am including this one, in case you need to see something


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example"
          android:versionCode="1"
          android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17"/>
    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
        <activity android:name="MyActivity"
                  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=".EnterDataActivity"/>
    </application>
</manifest>