Working with SQLite Database (CRUD operations) in Android

We’ve covered data storage options like SharedPreferences and Internal/External Storage before. It’s time to look at another option which is basically SQLite databases. Most data-driven Android applications will need a solution to store structured (relational) data. If that’s the case then SQLite is an amazing option that Android supports out of the box. The SQLite database management classes/APIs are available in the android.database.sqlite package. We’ll learn how to create an SQLite database and then save data into it as well as retrieve, modify and delete data.

Note: This article will not cover the basics of database related concepts like tables, columns, rows, select/insert/update/delete queries or even relational data. I assume you’re already familiar with the basics of a RDBMS (relational database management system). If you’ve worked with MySQL, MariaDB, PostgreSQL, SQLite, Oracle, SQL Server or any other SQL based DB before then you’ll be familiar with these concepts already.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Creating a Database

A database is created by creating a subclass of SQLiteOpenHelper and executing an SQL command (query) in the onCreate() method. Let’s see some code to clarify this:

public class PostDatabase extends SQLiteOpenHelper {

    private static final String TAG = "PostDatabase";

    // Database Specific Details
    
    // If you change the database schema, you must increment the database version.
    private static final int DB_VERSION = 1;
    // DB Name, same is used to name the sqlite DB file
    private static final String DB_NAME = "test_db";

    // `posts` table details
    
    public static final String TABLE_POSTS = "posts";
    public static final String ID = "id";
    public static final String COL_TITLE = "title";
    public static final String COL_CONTENT = "content";

    private static final String CREATE_TABLE_POSTS =
            "CREATE TABLE " + TABLE_POSTS
            + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
            + COL_TITLE + " TEXT NOT NULL, "
            + COL_CONTENT + " TEXT NOT NULL);";


    public PostDatabase(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // Called when the database is created for the
        // first time. This is where the creation of
        // tables and the initial population of the tables should happen.

        db.execSQL(CREATE_TABLE_POSTS);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // Called when the database needs to be upgraded.
        // The implementation should use this method to
        // drop tables, add tables, or do anything else
        // it needs to upgrade to the new schema version.

        Log.w(TAG, "Upgrading database. Existing contents will be lost. ["
                + oldVersion + "] -> [" + newVersion + "]");

        db.execSQL("DROP TABLE IF EXISTS " + TABLE_POSTS);

        // Create after dropping
        onCreate(db);
    }
}

So we created a PostDatabase class that extends SQLiteOpenHelper. You’ll notice several constants defining database name, database version, table names, table fields (columns), table creation SQL query, etc. This might seem a little strange if you’re coming from web development or some other domain where such practises were not follower but this is what Android recommends. This way you can use the same constants across all the classes in the same package and change in the value/name of one constant in this class will propagate everywhere else. If you’re familiar with SQL and OOP then all this should be a piece of cake for your really.

The database and its tables (single table in our case) are created in the onCreate() method whereas if the DB_VERSION is changed (bumped to say 2) then onUpgrade() will be triggered next time some class tries to access the database.

Contract Class

This class that we coded is also known as a contract class. A contract class explicitly specifies the schema (how a database and its tables are organized) in a systematic and self-documenting way. It contains constants that define URIs, table names, column names, etc. These constants can be consistently use across all the classes in the same package.

There’s another good recommended way to organize a contract class. Let’s see an example:

public final class DatabaseContract {

    public static abstract class Post implements BaseColumns {
        public static final String TABLE_NAME = "posts";
        public static final String COL_TITLE = "title";
        public static final String COL_CONTENT = "content";
    }
}

So any details/definitions global to the database can be put in the root level whereas for every table an inner class is created listing its columns that can be used by your SQLiteOpenHelper implementation to generate the CREATE and DROP SQL queries. Infact these queries could also reside in the contract classes depending upon your usage.

