Search code examples
androidandroid-roomdaggerandroid-workmanager

Pre-Populate Room Database using WorkManager and Dagger


I am trying to use WorkManager to populate the Room Database while creating the database. I am using dagger to initialize the database and its Dao's. While seeding the database, it gives the following error.

Could not instantiate *.*.*.SeedDatabaseWorker
    java.lang.NoSuchMethodException: <init> [class android.content.Context, class androidx.work.WorkerParameters]
        at java.lang.Class.getConstructor0(Class.java:2204)
        at java.lang.Class.getDeclaredConstructor(Class.java:2050)
        at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:91)
        at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:233)
        at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:127)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:761)
2019-03-16 16:22:29.135 18035-18051/*.* E/WM-WorkerWrapper: Could not create Worker *.*.*.SeedDatabaseWorker

Here's how i have setup the application.

AppModule.Kt

@Provides
@Singleton
fun provideAppDatabase(context: Context): AppDatabase {
    return Room.databaseBuilder(
        context,
        AppDatabase::class.java,
        "garden.db"
    )
        .addCallback(object : RoomDatabase.Callback() {
            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
                WorkManager.getInstance().enqueue(request)
            }
        })
        .build()
}

@Provides
@Singleton
fun providePlantDao(db: AppDatabase): PlantDao =
    db.plantDao()

PlantDao

@Dao
interface PlantDao {
    @Query("SELECT * FROM plants ORDER BY name")
    fun getPlants(): LiveData<List<Plant>>
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(plants: List<Plant>)
}

AppDatabase.kt

@Database(
        entities = [
            Plant::class
        ],
        version = 1,
        exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {

    abstract fun plantDao(): PlantDao
}

SeedDatabaseWorker.kt

class SeedDatabaseWorker @Inject constructor(
        context: Context,
        workerParameters: WorkerParameters
): CoroutineWorker(context, workerParameters) {


    private val TAG by lazy { SeedDatabaseWorker::class.java.simpleName }
    @Inject
    lateinit var database: AppDatabase
    override val coroutineContext = Dispatchers.IO

    override suspend fun doWork(): Result = coroutineScope {

        try {
            applicationContext.assets.open("plants.json").use { inputStream ->
                JsonReader(inputStream.reader()).use { jsonReader ->
                    val plantType = object : TypeToken<List<Plant>>() {}.type
                    val plantList: List<Plant> = Gson().fromJson(jsonReader, plantType)

                    database.plantDao().insertAll(plantList)

                    Result.success()
                }
            }
        } catch (ex: Exception) {
            Log.e(TAG, "Error seeding database", ex)
            Result.failure()
        }
    }
}

Can anyone help me to populate database using WorkManager? Note: I am trying this google sample


Solution

  • For now, Dagger 2 does not officially support injection with androidx.worker.Worker, this issue is still open, check here. One solution is proposed by this blog. Basically you could inject any object in the constructor like:

    class HelloWorldWorker @AssistedInject constructor(
        @Assisted private val appContext: Context,
        @Assisted private val params: WorkerParameters,
        private val foo: Foo
    ) : Worker(appContext, params) {
        private val TAG = "HelloWorldWorker"
        override fun doWork(): Result {
            Log.d(TAG, "Hello world!")
            Log.d(TAG, "Injected foo: $foo")
            return Result.success()
        }
    
        @AssistedInject.Factory
        interface Factory : ChildWorkerFactory
    }
    

    Here the object Foo is injected.

    I have same problem in my project, inspired by the blog, I solve the problem. You could check the my solution to see how to pre-populate data with WorkManager.