I'm not claiming that usage of Android Room is or should be complicated but, definitely not as simple as you think it is; looking to your example. Android developers spend a lot of time trying to learn how to use Android Architecture Components and Dagger 2 to make their applications as good as possible, which is, by the way, the recommend APIs by Google to be used with Android Room. Though, I'm still going to answer your question.
1. Entities
Decide what entities you're going to use in your database. Let's say we have only one entity here. Something like:
@Entity
class User constructor(
@ColumnInfo(name = "first_name")
var firstName: String,
@ColumnInfo(name = "last_name")
var lastName: String
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
)
2. DAO class
Create your DAO class like this:
/**
* Provides the abstraction API for accessing data.
*/
@Dao
interface AppDao {
/**
* Inserts a new [User].
* @param user The user to insert.
*/
@Insert
suspend fun insertUser(user: User)
/**
* Updates the specified [User].
* @param user The user to update.
*/
@Update
suspend fun updateUser(user: User)
/**
* Deletes the specified [User] from the database.
* @param user The user to delete.
*/
@Delete
suspend fun deleteUser(user: User)
}
3. Database class
Create your database in a separate class. (eg. AppDatabase.java)
Here is how a good database class looks:
@Database(entities = [User::class], version = AppDatabase.VERSION)
abstract class AppDatabase : RoomDatabase() {
/**
* Returns the DAO for this application.
* @return The DAO for this application.
*/
abstract fun getAppDao(): AppDao
companion object {
private const val TAG = "AppDatabase"
const val VERSION = 1
private const val DATABASE_NAME = "inventory_database.db"
@Volatile
private var instance: AppDatabase? = null
/**
* Gets and returns the database instance if exists; otherwise, builds a new database.
* @param context The context to access the application context.
* @return The database instance.
*/
fun getInstance(context: Context): AppDatabase =
instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
/**
* Creates and returns the callback object to execute when the database is first created.
* @return The callback object to execute when the database is first created.
*/
private fun appDatabaseCallback(): Callback = object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d(TAG, "Database has been created.")
// Throws exception
CoroutineScope(Dispatchers.IO).launch {
instance?.getAppDao()?.let { populateDbAsync(it) }
}
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d(TAG, "Database has been opened.")
}
}
/**
* Builds and returns the database.
* @param appContext The application context to reference.
* @return The built database.
*/
private fun buildDatabase(appContext: Context): AppDatabase {
val filesDir = appContext.getExternalFilesDir(null)
val dataDir = File(filesDir, "data")
if (!dataDir.exists())
dataDir.mkdir()
val builder =
Room.databaseBuilder(
appContext,
AppDatabase::class.java,
File(dataDir, DATABASE_NAME).toString()
).fallbackToDestructiveMigration()
// Execute the callback only in DEBUG mode.
if (BuildConfig.DEBUG) {
builder.addCallback(appDatabaseCallback())
}
return builder.build()
}
/**
* Populates the database when it is first created, as a suspended operation.
* @param appDao The application DAO to execute queries.
*/
private suspend fun populateDbAsync(appDao: AppDao) {
withContext(Dispatchers.IO) {
// Populate your database here...
}
}
}
}
4. Activities & Fragments
class MainActivity : AppCompatActivity() {
private lateinit var dao: AppDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the DAO..
dao = AppDatabase.getInstance(requireContext()).getAppDao()
}
// An example of how to execute a dao method in an activity (Again, not recommended)
private fun insertUser(firstName: String, lastName: String) {
val user = User(firstName, lastName)
lifecycleScope.launch {
dao.insertUser(user)
}
}
// The rest of the code here...
}
You can do the same for all your activities.
class ExampleFragment : Fragment(R.layout.fragment_example) {
private lateinit var dao: AppDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize the DAO..
dao = AppDatabase.getInstance(requireContext()).getAppDao()
}
// An example of how to execute a dao method in a fragment (Again, not recommended)
private fun insertUser(firstName: String, lastName: String) {
val user = User(firstName, lastName)
lifecycleScope.launch {
dao.insertUser(user)
}
}
// The rest of the code here...
}
You can do the same for all your fragments.