Introduction
In Android development, managing a local SQLite database can be challenging without a proper framework. Fortunately, the Room persistence library simplifies this process by providing an abstraction layer over SQLite, helping developers create robust and maintainable database systems. In Kotlin, combining Room with the Singleton design pattern offers a clean and efficient way to manage a single instance of the database throughout the app's lifecycle. This example demonstrates how to use a Singleton with Room to perform basic database operations, such as inserting and fetching data from a database table.
In this tutorial, we walk through an Android Kotlin example that sets up a Room database using the Singleton pattern. The goal is to ensure only one instance of the database is used, which is crucial for avoiding resource leakage and ensuring thread safety. We'll also explain how the app interacts with this database using Data Access Objects (DAOs), Entities, and asynchronous threading with Kotlin coroutines or libraries like Anko.
MainActivity.kt
The MainActivity.kt
file serves as the entry point for the application. It begins by setting up the user interface in the onCreate
method, where the layout defined in activity_main.xml
is loaded. The layout consists of a button (btn
) for inserting data and a scrollable TextView
(textView
) for displaying the inserted records. One of the key components of this file is initializing the Room database.
The Room database instance is created via the RoomSingleton.getInstance(applicationContext)
call, ensuring that only one instance of the database is used throughout the application's lifecycle. A button click listener is set up to trigger the database operation. Upon clicking the button, data insertion and retrieval occur asynchronously using Anko's doAsync
function, which handles background tasks. The inserted data is retrieved and displayed in the TextView
after the operation completes on the UI thread using uiThread
.
RoomSingleton.kt
The RoomSingleton.kt
file implements the Singleton pattern for the Room database. This design pattern is essential in scenarios where multiple parts of an application require access to the database, but only one instance should be created to prevent resource overuse and conflicts. The RoomSingleton
class is annotated with @Database
, which defines the entities and the version of the database.
The Singleton is implemented using the getInstance
method, which checks if an instance of the database already exists. If not, it initializes a new instance using Room.databaseBuilder
and assigns it to the INSTANCE
variable. This ensures that all calls to the database use the same instance, thus providing consistency and efficient resource management across the application.
RoomEntity.kt
The RoomEntity.kt
file defines the Student
data class, which is the entity that maps to the database table studentTbl
. Each entity in Room represents a table within the database. In this case, the Student
entity has two fields: id
, which serves as the primary key, and name
, which holds a unique identifier (UUID
) generated when inserting a new student.
Annotations such as @Entity
and @PrimaryKey
specify how this class corresponds to the table schema in the SQLite database. Room automatically maps this class to the underlying database table and handles the creation of SQL statements for CRUD operations.
RoomDao.kt
The RoomDao.kt
file defines the DAO (Data Access Object) for interacting with the studentTbl
. The StudentDao
interface contains methods for querying and inserting data into the database. The @Dao
annotation marks this interface as a DAO component for Room.
The allStudents()
method uses an SQL query to select all records from the studentTbl
, returning them as a list of Student
objects. The insert()
method inserts a new Student
object into the database, replacing any conflicting entries with the same primary key (in this case, the id
field). Room generates the required SQL code to perform these operations behind the scenes, allowing the developer to interact with the database in a more abstracted and Kotlin-friendly way.
activity_main.xml
The activity_main.xml
layout file defines the user interface for the main activity. It contains a button labeled "Insert Data" and a TextView
to display the inserted data. The TextView
is configured to be scrollable, allowing it to display multiple lines of text without overflowing off the screen.
ConstraintLayout is used to arrange the Button
and TextView
within the screen. The button is positioned at the top of the layout, while the TextView
is placed below it to display the records after the button is clicked.
Gradle Settings
The build.gradle
file contains essential dependencies required for this project. It includes the Room library for database management and Anko for simplified asynchronous threading. The Anko library, though deprecated in later versions, was a popular choice for Kotlin developers due to its concise syntax for handling background tasks and UI updates.
In this example, the Room version used is 2.1.0-alpha04
, and the kapt
plugin is applied for Room annotation processing. The kapt
tool generates the necessary code for Room during compilation.
Summary
In summary, this Android Kotlin example demonstrates how to use the Room persistence library with the Singleton pattern to efficiently manage a local SQLite database. The application allows the user to insert random student data into a database and display it in a scrollable TextView
. By using Room, developers can work with databases in a more structured and maintainable way, and the Singleton pattern ensures that only one instance of the database is created.
This approach not only improves application performance and resource management but also promotes a clean architecture. With tools like Anko or Kotlin coroutines, developers can manage asynchronous tasks with ease, ensuring the user interface remains responsive while database operations are performed in the background.
package com.cfsuman.jetpack
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var mDb: RoomSingleton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the room database
mDb = RoomSingleton.getInstance(applicationContext)
// Make text view content scrollable
textView.movementMethod = ScrollingMovementMethod()
// Insert data into table
btn.setOnClickListener {
textView.text = ""
doAsync {
mDb.studentDao().insert(Student(null,UUID.randomUUID().toString()))
val list = mDb.studentDao().allStudents()
uiThread {
// Show the records in text view
for (Student in list){
textView.append("${Student.id} : ${Student.name} \n")
}
}
}
}
}
}
package com.cfsuman.jetpack
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
@Database(entities = arrayOf(Student::class), version = 1, exportSchema = false)
abstract class RoomSingleton : RoomDatabase(){
abstract fun studentDao():StudentDao
companion object{
private var INSTANCE: RoomSingleton? = null
fun getInstance(context:Context): RoomSingleton{
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(
context,
RoomSingleton::class.java,
"roomdb")
.build()
}
return INSTANCE as RoomSingleton
}
}
}
package com.cfsuman.jetpack
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "studentTbl")
data class Student(
@PrimaryKey
var id:Long?,
@ColumnInfo(name = "uuid")
var name: String
)
package com.cfsuman.jetpack
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface StudentDao{
@Query("SELECT * FROM studentTbl")
fun allStudents():List<Student>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(student: Student)
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rootLayout"
tools:context=".MainActivity"
android:background="#fdfdfc">
<Button
android:text="Insert Data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"/>
<TextView
android:text=""
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/btn"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
android:textColor="#3F51B5"
tools:text="TextView"
android:padding="16dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
apply plugin: 'kotlin-kapt'
dependencies {
// Room support
def room_version = "2.1.0-alpha04"
implementation "androidx.room:room-runtime:$room_version"
kapt 'androidx.room:room-compiler:2.1.0-alpha04'
// Anko Commons
implementation "org.jetbrains.anko:anko-commons:0.10.5"
}