If you notice we implemented the BaseColumns interface that makes Post inner class inherit a field called _ID which is the unique primary key field required by some Android classes like the cursor adapters. Hence this can help your DB to work seamlessly with the Android framework. So this basically means that instead of having your ID column’s name as id or something else, it should be _id which is the value of BaseColumns._ID.

You must be wondering that we just defined our schema and create/update operations but how or when is the database and its tables created ? We’ll cover that in the next section (hint: on calling getWritableDatabase()).

Reading to/Writing From the Database

Since our DB definition is sorted, now we need to look at how to start querying our DB and even writing to it. So what we have to basically do is, instantiate our SQLiteOpenHelper implementation and call getReadableDatabase() (read-only) or getWritableDatabase() (read and write) on it. When getWritableDatabase() is called for the first time, the database will be opened and onCreate(), onUpgrade() and/or onOpen() will be called.

// From MainActivity.onCreate()

PostDatabase DB = new PostDatabase(MainActivity.this);
SQLiteDatabase sqlDB = DB.getWritableDatabase();

// Execute queries...

sqlDB.close();

The call the getWritableDatabase() returns an object of SQLiteDatabase. This class has all the methods to interact (read/write) with the database.

The newly created database is private to the application and is created at DATA/data/APP_NAME/databases/FILENAME where DATA is what Environment.getDataDirectory() returns (generally /data), APP_NAME is your package name and FILENAME is the database name specified in the SQLiteOpenHelper implementation.

Create Data (INSERT)

Let’s write some code to see how we can create records in the Database.

ContentValues values = new ContentValues();
values.put(PostDatabase.COL_TITLE, "Test Title");
values.put(PostDatabase.COL_CONTENT, "Test Content");

sqlDB.insert(PostDatabase.TABLE_POSTS, null, values);

We used the insert() method to insert a single record. You can inspect the database file from ADB like this:

$ adb shell
root@vbox86p:/ # sqlite3 /data/data/com.pycitup.pyc/databases/test_db
SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
android_metadata  posts
sqlite> select * from posts;
1|Test Title|Test Content

Check the last two lines where I select all the records from the posts table and it prints the record that we had inserted. sqlite3 database tool ships with the Android SDK that lets you inspect and browse the contents of a database and its tables. It is a standalone command line program that lets you perform many other useful functions too. More information here.

You’ll notice that we used a class called ContentValues to set key/value pairs for insertion. It’s just a convenience class for inserting and updating entries in a database table.

Read/Retrieve Information (SELECT)

There are a couple of ways to fetch data from a particular table. Let’s see how:

// Insert a record

ContentValues values = new ContentValues();
values.put(PostDatabase.COL_TITLE, "Test Title");
values.put(PostDatabase.COL_CONTENT, "Test Content");

sqlDB.insert(PostDatabase.TABLE_POSTS, null, values);

// Raw Query
Cursor cursor = sqlDB.rawQuery("SELECT * FROM posts WHERE id = ?", new String[]{ "1" });

if (cursor != null && cursor.moveToFirst()) {
    String title = cursor.getString(cursor.getColumnIndex(PostDatabase.COL_TITLE));
    String content = cursor.getString(cursor.getColumnIndex(PostDatabase.COL_CONTENT));

    // Dumps "Title: Test Title Content: Test Content"
    Log.d(TAG, "Title: " + title + " Content: " + content);

    cursor.close();
}


// Another insertion (second)

values = new ContentValues();
values.put(PostDatabase.COL_TITLE, "Second Title");
values.put(PostDatabase.COL_CONTENT, "Second Content");

sqlDB.insert(PostDatabase.TABLE_POSTS, null, values);


// Now we'll use the `query()` method
// public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)

cursor = sqlDB.query(PostDatabase.TABLE_POSTS,
        new String[] { PostDatabase.ID, PostDatabase.COL_TITLE, PostDatabase.COL_CONTENT },
        null, null, null, null, null);

