A Kotlin Android SQLite Database Tutorial

From Techotopia
Revision as of 14:50, 29 November 2017 by Neil (Talk | contribs) (Created page with "The chapter entitled “An Overview of Android SQLite Databases” covered the basic principles of integrating relational database storage into Android applications using the...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The chapter entitled “An Overview of Android SQLite Databases” covered the basic principles of integrating relational database storage into Android applications using the SQLite database management system. The previous chapter took a minor detour into the territory of designing TableLayouts within the Android Studio Layout Editor tool, in the course of which, the user interface for an example database application was created. In this chapter, work on the Database application project will be continued with the ultimate objective of completing the database example.


Contents


About the Database Example

As is probably evident from the user interface layout designed in the preceding chapter, the example project is a simple data entry and retrieval application designed to allow the user to add, query and delete database entries. The idea behind this application is to allow the tracking of product inventory.

The name of the database will be productID.db which, in turn, will contain a single table named products. Each record in the database table will contain a unique product ID, a product description and the quantity of that product item currently in stock, corresponding to column names of “productid”, “productname” and “productquantity”, respectively. The productid column will act as the primary key and will be automatically assigned and incremented by the database management system.

The database schema for the products table is outlined in Table 64-5:

�
ColumnData Type
productidInteger / Primary Key/ Auto Increment
productnameText
productquantityInteger

Creating the Data Model

Once completed, the application will consist of an activity and a database handler class. The database handler will be a subclass of SQLiteOpenHelper and will provide an abstract layer between the underlying SQLite database and the activity class, with the activity calling on the database handler to interact with the database (adding, removing and querying database entries). In order to implement this interaction in a structured way, a third class will need to be implemented to hold the database entry data as it is passed between the activity and the handler. This is actually a very simple class capable of holding product ID, product name and product quantity values, together with getter and setter methods for accessing these values. Instances of this class can then be created within the activity and database handler and passed back and forth as needed. Essentially, this class can be thought of as representing the database model.

Within Android Studio, navigate within the Project tool window to app -> java and right-click on the package name. From the popup menu, choose the New -> Kotlin File/Class option and, in the Create New Class dialog, name the class Product and change the Kind menu to Class before clicking on the OK button.

Once created the Product.kt source file will automatically load into the Android Studio editor. Once loaded, modify the code to add the appropriate constructors:

package com.ebookfrenzy.database
 
class Product {
 
    var id: Int = 0
    var productName: String? = null
    var quantity: Int = 0
 
    constructor(id: Int, productname: String, quantity: Int) {
        this.id = id
        this.productName = productname
        this.quantity = quantity
    }
 
    constructor(productname: String, quantity: Int) {
        this.productName = productname
        this.quantity = quantity
    }
}

The completed class contains private data members for the internal storage of data columns from database entries .


Implementing the Data Handler

The data handler will be implemented by subclassing from the Android SQLiteOpenHelper class and, as outlined in “An Overview of Android SQLite Databases”, adding the constructor, onCreate() and onUpgrade() methods. Since the handler will be required to add, query and delete data on behalf of the activity component, corresponding methods will also need to be added to the class.

Begin by adding a second new class to the project to act as the handler, this time named MyDBHandler. Once the new class has been created, modify it so that it reads as follows:

package com.ebookfrenzy.database
 
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.content.Context
import android.content.ContentValues
 
class MyDBHandler(context: Context, name: String?,
           factory: SQLiteDatabase.CursorFactory?, version: Int) : 
                  SQLiteOpenHelper(context, DATABASE_NAME, 
                  factory, DATABASE_VERSION) {
 
    override fun onCreate(db: SQLiteDatabase) {
 
    }
 
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int,
                           newVersion: Int) {
 
    }
}

Having now pre-populated the source file with template onCreate() and onUpgrade() methods, the next task is to modify the code to declare constants for the database name, table name, table columns and database version as follows:

package com.ebookfrenzy.database
 
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.content.Context
import android.content.ContentValues
 
class MyDBHandler(context: Context, name: String?,
                  factory: SQLiteDatabase.CursorFactory?, version: Int) : SQLiteOpenHelper(context, DATABASE_NAME, factory, DATABASE_VERSION) {
 
    override fun onCreate(db: SQLiteDatabase) {
 
    }
 
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int,
                           newVersion: Int) {
        
    }
 
    companion object {
 
        private val DATABASE_VERSION = 1
        private val DATABASE_NAME = "productDB.db"
        val TABLE_PRODUCTS = "products"
 
        val COLUMN_ID = "_id"
        val COLUMN_PRODUCTNAME = "productname"
        val COLUMN_QUANTITY = "quantity"
    }
} 

Next, the onCreate() method needs to be implemented so that the products table is created when the database is first initialized. This involves constructing a SQL CREATE statement containing instructions to create a new table with the appropriate columns and then passing that through to the execSQL() method of the SQLiteDatabase object passed as an argument to onCreate():

override fun onCreate(db: SQLiteDatabase) {
    val CREATE_PRODUCTS_TABLE = ("CREATE TABLE " +
            TABLE_PRODUCTS + "("
            + COLUMN_ID + " INTEGER PRIMARY KEY," +
            COLUMN_PRODUCTNAME
            + " TEXT," + COLUMN_QUANTITY + " INTEGER" + ")")
    db.execSQL(CREATE_PRODUCTS_TABLE)
}

The onUpgrade() method is called when the handler is invoked with a greater database version number from the one previously used. The exact steps to be performed in this instance will be application specific, so for the purposes of this example, we will simply remove the old database and create a new one:

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int,
                       newVersion: Int) {
    db.execSQL("DROP TABLE IF EXISTS " + TABLE_PRODUCTS)
    onCreate(db)
}

