I'm working on a multi db app which can be instantiated at any time not only when the app starts.
I created a subComponet "LoggedInComponent" which need to be created whenever the app need the to use database. The database is injected in a datasource, the repository is injected in the use case and the use case is injected in the viewModel, when the database is injected directly in viewModel the the build succeed, but if try to inject it to the datasource Gradle fails to build with this error:
java.lang.IllegalArgumentException: no expression found for BindingRequest{key=com.xxxxx.app.framework.database.Database, requestKind=Optional[PROVIDER], frameworkType=Optional[Provider]}
thanks
ApplicationComponent
@ApplicationScope @Component(
modules = [
AndroidSupportInjectionModule::class,
AndroidInjectionModule::class,
ApplicationModule::class,
ActivityBuilder::class,
FragmentBuilder::class
] ) interface ApplicationComponent : AndroidInjector<Altagem> {
@Component.Factory
abstract class Factory : AndroidInjector.Factory<Altagem> {
abstract override fun create(@BindsInstance instance: Altagem): ApplicationComponent
}
fun activityInjector(): DispatchingAndroidInjector<Activity>
val loggedInComponentFactory: LoggedInComponent.Factory
val loggedInComponentManager: LoggedInComponentManager
}
LoggedInComponent
@LoggedInScope @Subcomponent(modules =
[LoggedInActivityBuilder::class, DatabaseModule::class]) interface
LoggedInComponent {
@Subcomponent.Factory
interface Factory {
fun create(): LoggedInComponent
}
fun activityInjector(): DispatchingAndroidInjector<Activity>
}
DatabaseModule
@Module
class DatabaseModule {
@Provides
@LoggedInScope
fun provideDatabase(
@ApplicationContext context: Context, @DatabaseFile databaseFile: File,
isInitialized
: Boolean
): Database =
if (isInitialized)
DatabaseBuilder.build(context)
else
DatabaseBuilder.build(context, databaseFile)
@Provides
@DatabaseFile
@LoggedInScope
fun provideDatabaseFile(fileManager: FileManager): File {
return fileManager.getFileFromInternalCache(DATABASE_FILE_NAME)
}
@Provides
@LoggedInScope
fun provideIsInitialized(appConfigurationFile: AppConfigurationFile) = true
/*appConfigurationFile.getAppConfiguration().isDatabaseInitialized*/
}
UseCaseModule
@Module(includes = [RepositoryModule::class])
abstract class UseCaseModule {
@Binds
abstract fun bindsGetReportSheetsUseCase(useCase: GetReportSheetsUseCaseImpl): GetReportSheetsUseCase
}
RepositoryModule
@Module(includes = [DataSourcesModule::class])
abstract class RepositoryModule {
@Binds
abstract fun bindsReportSheetRepository(repository: ReportSheetRepositoryImpl): ReportSheetRepository
}
DataSourceModule
@Module
abstract class DataSourcesModule {
@Binds
abstract fun bindsReportSheetDatabase(database: ReportSheetDatabaseImpl): ReportSheetDatabase
}
ReportSheetDatabaseImpl
class ReportSheetDatabaseImpl @Inject constructor(
private val database: Database // Build fail when trying to inject db here
) : ReportSheetDatabase {
override fun getReportSheetsByReportId(reportId: String): Flow<List<ReportSheet>> = emptyFlow()
}
ReportSheetRepositoryImpl
class ReportSheetRepositoryImpl @Inject constructor(
private val reportSheetDatabase: ReportSheetDatabase
) : ReportSheetRepository {
override fun getReportSheetsById(reportId: String): Flow<List<ReportSheet>> =
reportSheetDatabase.getReportSheetsByReportId(reportId)
}
GetReportSheetsUseCaseImpl
class GetReportSheetsUseCaseImpl @Inject constructor(
private val reportSheetRepository: ReportSheetRepository
): GetReportSheetsUseCase {
override fun invoke(reportId: String): Flow<List<ReportSheet>> =
reportSheetRepository.getReportSheetsById(reportId)
}
ReportViewModel
class ReportViewModel @Inject constructor(
app: Altagem,
private val getReportSheetsUseCase: GetReportSheetsUseCase
/*, database: Database // build succeed if database injected here */
) : BaseViewModel(app) {
}
LoggedInActivityBuilder
@Module
abstract class LoggedInActivityBuilder{
@ActivityScope
@ContributesAndroidInjector(modules = [HomeModule::class, FragmentBuilder::class])
abstract fun contributesHomeActivity(): HomeActivity
@ActivityScope
@ContributesAndroidInjector(modules = [InfoModule::class,FragmentBuilder::class])
abstract fun contributesInfoActivity(): InfoActivity
}
FragmentBuilder
@Module
abstract class FragmentBuilder {
@FragmentScope
@ContributesAndroidInjector(modules = [ReportModule::class])
abstract fun contributeReportFragment(): ReportFragment
}
ApplicationClass
class App : Application(), HasAndroidInjector {
private lateinit var appComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
appComponent = initAppComponent()
}
private fun initAppComponent() = DaggerApplicationComponent
.factory()
.create(this)
.apply { inject(this@App) }
override fun androidInjector(): AndroidInjector<Any> {
fun injectActivity(activity: BaseActivity) {
if (activity.isDatabaseSubComponent)
appComponent
.loggedInComponentManager
.getComponent().activityInjector()
.inject(activity)
else
appComponent.activityInjector().inject(activity)
}
return AndroidInjector { injected ->
when (injected) {
is BaseActivity -> injectActivity(injected)
else -> {
}
}
}
}
}
LoggedInComponentManager (used to manage LoggedInComponent life cycle)
@ApplicationScope
class LoggedInComponentManager @Inject constructor(
private val dbComponentFactory: LoggedInComponent.Factory
) {
private var component: LoggedInComponent? = null
fun createDatabaseComponent() {
component = dbComponentFactory.create()
}
fun releaseDatabaseComponent() {
component = null
}
fun getComponent() = component ?: createDatabaseComponent().let { component!! }
})