What You'll Learn

Here are the objectives of this lab:

So far, we've written applications that store ephemeral data. (The state of the app is lost on an app restart). Most apps persist data for a number of reasons.

State Preservation

When an app restarts, users expect it to continue from where they left off. Imagine if everytime you open whatsapp, you have a blank application with no chat history. Or imagine loading TikTok but you're given content at random in a language you don't understand. Or, imagine shopping online but when you close the app, your card information and cart is cleared.

Persistence is important for storing state and user preferences. While some of the examples above persist data on a server, it's important to know how to persist data offline. Imagine only being able to use whatsapp on proper WiFI when you want to send messages and read old messages.

Offline Access

Persisting data locally enables apps to function partially or fully offline. Data such as cached content, user-generated content, or downloaded files can be accessed even when there's no network connection. This is critical for apps like social media, news, or note-taking apps that allow users to view or create content offline.

Authentication Persistence

Maintaining authentication tokens between app restarts is vital for a smooth user experience. Users don't want to log in every time they open the app.

Performance Optimization

By persisting data locally, you can reduce the need to fetch data from remote sources on every app restart, improving the app's performance and reducing data usage. This is particularly important for apps that frequently fetch data from APIs.

Android has a built in SQL engine called SQLITE. It's a lightweight SQL library that can be used to create databases on the device. For today's lab we're going to create a new app called WhatsUpp

Create an application called WhatsUpp that gives the user the ability to send chat messages.

  1. Create a new project named WhatsUpp.

We would need a few data models in this app.

ChatMessage

The ChatMessage model should store the following information

// com/example/whatsupp/models/ChatMessage.kt
data class ChatMessage(val messageId:Int, val contactId:Int, val text:String, val timestamp:Int)

ChatContact

The ChatContact model should store the following information.

// com/example/whatsupp/models/ChatMessage.kt
data class ChatContact(val contactId:Int, val name:String, val image:String, val lastMessageId: Int?)

This class is responsible for performing all operations on the database such as

class DatabaseHelper(context: Context, factory: SQLiteDatabase.CursorFactory?):SQLiteOpenHelper(context, DB_NAME,factory, DB_VERSION) {
    override fun onCreate(db: SQLiteDatabase) {
        val createChatMessageTableQuery = ("CREATE TABLE ChatMessage (" +
                            "messageID INTEGER PRIMARY KEY AUTOINCREMENT," +
                            "contactID INTEGER," +
                            "text TEXT," +
                            "timestamp INTEGER)")

        val createChatContactTableQuery = ("CREATE TABLE ChatContact (" +
                            "contactID INTEGER PRIMARY KEY AUTOINCREMENT," +
                            "name TEXT," +
                            "image TEXT," +
                            "lastMessageId INTEGER NULL)")

        db.execSQL(createChatMessageTableQuery)
        db.execSQL(createChatContactTableQuery)
    }

    // WHen upgrading DB versions, we have to carefully add columns in a non destructive way
    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
        // Content similar to enterprise DB
    }

    fun createChatContact(name: String, image: String){
        val values = ContentValues()

        values.put("name", name)
        values.put("image", image)

        val db = this.writableDatabase

        db.insert("ChatContact",null, values)

        db.close()
    }


    fun createChatMessage(contactId: Int, text: String){
        val now = System.currentTimeMillis()

        val values = ContentValues()

        values.put("contactID", contactId)
        values.put("text",text)
        values.put("timestamp",now)


        val db = this.writableDatabase

        db.insert("ChatMessage",null, values)

        db.close()
    }

    fun getAllChatContacts():List<ChatContact>{
        val result:MutableList<ChatContact> = mutableListOf()

        val db = this.readableDatabase
        val cursor = db.rawQuery("SELECT * FROM ChatContact", null)

        if(!(!cursor.moveToFirst() || cursor.count == 0)){
            do {
                val contactIdIdx = cursor.getColumnIndex("contactID")
                val contactNameIdx = cursor.getColumnIndex("name")
                val contactImageIdx = cursor.getColumnIndex("image")
                val contactLastMessageIdIdx = cursor.getColumnIndex("lastMessageId")

                if (contactIdIdx >0 && contactNameIdx >0 && contactImageIdx > 0 && contactLastMessageIdIdx>0){
                    val contactId = cursor.getInt(contactIdIdx)
                    val contactName = cursor.getString(contactNameIdx)
                    val contactImage = cursor.getString(contactImageIdx)
                    val contactLastMessageId = cursor.getIntOrNull(contactLastMessageIdIdx)
                    result.add(ChatContact(contactId, contactName, contactImage, contactLastMessageId))
                }
            } while (cursor.moveToNext())
        }
        cursor.close()
        db.close()
        return result
    }
}