Попытка предварительно заполнить базу данных комнат из json с помощью Hilt

#android #dependency-injection #android-room #dagger-hilt #android-database

Вопрос:

У меня есть приложение, в котором я предварительно заполняю базу данных своей комнаты файлом json. Вот моя настройка базы данных для этого:

 @Database(version = 2, entities = [(ClimbingRoute::class)], exportSchema = false)
abstract class ClimbingRoutesDatabase : RoomDatabase() {

    abstract fun climbingRouteDao(): ClimbingRouteDao

    private class ClimbingRouteDatabaseCallback(
        private val scope: CoroutineScope,
        private val resources: Resources
    ) : RoomDatabase.Callback() {

        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                //1
                scope.launch{
                    val climbingRouteDao = database.climbingRouteDao() // 2
                    prePopulateDatabase(climbingRouteDao) // 3
                }            }
        }
        private suspend fun prePopulateDatabase(climbingRouteDao: ClimbingRouteDao){
            // 1
            val jsonString = resources.openRawResource(R.raw.climbing_routes).bufferedReader().use {
                it.readText()
            }
            // 2
            val typeToken = object : TypeToken<List<ClimbingRoute>>() {}.type
            val boulderClimbingRoutes = Gson().fromJson<List<ClimbingRoute>>(jsonString, typeToken)
            // 3
            climbingRouteDao.insertAllClimbingRoutes(boulderClimbingRoutes)
        }
    }


    companion object {

        @Volatile
        private var INSTANCE: ClimbingRoutesDatabase? = null

        fun getDatabase(
            context: Context,
            coroutineScope: CoroutineScope, // 1
            resources: Resources // 2
        ): ClimbingRoutesDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }

            synchronized(this) {
                val instance = Room.databaseBuilder(context.applicationContext,
                    ClimbingRoutesDatabase::class.java,
                    "climbing_routes_database")
                    .addCallback(ClimbingRouteDatabaseCallback(coroutineScope, resources))
                    .build()
                INSTANCE = instance
                return instance
            }
        }
    }
}
 

Теперь я следую учебнику из codingwithflow, в котором используется Hilt. Я хочу использовать его шаблон для добавления инъекции зависимостей в мое приложение. Вот база данных задач и модуль приложений codingwithflow из его учебника.

Его база данных задач

 @Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {

    abstract fun taskDao(): TaskDao

    class Callback @Inject constructor(
        private val database: Provider<TaskDatabase>,
        @ApplicationScope private val applicationScope: CoroutineScope,
    ) : RoomDatabase.Callback() {

        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)

            val dao = database.get().taskDao()

            applicationScope.launch {
                dao.insert(Task("Wash the dishes"))
                dao.insert(Task("Do the laundry"))
                dao.insert(Task("Buy groceries", important = true))
                dao.insert(Task("Prepare food", completed = true))
                dao.insert(Task("Call mom"))
                dao.insert(Task("Visit grandma", completed = true))
                dao.insert(Task("Repair my bike"))
                dao.insert(Task("Call Elon Musk"))
            }
        }
    }
}
 

His AppModule:

 @Module
@InstallIn(ApplicationComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideDatabase(
        app: Application,
        callback: TaskDatabase.Callback
    ) = Room.databaseBuilder(app, TaskDatabase::class.java, "task_database")
        .fallbackToDestructiveMigration()
        .addCallback(callback)
        .build()

    @Provides
    fun provideTaskDao(db: TaskDatabase) = db.taskDao()

    @ApplicationScope
    @Provides
    @Singleton
    fun provideApplicationScope() = CoroutineScope(SupervisorJob())
}

@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class ApplicationScope
 

I have attempted to utilize this approach for my app but cannot successfully pre-populate the database using my json with dependency injection. The app is loading up with no crashes, but I keep getting an empty database.

I do not yet understand di enough to modify appmodule to make this work. Here is my database setup, appmodule and dao as it stands right now. (not working):

My ClimbingRouteDatabase:

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

    abstract fun climbingRouteDao(): ClimbingRouteDao

    class Callback @Inject constructor(
        private val database: Provider<ClimbingRouteDatabase>,
        @ApplicationScope private val applicationScope: CoroutineScope,
        private val resources: Resources
    ) : RoomDatabase.Callback() {

        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)

            val climbingRouteDao = database.get().climbingRouteDao()

                applicationScope.launch{
                    prePopulateDatabase(climbingRouteDao)
                }
        }

        private suspend fun prePopulateDatabase(climbingRouteDao: ClimbingRouteDao){
            
            val jsonString = resources.openRawResource(R.raw.climbing_routes).bufferedReader().use {
                it.readText()
            }
            
            val typeToken = object : TypeToken<List<ClimbingRoute>>() {}.type
            val boulderClimbingRoutes = Gson().fromJson<List<ClimbingRoute>>(jsonString, typeToken)
            
            climbingRouteDao.insert(boulderClimbingRoutes)
        }
    }
}
 

My AppModule:

 @Module
@InstallIn(ApplicationComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideDatabase(
        app: Application,
        callback: ClimbingRouteDatabase.Callback
    ) = Room.databaseBuilder(
        app,
        ClimbingRouteDatabase::class.java,
        "climbing_route_database")
        .fallbackToDestructiveMigration()
        .addCallback(callback)
        .build()

    @Provides
    fun provideClimbingRouteDao(db: ClimbingRouteDatabase) = db.climbingRouteDao()

    @Provides
    @Singleton
    fun resourcesProvider(@ApplicationContext context: Context): Resources = context.resources

    @ApplicationScope
    @Provides
    @Singleton
    fun provideApplicationScope() = CoroutineScope(SupervisorJob())
}

@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class ApplicationScope
 

Восхождение на вершину

 @Dao
interface ClimbingRouteDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(climbingRoutes: List<ClimbingRoute>)

    @Query("SELECT * FROM climbing_route_table")
    fun getClimbingRoutes(): Flow<List<ClimbingRoute>>

    @Update
    suspend fun update(climbingRoutes: ClimbingRoute)

    @Delete
    suspend fun delete(climbingRoutes: ClimbingRoute)
}
 

Любая помощь будет очень признательна. Спасибо.

Ответ №1:

Хорошо, поэтому я решил проблему с помощью….

УДАЛИТЕ ПРИЛОЖЕНИЕ С МОЕГО УСТРОЙСТВА, ПРЕЖДЕ ЧЕМ ЗАПУСКАТЬ ЕГО СНОВА ИЗ ANDROID STUDIO!

Боже, я думаю, живи и учись. Этот приведенный выше код, похоже, пока работает нормально.