component dependencies in Hilt - android

Good day, I have experience in dagger2 but I'm trying to gain some experience in Hilt so I start with the demo application by migrating the demo from Dagger2 to Hilt. in my Dagger app, I have two components ApplicationComponent and ActivityComponent. ActivityComponent depends on the ApplicationComponent so I wrote ActivityComponent as following:-
#ActivityScope
#Component(
dependencies = [ApplicationComponent::class],
modules = [ActivityModule::class]
)
interface ActivityComponent {
fun inject(dummyActivity: DummyActivity)
fun inject(dummyActivity2: DummyActivity2)
}
so I tried to achieve the same behavior in Hilt. I know Hilt users never define or instantiate Dagger components directly. Instead, Hilt offers predefined components that are generated for us, and as the documentation explains "child component can have dependencies on any binding in an ancestor component"
so i thought i can do the following as ActivityComponent is a child of the SingletonComponent/ ApplicationComponent so ActivityComponent can access any bindings in the ApplicationComponent:-
#Module
#InstallIn(ApplicationComponent::class)
class ApplicationModule {
#Provides
#Singleton
fun provideDispatcherProvider(): DispatcherProvider = FlowDispatcherProvider()
#Provides
#Singleton
fun provideSharedPreferences(#ApplicationContext application: Application): SharedPreferences =
application.getSharedPreferences("project-prefs", Context.MODE_PRIVATE)
#Provides
#Singleton
fun provideNetworkHelper(#ApplicationContext application: Application): NetworkHelper = NetworkHelper(application)
#Provides
#Singleton
fun provideNetworkService(#ApplicationContext application: Application): NetworkService =
Networking.create(
BuildConfig.USERNAME,
BuildConfig.PASSWORD,
BuildConfig.BASE_URL,
application.cacheDir,
10 * 1024 * 1024 // 10MB
)
}
#Module
#InstallIn(ActivityComponent::class)
class ActivityModule {
#Provides
#ActivityScoped
fun provideMyHelper(
dispatcherProvider: DispatcherProvider,
networkHelper: NetworkHelper
):MyHelper = MyHelper(dispatcherProvider, networkHelper)
}
class MyHelper
(
private val dispatcherProvider: DispatcherProvider,
private val networkHelper: NetworkHelper
) {
}
#ActivityScoped
#AndroidEntryPoint
class DummyActivity(): AppCompatActivity() {
#Inject
lateinit var myhelper: MyHelper
}
but when I try to build this code I get:-
error: [Dagger/MissingBinding] #com.mypackage.ApplicationContext android.app.application cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements MyApplication_GeneratedInjector,
^
#com.mypackage.ApplicationContext android.app.Application is injected at
mypackage.module.ApplicationModule.provideNetworkHelper(application)
com.mypackage.NetworkHelper is injected at
com.mypackage.ActivityModule.provideMyHelper(�, networkHelper)
com.mypackage.MyHelper is injected at
com.mypackage.DummyActivity.myhelper
com.mypackage.DummyActivity is injected at
com.mypackage.DummyActivity.myhelper
so what am I missing here?
Thanks in advance

Apparently you do not import the ApplicationContext qualifier correctly, it should be dagger.hilt.android.qualifiers.ApplicationContext.

Related

Hilt - app cannot be provided without an #Inject constructor or an #Provides-annotated method

This is my hilt module:
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideDatabase(app: App) =
Room.databaseBuilder(app, AppDatabase::class.java, "app_database").build()
#Provides
fun provideUserDao(database: AppDatabase) = database.userDao()
#Provides
#Singleton
fun provideApi(): ContactsService {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(
Moshi.Builder().build()))
.build()
.create(ContactsService::class.java)
}
#Provides
#Singleton
fun provideRepository(api:ContactsService,dao: ContactsDao): ContactRepository{
return ContactRepositoryImpl(api,dao)
}
}
This is the indicated App class:
#HiltAndroidApp
class App : Application() {
}
This is the error I get:
..\assignment\App_HiltComponents.java:128: error: [Dagger/MissingBinding] com.example.assignment.App cannot be provided without an #Inject constructor or an #Provides-annotated method.
I double check every class that I use injection and in each of them I used #Inject annotation. I read every stack question but none of them solved my problem.
Hilt is built on top of the DI library Dagger. Dagger works only with a specific type of class.
Hilt provides #ApplicationContext as Context type.
You should provide your App explicitly.
#Provides
fun provideApp(#ApplicationContext context: Context): App = context as App

Gradle build keeps failing due to MissingBinding on Dagger Hilt migration

I'm trying to migrate my project to Dagger Hilt and facing an issue with missing binding. I was following the Googles codelab to achieve this.
This is the place where the build fails:
error: [Dagger/MissingBinding] java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> cannot be provided without an #Provides-annotated method.
public abstract static class ApplicationC implements WhatToCookApp_GeneratedInjector,
^
java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> is injected at
dagger.android.DispatchingAndroidInjector(�, injectorFactoriesWithStringKeys)
dagger.android.DispatchingAndroidInjector<java.lang.Object> is injected at
dagger.android.support.DaggerAppCompatActivity.androidInjector
at.bwappsandmore.whattocook.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [at.bwappsandmore.whattocook.WhatToCookApp_HiltComponents.ApplicationC ? at.bwappsandmore.whattocook.di.ActivityModule_InjectMainActivity.MainActivitySubcomponent]
It is also requested at:
dagger.android.DispatchingAndroidInjector(�, injectorFactoriesWithStringKeys)
The following other entry points also depend on it:
These are the relevant parts of the project:
#HiltAndroidApp
open class WhatToCookApp : Application() {
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
return DaggerAppComponent.factory().create(applicationContext)
}
}
The AppComponent:
#Singleton
#Component(
modules = [AppModule::class,
ActivityModule::class,
AndroidSupportInjectionModule::class]
)
interface AppComponent : AndroidInjector<WhatToCookApp> {
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
The AppModule:
#InstallIn(ApplicationComponent::class)
#Module
class AppModule {
#Provides
fun provideDB(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getDatabase(context)
}
#Provides
fun provideDAO(app: AppDatabase): WhatToCookDao {
return app.whatToCookDao()
}
#Provides
fun provideAppRepository(dao: WhatToCookDao): AppRepository{
return AppRepository(dao)
}
#Provides
fun provideSharedPreferences(#ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}
The ApplicationModulde:
#InstallIn(ApplicationComponent::class)
#Module
interface ActivityModule {
#ActivityScope
#ContributesAndroidInjector(modules = [ViewModelModule::class])
fun injectMainActivity(): MainActivity
}
The ViewModelModule:
#InstallIn(ApplicationComponent::class)
#Module
abstract class ViewModelModule {
companion object{
#Provides
fun providesSharedViewModel (activity: MainActivity) : SharedViewModel = activity.viewModel
}
}
The ActivityScope:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
I realise that I have to use the #Provides annotation for the AndroidInjector, but I don't know where and how. Any help is appreciated.
Thank you so much in advance.

Dagger.android UserScope

I'm trying to create UserScope with https://google.github.io/dagger/android I have #Singleton, #ActivityScope, #FragmentScope and #UserScope.
AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
AppModule::class,
ApiModule::class
]
)
interface AppComponent {
fun inject(application: Application)
fun createUserComponent(): UserComponent.Builder
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun apiModule(module: ApiModule): Builder
fun build(): AppComponent
}
}
UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class]
)
interface UserComponent {
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun user(user: User): Builder
fun build(): UserComponent
}
}
UserModule:
#Module
class UserModule {
#UserScope
#Provides
fun provideUser(user: User): User = user
}
Here is how I am creating UserComponent after successfull login:
private fun createSession(user: User) {
userComponent = appComponent.createUserComponent().user(user).build()
}
And also I have UserManager which is triggering an issue with User injection constructor param
#UserScope
class SomeManager #Inject constructor(
private val apiService: ApiService,
private val user: User
) {}
Error message: Error:[dagger.android.AndroidInjector.inject(T)]
domain.model.authorize.User cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
di.modules.MainActivityBindingModule_ContributeUserFragment.UserFragmentSubcomponent
scoped with #di.scopes.FragmentScope may not reference bindings with
different scopes: #di.scopes.UserScope class
domain.managers.SomeManager
I want to create #UserScope to manage related ApiManagers lifecycle
UPD
#Module
class UserFragmentModule {
#Provides
#FragmentScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
ContributesAndroidInjector logic:
#Module
interface ActivityBindingModule {
#ActivityScope
#ContributesAndroidInjector(modules = [SplashModule::class])
fun contributeSplashActivity(): SplashActivity
#ActivityScope
#ContributesAndroidInjector(modules = [SignInModule::class])
fun contributeAuthorizeActivity(): Activity
#ActivityScope
#ContributesAndroidInjector(modules = [MainModule::class])
fun contributeMainActivity(): MainActivity
}
#Module(includes = [MainActivityBindingModule::class])
class MainModule
#Module
interface MainActivityBindingModule {
#FragmentScope
#ContributesAndroidInjector(modules = [UserFragmentModule::class])
fun contributeUserFragment(): UserFragment
}
You are trying to inject a User instance that is provided only in the #UserScope in #FragmentScope.
Basically, the rule is whenever you need a User injected - you need to be in the #UserScope.
You need to change your #FragmentScope to #UserScope annotation in all places like that.
From what you posted I believe it is here:
#Module
class UserFragmentModule {
#Provides
#UserScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
And you'll need to move your UserFragmentModule to the UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class, UserFragmentModule::class]
)
interface UserComponent {
And you'll also need to inject with application.userComponent into your UserFragment
Here is a good article about subcomponents. I'd recommend you to read it to gain full understanding.
Another option, I'm not sure about this one, but maybe you can just inject your UserFragment with your application.userComponent.
I think your problem is wrong use of scopes.
Scopes help us to handle injected item life-cycle to prevent keep un-needed object in whole app life cycle.
and take look at this image:
Your modules should be in same scope that your component is.
for example in your AppComponent, you have ActivityBindingModule that have ActivityScope. why you do this?
I think it is better to have a activityComopnent that have dependency toappComponent. If your activityComponent had some dependency in upper scoped component (appComponent), first you had add dependency of that component. (dependencies= [ AppComponent::class]). Second, you had to expose needed dependency in appComponent with a method that return needed dependency object. Finally in wireUpping you should call appComponent as call activity modules in Dagger wire upping. ( I will show this in a example)
So all you need is activityComponent like this:
#ActivityScope
#Component(modules = [ActivityBindingModule::class],dependencies= [ AppComponent::class])
interface activityComponent {
.
.
.
This is my example written in java that shows how connect appComponent and activityComponent:
#Singleton
#Component(modules = {ApplicationModule.class ,ServicesModule.class})
public interface ApplicationComponent {
void inject(ImicoApplication imicoApplication);
// exposed for child-components.
Context getContext();
Application getApplication();
CompanyService getCompanyService();
CategoryService getCategoryService();
RatingService getRatingService();
NewsService getNewsService();
AuthorizationManager getAuthorizationManager();
}
And this is activityComponent:
#ActivityScope
#Component(dependencies = {ApplicationComponent.class},modules = {SearchActivityModule.class})
public interface SearchserviceComponent {
void inject (SearchFragment searchFragment);
}
And in SearchFragment do this for wire-up:
DaggerSearchserviceComponent.builder()
.applicationComponent(((ImicoApplication) getActivity().getApplication()).getApplicationComponent())
.searchActivityModule(new SearchActivityModule(this)).build().inject(this);
If in SearchFragment i need CompanyService just inject it and it is provided and exposed by applicationComponent.

Dagger 2 not injecting sharedPreference

Hi i am new to dagger 2 and trying to inject an instance of sharedPreference inside my MyActivity class below:
class MyApplication : Application() {
companion object {
#JvmStatic lateinit var applicationComponent : ApplicationComponent
}
override fun onCreate() {
super.onCreate()
applicationComponent = DaggerApplicationComponent.builder().androidModule(AndroidModule(this)).build()
}
}
Here is the component and modules
#Singleton
#Component(modules = arrayOf(AndroidModule::class))
interface ApplicationComponent {
fun inject(mainActivity: MainActivity)
}
#Module
class AndroidModule (private val application: Application){
#Provides
#Singleton
fun provideApplicationContext() : Context = application
#Provides
#Singleton
fun provideSharedPreference() : SharedPreferences = application.getSharedPreferences("shared pref", Context.MODE_PRIVATE)
}
class MainActivity: Activity{
#Inject
internal lateinit var sharedPreference: SharedPreferences
#Inject
internal lateinit var MainScreenPresenter: MainScreenContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_Screen)
MyApplication.applicationComponent.inject(this)
sharedPreference.toString()
initiateViews()
}
}
I get the error below:
Error:(7, 1) error: android.content.SharedPreferences cannot be provided without an #Provides- or #Produces-annotated method.
You have done it a little bit incorrect. First of all now there is dagger-android that helps with the problem of principle that solves the problem that components (such as Activities) should not know about how the injection happens.
you can read it here: https://medium.com/#iammert/new-android-injector-with-dagger-2-part-1-8baa60152abe
Just to be sure that dagger dependencies are in the Android project:
android {
kapt {
generateStubs = true
}
}
compile "com.google.dagger:dagger:2.13"
compile "com.google.dagger:dagger-android:2.13"
compile "com.google.dagger:dagger-android-support:2.13"
kapt "com.google.dagger:dagger-compiler:2.13"
kapt "com.google.dagger:dagger-android-processor:2.13"
Your mistake is that you didn't tell to your graph that you want to make injections into the MainActivity. In the best way you should create Subcomponent for MainActivity, connect it with another Module for MainActivity that have injections that you want to inject into the MainActivity, set in your AppComponent the connection with Subcomponent and only than in MainAcitivy onCreate() method inject your dependency graph. But with dagger-android everything is easier.
Here is the code:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<DaggerApplication> {
fun inject(application: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance fun application(application: MyApplication): Builder
fun build(): AppComponent
}
}
AndroidSupportInjectionModule.class : This goes from the dagger.android.support library. And it provides Android components (Activities/Fragments/Services/BroadcastReceiver/ContentProvider) with our module.
#Component.Builder in dagger2.10 provides us better way to create a builder of DaggerAppComponent.
#BindsInstance in the Builder will automatically create an instance of MyApplication so in AppModule we don't need to instantiate with MyApplication. It is already a dependency in the graph.
ActivityBindingModule.clas is our. I will tell about it later.
You can read more about this part here: https://proandroiddev.com/dagger-2-component-builder-1f2b91237856
Next is AppModule.class :
#Module
abstract class AppModule{
#Binds
abstract fun provideContext(application: MyApplication) : Context
#Module
companion object {
#JvmStatic
#Provides
fun provideSharedPreferences(context: Context): SharedPreferences =
context.getSharedPreferences("SharedPreferences", Context.MODE_PRIVATE)
}
}
#Binds annotation replaces #Provides annotation and it just returns the value in the function parameter. As you see an instance of MyApplication is already in the graph and there is no need to inject MyApplication in the AppModule constructor.
NOTE: function with #Binds annotation should be abstract, and if there are function with #Provides annotation they should be static.
The solution in Kotlin for static funcitons you can find here: https://github.com/google/dagger/issues/900
ActivityBindingModule.class:
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun bindMainActivity(): MainActivity
}
With the ActivityBindingModule class we just create separate Module that will create Subcomponents for Android components for us.
More about ContributesAndroidInjector and Binds you can read here:
https://proandroiddev.com/dagger-2-annotations-binds-contributesandroidinjector-a09e6a57758f
MainActivityModule.class:
#Module
abstract class MainActivityModule {
#Binds
internal abstract fun provideMainActivity(activity: MainActivity): MainActivity
}
MyApplication.class:
class MyApplication: DaggerApplication(){
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
val appComponent = DaggerAppComponent.builder()
.application(this)
.build()
appComponent.inject(this)
return appComponent
}
}
Do not forget insert Application in the Mainfest file.
<application
android:name=".MyApplication"
...
>
...
</application>
For your Application class you need to implement DaggerApplication that implements HasActivityInjector/HasFragmentInjector/etc as well as call AndroidInjection.inject().
About this you can read more here : https://google.github.io/dagger/android.html
MainActivity.class:
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("TAAAAG", sharedPreferences.toString())
}
}
As you can see MainActivity now does not know how SharedPreferences are injected. Actually there is AndroidInjection.inject(this); in the DaggerAppCompatActivity. If you don't extend you class from this, than you need to specify it in onCreate method by yourself, otherwise no injections will be done.
EDIT:
You can check the code from GitHub: https://github.com/Belka1000867/Dagger2Kotlin

