0

I'm new to DI and recently started using dagger2 in my project. I'm able to provide repositories and use ViewModelFactory for my fragment, but I can't get an idea of how to provide ApplicationContext into my ViewModel. I need it to access SharePreferenceHelper class.

Here is my dagger2 implementation:

Where exactly should I create provide fun for appContext and how to Inject it from ViewModel?

I know this is a long code snippet, but would be grateful if someone could take a look.

ApplicationComponent:

@Singleton
@Component(
    modules = [
        ApplicationModule::class,
        AndroidSupportInjectionModule::class,
        SettingsModule::class
    ]
)

interface ApplicationComponent : AndroidInjector<MyApplication> {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance applicationContext: Context): ApplicationComponent
    }

}

ApplicationModule:

@Module
class ApplicationModule {

    @Singleton
    @Provides
    fun provideAssetsRepository(context: Context): AssetsRepository {
        return AssetsRepository(
            AppDatabase.getDatabase(context.applicationContext).assetDao()
        )
    }

    @Singleton
    @Provides
    fun provideExpensesRepository(context: Context): ExpensesRepository {
        return ExpensesRepository(
            AppDatabase.getDatabase(context.applicationContext).expenseDao()
        )
    }

    @Singleton
    @Provides
    fun provideTransactionsRepository(context: Context): TransactionsRepository {
        return TransactionsRepository(
            AppDatabase.getDatabase(context.applicationContext).transactionsDao()
        )
    }

}

MyApplication:

open class MyApplication : DaggerApplication() {

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerApplicationComponent.factory().create(applicationContext)
    }

}

SettingsModule:

@Module
abstract class SettingsModule {

    @ContributesAndroidInjector(
        modules = [
            ViewModelBuilder::class]
    )

    internal abstract fun settingsFragment(): SettingsFragment

    @Binds
    @IntoMap
    @ViewModelKey(SettingsViewModel::class)
    internal abstract fun bindViewModel(viewModel: SettingsViewModel): ViewModel
}

ViewModelFactory:

class ViewModelFactory @Inject constructor(
    private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        var creator: Provider<out ViewModel>? = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }
        if (creator == null) {
            throw IllegalArgumentException("Unknown model class: $modelClass")
        }
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

@Module
internal abstract class ViewModelBuilder {
    @Binds
    internal abstract fun bindViewModelFactory(
        factory: ViewModelFactory
    ): ViewModelProvider.Factory
}

@Target(
    AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
MakinTosH
  • 623
  • 7
  • 12
  • 2
    Why don't you inject your `SharePreferenceHelper` instead? Make a `@Provides` method for it? It's generally a bad practice to have the ViewModel use a context. – Nicolas May 02 '20 at 15:02
  • My `PreferenceHelper` is an object(so no constructor to pass context), if I use it as a class and make a `@Provides` method for it, I then need to inject it in every fragment's constructor to use it, or can I somehow use field injection? – MakinTosH May 02 '20 at 15:33
  • Make it a class, annotate it with `@Singleton` and add `@Inject constructor(context: Context)`, and inject it as you'd like, either constructor or field injection it doesn't matter. This way you don't even need `@Provides`. – Nicolas May 02 '20 at 16:06
  • Ok, so this is my class now `@Singleton class PreferenceHelper @Inject constructor(private val context: Context) {}` and when I inject it in MainActivity `@Inject lateinit var prefsHelper: PreferenceHelper` I get nullException because it doesn't initialise – MakinTosH May 02 '20 at 17:23
  • Did you inject fields in your activity? You need to do something like `(applicationContext as MyApp).appComponent.inject(this)` in onCreate, the `inject` method being in your `AppComponent` class. (`fun inject(activity: MainActivity)`) – Nicolas May 02 '20 at 17:38
  • Ok, I tried like this `(applicationContext as MyApplication).androidInjector().inject(this)` in `MainActivity` but I get `java.lang.IllegalArgumentException: No injector factory bound for Class<.MainActivity>` – MakinTosH May 02 '20 at 18:27
  • I never used dagger-android, can't help you with that. I suggest you find a tutorial somewhere, it'll be better explained that what I can ever do. – Nicolas May 02 '20 at 18:29

0 Answers0