All that now remains to be implemented in the MyDBHandler.kt handler class are the methods to add, query and remove database table entries.

The Add Handler Method

The method to insert database records will be named addProduct() and will take as an argument an instance of our Product data model class. A ContentValues object will be created in the body of the method and primed with key-value pairs for the data columns extracted from the Product object. Next, a reference to the database will be obtained via a call to getWritableDatabase() followed by a call to the insert() method of the returned database object. Finally, once the insertion has been performed, the database needs to be closed:

fun addProduct(product: Product) {
 
    val values = ContentValues()
    values.put(COLUMN_PRODUCTNAME, product.productName)
    values.put(COLUMN_QUANTITY, product.quantity)
 
    val db = this.writableDatabase
 
    db.insert(TABLE_PRODUCTS, null, values)
    db.close()
}

The Query Handler Method

The method to query the database will be named findProduct() and will take as an argument a String object containing the name of the product to be located. Using this string, a SQL SELECT statement will be constructed to find all matching records in the table. For the purposes of this example, only the first match will then be returned, contained within a new instance of our Product data model class:

fun findProduct(productname: String): Product? {
    val query = 
"SELECT * FROM $TABLE_PRODUCTS WHERE $COLUMN_PRODUCTNAME =  \"$productname\""
 
    val db = this.writableDatabase
 
    val cursor = db.rawQuery(query, null)
 
    var product: Product? = null
 
    if (cursor.moveToFirst()) {
        cursor.moveToFirst()
 
        val id = Integer.parseInt(cursor.getString(0))
        val name = cursor.getString(1)
        val quantity = Integer.parseInt(cursor.getString(2))
        product = Product(id, name, quantity)
        cursor.close()
    }
 
    db.close()
    return product
}

The Delete Handler Method

The deletion method will be named deleteProduct() and will accept as an argument the entry to be deleted in the form of a Product object. The method will use a SQL SELECT statement to search for the entry based on the product name and, if located, delete it from the table. The success or otherwise of the deletion will be reflected in a Boolean return value:

fun deleteProduct(productname: String): Boolean {
 
    var result = false
 
    val query = 
  "SELECT * FROM $TABLE_PRODUCTS WHERE $COLUMN_PRODUCTNAME = \"$productname\""
 
    val db = this.writableDatabase
 
    val cursor = db.rawQuery(query, null)
 
    if (cursor.moveToFirst()) {
        val id = Integer.parseInt(cursor.getString(0))
        db.delete(TABLE_PRODUCTS, COLUMN_ID + " = ?",
                    arrayOf(id.toString()))
        cursor.close()
        result = true
    }
    db.close()
    return result
}

Implementing the Activity Event Methods

The final task prior to testing the application is to wire up onClick event handlers on the three buttons in the user interface and to implement corresponding methods for those events. Locate and load the activity_database.xml file into the Layout Editor tool, switch to Text mode and locate and modify the three button elements to add onClick properties:

<Button
    android:text="@string/add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button3"
    android:layout_weight="1"
    style="@style/Widget.AppCompat.Button.Borderless"
    android:onClick="newProduct" />
 
<Button
    android:text="@string/find"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button2"
    android:layout_weight="1"
    style="@style/Widget.AppCompat.Button.Borderless"
    android:onClick="lookupProduct" />
 
<Button
    android:text="@string/delete"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button"
    android:layout_weight="1"
    style="@style/Widget.AppCompat.Button.Borderless"
    android:onClick="removeProduct" />

Load the DatabaseActivity.kt source file into the editor and implement the code to implement the three “onClick” target methods:

package com.ebookfrenzy.database
 
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
 
import kotlinx.android.synthetic.main.activity_database.*
 
class DatabaseActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_database)
    }
 
    fun newProduct(view: View) {
        val dbHandler = MyDBHandler(this, null, null, 1)
 
        val quantity = Integer.parseInt(productQuantity.text.toString())
 
        val product = Product(productName.text.toString(), quantity)
 
        dbHandler.addProduct(product)
        productName.setText("")
        productQuantity.setText("")
    }
 
    fun lookupProduct(view: View) {
        val dbHandler = MyDBHandler(this, null, null, 1)
 
        val product = dbHandler.findProduct(
                productName.text.toString())
 
        if (product != null) {
            productID.text = product.id.toString()
 
            productQuantity.setText(
                    product.quantity.toString())
        } else {
            productID.text = "No Match Found"
        }
    }
 
    fun removeProduct(view: View) {
        val dbHandler = MyDBHandler(this, null, null, 1)
 
        val result = dbHandler.deleteProduct(
                productName.text.toString())
 
        if (result) {
            productID.text = "Record Deleted"
            productName.setText("")
            productQuantity.setText("")
        } else
            productID.text = "No Match Found"
    }
}

Testing the Application

With the coding changes completed, compile and run the application either in an AVD session or on a physical Android device. Once the application is running, enter a product name and quantity value into the user interface form and touch the Add button. Once the record has been added the text boxes will clear. Repeat these steps to add a second product to the database. Next, enter the name of one of the newly added products into the product name field and touch the Find button. The form should update with the product ID and quantity for the selected product. Touch the Delete button to delete the selected record. A subsequent search by product name should indicate that the record no longer exists.

Summary

The purpose of this chapter has been to work step by step through a practical application of SQLite based database storage in Android applications. As an exercise to develop your new database skill set further, consider extending the example to include the ability to update existing records in the database table.