Populating Room Database with Initial Data: Callback vs. WorkManager

When building Android apps with Room, you often need to initialize the database with default or seed data, such as pre-defined categories, sample users, or system settings. Two common methods for achieving this are:

  1. Using a RoomDatabase.Callback.
  2. Using WorkManager for background tasks.

This post explores the pros and cons of each method and provides generalized code examples to help you choose the best approach.

1. Using RoomDatabase.Callback

Room provides a RoomDatabase.Callback that can be used to perform actions when the database is first created or opened. This makes it an intuitive choice for initializing data during the database setup.

Implementation

Add a RoomDatabase.Callback to your database builder:

val database = Room.databaseBuilder(
    context,
    AppDatabase::class.java,
    "app_database"
)
    .addCallback(object : RoomDatabase.Callback() {
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            CoroutineScope(Dispatchers.IO).launch {
                // Example of inserting initial data
                val dao = AppDatabase.getInstance(context).exampleDao()
                if (dao.getAll().isEmpty()) {
                    val initialData = generateInitialData()
                    dao.insertAll(initialData)
                }
            }
        }
    })
    .build() 

Pros

  • Easy to implement for one-time data initialization.
  • Runs automatically during database creation.

Cons

  • No retry mechanism if the app is closed during data insertion.
  • Tight coupling between the database and initialization logic.
  • Won’t run again unless the database is recreated.

2. Using WorkManager

WorkManager is an Android library designed for deferrable and guaranteed background tasks. It is well-suited for initializing Room databases as it persists tasks and retries on failures, even if the app is closed.

Implementation

Create a Worker for data initialization:

class DataInitializationWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            // Access the database and insert initial data
            val dao = AppDatabase.getInstance(applicationContext).exampleDao()
            val initialData = generateInitialData()
            dao.insertAll(initialData)
            Result.success()
        } catch (e: Exception) {
            Result.retry() // Retry in case of failure
        }
    }
} 

Schedule the task with WorkManager inside you Application Class:

val workRequest = OneTimeWorkRequestBuilder<DataInitializationWorker>().build()
WorkManager.getInstance(context).enqueueUniqueWork(
    "initialize_database",
    ExistingWorkPolicy.KEEP, // Avoid duplicate tasks
    workRequest
) 

Pros

  • Tasks persist across app restarts or closures.
  • Built-in retry mechanism ensures task completion.
  • Decouples initialization logic from the database setup.

Cons

  • Slightly more complex to set up than a callback.
  • May have a delay due to WorkManager scheduling policies.

When the App is Closed

  • Callback: The operation halts, and the task won’t resume. If partially inserted data exists, you’ll need to handle cleanup manually.
  • WorkManager: Tasks persist and will resume when conditions are met, ensuring a reliable initialization process.

Conclusion

For lightweight, one-time data initialization tied to database creation, RoomDatabase.Callback is a simple and effective option. However, if your app needs robustness, persistence, and retries (e.g., for large datasets or complex initializations), WorkManager is the better choice.

By choosing the right method based on your app’s needs, you can ensure a smooth user experience and reliable database setup.

Scroll to Top