Dagger2 and qualifiers in dependent components

I have an app component and a dependent component. The app component declares explicit dependencies, and the dependent component can inject those. However, when I have a dependency that I have to disambiguate with a #Qualifier, the dependent component is not able to inject that dependency.
This is the app component
#Component(modules = [AppModule::class, SchedulersModule::class, StorageModule::class])
#ApplicationScope
interface AppComponent {
fun inject(app: Application)
/* other stuff omitted for brevity */
val bitmapCache: BitmapCache
#UiScheduler fun uiScheduler(): Scheduler
}
This is the scheduler module:
#Module
class SchedulersModule {
#ApplicationScope
#Provides
#IoScheduler
fun provideIoScheduler(): Scheduler = Schedulers.io()
#ApplicationScope
#Provides
#UiScheduler
fun provideMainThreadScheduler(): Scheduler = AndroidSchedulers.mainThread()
}
This is the qualifier:
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class UiScheduler
And this is the dependent component:
#Component(
dependencies = [AppComponent::class],
modules = [EditEntryActivityModule::class, ViewModelModule::class]
)
#ActivityScope
interface EditEntryActivityComponent {
fun inject(editEntryActivity: EditEntryActivity)
fun inject(editEntryFragment: EditEntryFragment)
}
This is how the scheduler is injected in the fragment:
class EditEntryFragment : Fragment() {
#Inject #UiScheduler lateinit var uiScheduler: Scheduler
/* other stuff */
}
So why can the dependent component inject the bitmap cache, declared in the parent component, but not the UI scheduler? This is the error I get:
error: io.reactivex.Scheduler cannot be provided without an #Provides- or #Produces-annotated method.
io.reactivex.Scheduler is injected at
com.test.edit.EditEntryFragment.uiScheduler
com.test.edit.EditEntryFragment is injected at
com.test.edit.EditEntryActivityComponent.inject(arg0)
1 error
Using #field:UiScheduler in class EditEntryFragment
Try #Named annotition
#Inject #field:Named("UiScheduler") lateinit var uiScheduler: Scheduler
check out this issue

Categories

Resources