if (cursor != null) {
    int idIndex = cursor.getColumnIndex(PostDatabase.ID);
    int titleIndex = cursor.getColumnIndex(PostDatabase.COL_TITLE);
    int contentIndex = cursor.getColumnIndex(PostDatabase.COL_CONTENT);

    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        int id = cursor.getInt(idIndex);
        String title = cursor.getString(titleIndex);
        String content = cursor.getString(contentIndex);

        // Dumps (2 rows):
        // ID: 1 Title: Test Title Content: Test Content
        // ID: 2 Title: Second Title Content: Second Content
        Log.d(TAG, "ID: " + id + " Title: " + title + " Content: " + content);

        cursor.moveToNext();
    }
}

So we learnt how to use the rawQuery() and query() method to query our database tables. This is the syntax of the query() method:

Cursor c = db.query(
    PostDatabase.TABLE_POSTS,                 // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // SQL GROUP BY (null = don't group)
    null,                                     // SQL HAVING (null = include all rows)
    sortOrder                                 // The sort order
);

One thing you’ll notice is that, a query operation returns a Cursor that basically points to all the rows returned by the query. Using a Cursor interface we can read from a resultset returned by a database query operation and also navigate through it.

To get a particular row’s data we first move the Cursor there before reading values. You’ll notice moveToFirst() method being used right after querying. That is done so that the cursor’s position is placed on the first entry of the result set. Then you can use various methods like getString() or getInt() to get a column’s value. These methods accept the column index which can be obtained by calling getColumnIndex() or getColumnIndexOrThrow() on the cursor object and passing the column/field string name to it.

You can create a model class for posts or any another table with getters and setters representing each rows if you want. You can then create an ArrayList of such models and show in a ListView for instance. This approach can prove to be really useful while coding applications where you take data from your database and show up in a particular format in the user interface.

Update Records (UPDATE)

Time to see how to update() records based on a WHERE condition.

// New value for one column
ContentValues values = new ContentValues();
values.put(PostDatabase.COL_TITLE, "Second title changed");

// Which row to update, based on the ID
String whereClause = PostDatabase.ID + " LIKE ?";
String[] whereArgs = { "2" };

int affectedRows = sqlDB.update(
                        PostDatabase.TABLE_POSTS,
                        values,
                        whereClause,
                        whereArgs
                    );

// Dumps 1
Log.d(TAG, String.valueOf(affectedRows));

Delete Records (DELETE)

Let’s see how to delete() records from a table based on a condition (WHERE clause).

// Define 'where' part of query.
String whereClause = PostDatabase.ID + " = ?";
// Specify arguments in placeholder order.
String[] whereArgs = { "1" };
// Issue SQL statement.
sqlDB.delete(PostDatabase.TABLE_POSTS, whereClause, whereArgs);

Passing null as the whereClause will delete all the rows in the table.

SQLiteQueryBuilder

SQLiteQueryBuilder is basically a convenience class for building complex queries that contains JOINs, unions, column aliases, multiple tables, subqueries, etc. and send them to SQLiteDatabase objects.

For instance it has a convenient method called appendWhere() that we can call separately to add a chunk to the WHERE clause of the query.

SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(PostDatabase.TABLE_POSTS);
queryBuilder.appendWhere("id = 1");

// Dumps "SELECT * FROM posts WHERE (id = 1)"
Log.d(TAG, queryBuilder.buildQuery(null, null, null, null, null, null));

// queryBuilder.query(DB.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder)

Here’s how you could build a JOIN (between posts and comments table):

queryBuilder.setTables(
                PostDatabase.TABLE_POSTS
                + " INNER JOIN "
                + PostDatabase.TABLE_COMMENTS
                + " ON "
                + PostDatabase.ID
                + " = "
                + (PostDatabase.TABLE_COMMENTS + "." + PostDatabase.COMMENT_POST_ID)
);

// queryBuilder.query(...)

Wrapping Up

So we learnt how to do basic CRUD (Create, Read, Update, Delete) operations in Android with SQLite. Next we should learn about a concept called Content Providers in Android that uses what you learnt in this article as the base.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

Leave a Reply

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