Dagger 2 с ViewModel, хранилищем, комнатой и сопрограммами - PullRequest
1 голос
/ 20 января 2020

Я пытаюсь использовать Dagger 2 в проекте ViewModel + Respository + Room + Retrofit + Coroutines, написанном в Kotlin.

В настоящее время каждый ViewModel инициализирует требуемые репозитории и их зависимости, например, так:

class HomeViewModel(
    application: Application
) : AndroidViewModel(application) {
    private val repository: UserRepository =
        UserRepository(
            Webservice.create(),
            AppDatabase.getDatabase(application, viewModelScope).userDao()
        )

Я бы хотел упростить это до этого

class HomeViewModel @Inject constructor(
    private val repository: UserRepository
): ViewModel()

Чего я достиг к настоящему моменту

Создание компонента и модулей кинжала

@Singleton
@Component(modules = [
    AppModule::class,
    NetworkModule::class,
    DataModule::class,
    RepositoryModule::class
])
interface AppComponent {
    val webservice: Webservice
    val userRepository: UserRepository
}

@Module
class AppModule(private val app: Application) {
    @Provides
    @Singleton
    fun provideApplication(): Application = app
}

@Module
class DataModule {
    @Provides
    @Singleton
    fun provideApplicationDatabase(app: Application, scope: CoroutineScope) =
        AppDatabase.getDatabase(app, scope)

    @Provides
    @Singleton
    fun provideUserDao(db: AppDatabase) = db.userDao()
}

@Module
class NetworkModule {
    @Provides
    @Singleton
    fun provideWebservice() = Webservice.create()
}

@Module
class RepositoryModule {
    @Provides
    @Singleton
    fun provideUserRepository(webservice: Webservice, userDao: UserDao) =
        UserRepository(webservice, userDao)
}

Получил AppComponent, инициализированный в классе приложения

class App : Application() {
    companion object {
        lateinit var appComponent: AppComponent
    }
    override fun onCreate() {
        super.onCreate()
        appComponent = initDagger(this)
    }
    private fun initDagger(app: App): AppComponent =
        DaggerAppComponent.builder()
            .appModule(AppModule(app))
            .build()
}

И теперь я застрял.

Первый вопрос : Как мне сделать конструктор инъекции ViewModel работа?

Первоначально он был инициализирован из HomeFragment примерно так:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)

Как мне теперь вызвать инициализатор?

Второй вопрос немного сложнее.

Конструктор базы данных требует область действия Coroutine, чтобы предварительно заполнить ее в фоновом потоке во время создания. Как мне передать область сейчас?

Вот определение базы данных и обратный вызов

@Database(
    entities = [User::class],
    version = 1, exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context, scope: CoroutineScope): AppDatabase {
            val tempInstance =
                INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "database"
                )
                    .fallbackToDestructiveMigration()
                    .addCallback(AppDatabaseCallback(scope))
                    .build()
                INSTANCE = instance
                return instance
            }
        }
    }

    private class AppDatabaseCallback(
        private val scope: CoroutineScope
    ) : RoomDatabase.Callback() {RoomDatabase.Callback() {
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                scope.launch(Dispatchers.IO) {
                    //inserts
                }
            }
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 20 января 2020

Второй вопрос немного сложнее.

Конструктор базы данных запрашивает область Coroutine, чтобы предварительно заполнить ее в фоновом потоке во время создания. Как мне передать область сейчас?

Это на самом деле проще, не передавайте CoroutineScope, используйте GlobalScope для этой операции.

Первый вопрос: как мне заставить конструктор инъекций ViewModel работать?

Вам нужно получить Provider<HomeViewModel> от Dagger, а затем вызвать его внутри ViewModelProvider.Factory, чтобы создать экземпляр HomeViewModel через провайдера, зарегистрированного в компоненте Dagger.

В качестве альтернативы, если у Activity есть собственный субкомпонент, вы можете использовать @BindsInstance, чтобы получить экземпляр Activity в граф, а затем переместить ViewModelProviders.of(activity).get(HomeViewModel::class.java, object: ViewModelProvider.Factory { ... return homeViewModelProvider.get() as T ... }) в модуль. этого подкомпонента. Затем из этого подкомпонента можно будет получить фактический экземпляр HomeViewModel и по-прежнему получить правильный обратный вызов + onCleared().

0 голосов
/ 30 марта 2020

Вам не нужно передавать область сопрограмм, просто запустите сопрограмму в диспетчере ввода-вывода, например:

@Database(
entities = [
    Login::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun loginDao(): LoginDao

companion object {

    @Volatile private var INSTANCE: AppDatabase? = null

    fun getInstance(app: Application): AppDatabase = INSTANCE ?: synchronized(this) {
            INSTANCE ?: buildDatabase(app).also { INSTANCE = it }
        }

    private fun buildDatabase(app: Application) =
        Room.databaseBuilder(app,
                AppDatabase::class.java,
                "daily_accountant")
            // prepopulate the database after onCreate was called
            .addCallback(object : Callback() {
                override fun onCreate(db: SupportSQLiteDatabase) {
                    super.onCreate(db)
                    // Do database operations through coroutine or any background thread
                    val handler = CoroutineExceptionHandler { _, exception ->
                        println("Caught during database creation --> $exception")
                    }

                    CoroutineScope(Dispatchers.IO).launch(handler) {
                        // Pre-populate database operations
                    }
                }
            })
            .build()
    }
}

И удалите coroutineScope из параметра функции.

...