Building the Database
The Database Class: The Hub of Your Data
Now that we have our Entity and DAO, we can build the Database class. The Database class is the hub of your data. It ties all your Entities and DAOs together. It tells Room what tables exist and how to access them. You only need one Database class for your app.
The Database class ties all your Entities and DAOs together. It tells Room what tables exist and how to access them. You only need one Database class for your app.
- Create a Database class when you have at least one Entity and one DAO.
- Use it to manage the database version and provide access to your DAOs.
Example: NoteDatabase (from RoomDatabaseDemo)
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
// Abstract method to provide the Data Access Object (DAO) for notes.
// Room will generate the implementation for this method.
abstract fun noteDao(): NoteDao
// Companion object allows us to define static-like members and methods for the database.
companion object {
// The @Volatile annotation ensures that changes to the INSTANCE variable are immediately
// visible to all threads. This is crucial for thread-safe singleton implementation.
@Volatile
private var INSTANCE: NoteDatabase? = null
// Provides a singleton instance of the NoteDatabase.
// If INSTANCE is null, it creates the database in a synchronized block to prevent multiple instances.
fun getDatabase(context: Context): NoteDatabase {
return INSTANCE ?: synchronized(this) {
// Create database here if INSTANCE is null.
val instance = Room.databaseBuilder(
context.applicationContext, // Application context to prevent memory leaks.
NoteDatabase::class.java,
"notes_db" // The name of the database file.
).build()
INSTANCE = instance
instance
}
}
}
}
Understanding the `NoteDatabase` Class
This section explains the core components of your Room Database implementation. The `NoteDatabase` class serves as the central hub for your database operations, bringing together your data entities and data access objects (DAOs).
1. Database Annotation and Class Definition
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
// ...
}
Here's what each part means:
@Database(...): This annotation tells Room that this abstract class is your database. It requires a few parameters:entities = [Note::class]: This is a list of all the data classes (Entities) that belong to this database. In our case, we only have one:Note. If you had more tables, you'd list them here.version = 1: This is the version number of your database. Every time you make a structural change to your database (like adding a new table, adding a column, or changing a column type), you must increment this version number. Room uses this to manage database migrations.exportSchema = false: This setting prevents Room from exporting the database schema into a JSON file. While exporting the schema can be useful for version control and reviewing database history, for simpler projects or initial development, setting it tofalseis common.
abstract class NoteDatabase : RoomDatabase(): Your database class must be an `abstract class` and it must `extend RoomDatabase`. Room will automatically generate the necessary code for this abstract class during compilation.
2. Data Access Object (DAO) Declaration
abstract fun noteDao(): NoteDao
abstract fun noteDao(): NoteDao: Inside your `NoteDatabase` class, you must declare an abstract function for each DAO you have. This function simply returns an instance of your DAO interface (NoteDaoin this example). Room will generate the implementation for this function, allowing you to access your DAO methods to perform database operations.
3. Singleton Pattern for Database Instance
companion object {
@Volatile
private var INSTANCE: NoteDatabase? = null
fun getDatabase(context: Context): NoteDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
"notes_db"
).build()
INSTANCE = instance
instance
}
}
}
This `companion object` block implements the Singleton pattern, which is a design pattern that ensures only one instance of a class exists throughout your application. For a database, this is crucial to prevent multiple connections and potential data inconsistencies.
companion object { ... }: In Kotlin, a companion object is used to define static-like members and methods for a class.@Volatile private var INSTANCE: NoteDatabase? = null:@Volatile: This annotation is vital for thread safety. It ensures that any changes made to the `INSTANCE` variable are immediately visible to all threads. Without `volatile`, one thread might see an outdated value of `INSTANCE`, leading to multiple database instances being created.private var INSTANCE: NoteDatabase? = null: This declares a private, nullable variable `INSTANCE` to hold our single database instance. It's initialized to `null`.
fun getDatabase(context: Context): NoteDatabase { ... }: This is the public method that your application will call to get the database instance.return INSTANCE ?: synchronized(this) { ... }: This is a "double-checked locking" mechanism for thread-safe singleton creation:INSTANCE ?:: It first checks if `INSTANCE` is already initialized. If it is, that existing instance is returned directly.synchronized(this) { ... }: If `INSTANCE` is `null`, it enters a synchronized block. This ensures that only one thread can execute the code inside this block at a time, preventing race conditions where multiple threads might try to create the database simultaneously.val instance = Room.databaseBuilder(...): Inside the synchronized block, the database is actually built:context.applicationContext: It uses the application context to create the database. This is important because using an Activity context could lead to memory leaks if the Activity is destroyed while the database connection is still active.NoteDatabase::class.java: Specifies the database class (our `NoteDatabase`)."notes_db": This is the name of your database file that will be created on the device's storage.
.build(): Finalizes the database creation.INSTANCE = instance: The newly created database instance is assigned to `INSTANCE`.instance: The created instance is returned.
Tips for Success
- Use the Database class to keep your data organized and easy to access.
- Only one Database class is needed for your app.
- Keep your Database class simple—just list your Entities and DAOs.
Common Mistakes to Avoid
- Forgetting to include all your Entities in the
@Databaseannotation. - Creating multiple Database classes for the same app.
- Not updating the version number when you change your database structure.
Best Practices
- Use a Singleton pattern to make sure there's only one database instance.
- Keep your Database class focused on connecting Entities and DAOs.
- Plan your database structure before you start coding.