I'm learning dagger2 with a module architecture. And I think, something is not clear to me, for example
in module utilites i have di package
class UtilsComponent
#Component(modules = [UtilsModule::class])
interface UtilsComponent {
fun getResourceProvider() : IResourceProvider
fun getNetworkProvider(): INetworkProvider
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): UtilsComponent.Builder
fun build(): UtilsComponent
}
}
#Module
abstract class UtilsModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
companion object {
#Provides
#JvmStatic
fun bindResourceProvider(context: Context): IResourceProvider {
return ResourceProvider(context = context)
}
#Provides
#JvmStatic
fun bindNetworkProvider(context: Context): INetworkProvider {
return NetworkProvider(context = context)
}
}
}
then in app package in AppComponent i included all modules
#Component(
dependencies = [UtilsComponent::class],
modules = [
AndroidInjectionModule::class,
ActivityBindingModule::class,
MainModule::class // test module
]
)
#AppScope
interface AppComponent: AndroidInjector<App> {
// inject to ...
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): AppComponent.Builder
fun utilsComponent(utilsComponent: UtilsComponent): AppComponent.Builder
fun build(): AppComponent
}
}
In the app component, I have MainModule - this is my test module, so this module looks like this
#Module
class MainModule {
#Provides
fun getMainPresenter(networkProvider: NetworkProvider): MainPresenter {
return MainPresenter(networkProvider)
}
}
when I run the app, I have an error
[Dagger/MissingBinding] com.example.utilities.di.UtilsModule cannot be
provided without an #Provides-annotated method. public abstract
interface AppComponent extends
dagger.android.AndroidInjector<com.example.testmoduleapp.App> {
^
com.example.utilities.di.UtilsModule is injected at
com.example.testmoduleapp.di.modules.MainModule.getMainPresenter(utilsModule)
com.example.testmoduleapp.ui.activities.main.MainPresenter is injected at
com.example.testmoduleapp.ui.activities.main.MainActivity.mainPresenter
com.example.testmoduleapp.ui.activities.main.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.example.testmoduleapp.di.AppComponent →
com.example.testmoduleapp.di.modules.ActivityBindingModule_MainActivity.MainActivitySubcomponent]
I understand that the error is because I have not a module witch return NetworkProvider object, but I can't understand how I can get this object from UtilsModule
also in App
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
companion object{
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
appComponent = DaggerAppComponent
.builder()
.application(this)
.utilsComponent(provideUtilsComponent())
.build()
}
private fun provideUtilsComponent(): UtilsComponent {
return DaggerUtilsComponent
.builder()
.application(this)
.build()
}
In Dependencies Graph You already provide INetworkProvider so when you need NetworkProvider it means Dagger can not be resolved. Change to this, don't forget to change constructor MainPresenter to INetworkProvider
#Provides
fun getMainPresenter(networkProvider: INetworkProvider): MainPresenter {
return MainPresenter(networkProvider)
}
but i cant understand how i can get this object from UtilsModule
For your question, in UtilComponent you already exposed getNetworkProvider() it means any Componenent dependencies to UtilsComponent can be get it.
Related
I am new to dagger/Hilt and trying to convert a legacy dagger application over to hilt, the app is much to large to convert in a single go so I am using https://dagger.dev/hilt/migration-guide#daggerandroid the dagger.android section
Following what little it says
#HiltAndroidApp
class MyApplication : HasAndroidInjector {
#Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Object>
override fun androidInjector() = dispatchingAndroidInjector
}
here is the application
#HiltAndroidApp
class TestApplication : Application(), HasAndroidInjector {
#Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector() = dispatchingAndroidInjector
"other code"
here is the appComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ImageLoaderModule::class,
ViewModelFactoryModule::class,
// Activity Bindings
SplashActivityBindingModule::class,
WelcomeActivityBindingModule::class,
MainActivityBindingModule::class,
DevSettingsActivityBindingModule::class,
CustomizeBindingModule::class,
// Service Bindings
NotificationsServiceBindingModule::class
]
)
interface AppComponent {
fun inject(application: TestApplication)
#Component.Builder
interface Builder {
fun build(): AppComponent
#BindsInstance
fun forApplication(application: Application): Builder
}
}
here is the app module
#Module
object AppModule {
#Provides
fun provideAppContext(application: Application): Context = application.baseContext
#Provides
fun provideAppUpdateManager(context: Context): AppUpdateManager =
AppUpdateManagerFactory.create(context)
#Provides
fun provideAuthStateManager(): AuthStateManager = AuthStateManager
#Provides
fun provideSharedPreferencesManager(): SharedPreferencesManager = SharedPreferencesManager
#Provides
fun Manager(): Manager = Manager
#Provides
fun provideIOCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.IO
}
I have been searching around for answers with no luck on this specific issue.
With the set up below i'm not be able to inject a Singleton object to a Activity inside a dynamic feature module. I can inject to a subComponent, MainActivity, but not to an Activity that's in dynamic feature module.
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
My AppModule
#Module(includes = [AppProviderModule::class])
abstract class AppModule {
#Binds
abstract fun bindContext(application: Application): Context
}
#Module
object AppProviderModule {
#Provides
#Singleton
fun provideSharedPreferences(application: Application): SharedPreferences {
return application.getSharedPreferences("PrefName", Context.MODE_PRIVATE)
}
}
Dynamic Feature Module GalleryComponent
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
}
And MyApplication
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Application
return DaggerAppComponent.factory().create(this).apply {
inject(this#MyApplication)
}
}
}
Activity in dynamic feature module, when only inject GalleryViewer and DummyDependency is injected from GalleryModule it works fine
class GalleryActivity : AppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
#Inject
lateinit var galleryViewer: GalleryViewer
#Inject
lateinit var dummyDependency: DummyDependency
override fun onCreate(savedInstanceState: Bundle?) {
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.build()
.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
}
When i try to inject SharedPreferences or any dependency that does not depend on any arguments like context or application from AppModule i get error
error: [Dagger/MissingBinding] android.content.SharedPreferences cannot be provided without an #Provides-annotated method.
The error here is not including provision method for dependencies in AppComponent and not building dynamic feature component properly.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
/**
* 🔥🔥🔥 This method is required to get this object from a class that uses this component
* as dependent component
*/
fun provideSharedPreferences(): SharedPreferences
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
In dynamic feature
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
// Alternative1 With Builder
#Component.Builder
interface Builder {
fun build(): GalleryComponent
#BindsInstance
fun application(application: Application): Builder
fun galleryModule(module: GalleryModule): Builder
fun appComponent(appComponent: AppComponent): Builder
}
// Alternative2 With Factory
#Component.Factory
interface Factory {
fun create(appComponent: AppComponent,
galleryModule: GalleryModule,
#BindsInstance application: Application): GalleryComponent
}
}
You must either use Builder or Factory, with hilt none of this might be necessary in the future, however it does not support dynamic feature yet, i rather factory pattern since they deprecated Builder pattern.
inside Activity onCreate initialize injection
private fun initInjections() {
// Alternative1 With Builder
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.application(application)
.galleryModule(GalleryModule())
.build()
.inject(this)
// Alternative2 With Factory
DaggerGalleryComponent
.factory()
.create((application as MyApplication).appComponent, GalleryModule(), application)
.inject(this)
}
You should choose the same pattern used inside feature component.
Hi is it possible to add test modules in my AppComponent?
Below is my real representation of my appComponent
#Singleton
#Component(modules = arrayOf(MainModule::class,
AnalyticsModule::class,
MainAndroidBinding::class,
AccountAndroidBinding::class,
AndroidSupportInjectionModule::class,
HomeAndroidBinding::class,
NetworkModule::class))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(myApplication: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: Application): Builder
fun build(): ApplicationComponent
}
}
I could just add the test modules directly to the testAppComponent like this but it doesnt offer me much flexibility to dynamically add different testModules.
#Singleton
#Component(modules = [
(MainModuleTest::class),
(TestMainAndroidBindingTest::class),
(AccountAndroidBindingTest::class),
(AnalyticsModuleTest::class),
(AndroidSupportInjectionModule::class),
(NetworkModuleTest::class)])
interface TestAppComponent : ApplicationComponent {
fun inject(launchActivityTest: LaunchActivityTest)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: Application): Builder
fun build(): TestAppComponent
}
}
Here is my MyApplication class
class MyApplication : DaggerApplication() {
companion object {
private lateinit var application: MyApplication
fun get(): MyApplication {
return application
}
}
#Inject
lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>
lateinit var applicationComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
application = this
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
applicationComponent = DaggerApplicationComponent.builder()
.application(this)
.build()
applicationComponent.inject(this)
return applicationComponent
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
On the LaunchActivityTest this is how i set it up to use this testApp component
#Before
fun setUp() {
val app = InstrumentationRegistry.getTargetContext().applicationContext as MyApplication
val testAppComponent = DaggerTestAppComponent.builder().application(app).build()
app.applicationComponent = testAppComponent
testAppComponent.inject(this)
}
I was following this guide until i stumbled o the point where my DaggerTestAppComponent doesnt expose the modules for me to dynamically add myself due to the fact that my AppComponent class extends AndroidInjector which automatically adds the modules for you
https://proandroiddev.com/writing-espresso-instrumentation-tests-with-dagger2-kotlin-d30f12c4769b
The above dynamically adds its modules like this:
testAppComponent = DaggerTestAppComponent.builder()
.appModule(AppModule(app))
.apiModule(TestApiModule())
.prefModule(TestPrefModule())
.build()
I cant do that in my case unless i redo my AppComponent so that it doesnt extend AndroidInjector. If i do that then in my real impl code i have to manually set the modules.
Is there any other way?
You should add a function to your component builder and use "BindsInstance".
Example component
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
ApplicationTestModule.class,
ActivityBuilder.class})
public interface TestExampleComponent extends AndroidInjector<DaggerApplication> {
void inject(TestApplication app);
#Override
void inject(DaggerApplication instance);
#Component.Builder
interface Builder {
#BindsInstance
TestExampleComponent.Builder application(DaggerApplication application);
Builder applicationModule(ApplicationTestModule appTestModule);
TestExampleComponent build();
}
}
In this component, I added applicationModule function with using "BindsInstance" and I can pass ApplicationTestModule.
Then you can add different test modules.
TestApplicationComponent appComponent = DaggerTestAppComponent.builder().application(this).
applicationModule(appTestModule).build();
appComponent.inject(this);
Hi i have a ViewModel that has two var's that get injected using Dagger 2.11.
However it never gets injected in my viewModel whilst everywhere else in my app, these same dependecy Vars get initialised and injected perfectly
Below is my viewModel
class MyViewModel : AndroidViewModel, MyViewModelContract {
private lateinit var pointsBalance: MutableLiveData<PointsBalance>
#Inject
lateinit var accountDelegator: AccountDelegatorContract
#Inject
constructor(application: Application) : super(application)
init {
DaggerApplicationComponent.builder().application(getApplication() as MyApplication).build().inject(this)
}
override fun getPointsBalance(): LiveData<PointsBalance> {
if (!this::pointsBalance.isInitialized) {
//get balance from network api etc
}
return pointsBalance
}
accountDelegator complains that it is not initialised
Below is how i bind this viewModel inside MyModule.class
#Provides
#JvmStatic
#Singleton
fun providesViewModel(viewModel: MyViewModel): MyViewModelContract = viewModel
my custom application
class MyApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
val applicationComponent = DaggerApplicationComponent.builder()
.application(this)
.build()
applicationComponent.inject(this)
return applicationComponent
}
}
my applicationComponent
#Singleton
#Component(modules = arrayOf(MyModule::class,
))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(mApplication: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: MyApplication): Builder
fun build(): ApplicationComponent
}
}
How i use this viewmodel in a activity/fragment
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
I believe issue is that you're not calling inject. You'd need for a start to add following to ApplicationComponent
void inject(MyViewModel viewModel);
What I'm doing here then fwiw is inheriting from AndroidViewModel (which gives access to application instance) then calling following in ViewModel class (you'd need to substitute DaggerProvider.getComponent with however you're accessing component in your code)
init {
DaggerProvider.getComponent(application).inject(this)
}
There have been many other similar questions, but none of the answers have been applicable to my code. I cannot figure out what I have done wrong.
First I have a NetworkModule that is used as a module for the ApplicationComponent:
#Module
open class NetworkModule {
companion object {
private val BASE = "http://www.example.com/"
}
#Provides #ApplicationScope
fun provideClient(): OkHttpClient = OkHttpClient()
#Provides #ApplicationScope
fun provideMoshi(): Moshi {
return Moshi.Builder().add(InstantAdapter).add(UriAdapter).build()
}
#Provides #ApplicationScope
fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit {
return Retrofit.Builder().client(client).baseUrl(BASE)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides #ApplicationScope
fun provideArticleService(retrofit: Retrofit): ArticleService {
return retrofit.create(ArticleService::class.java)
}
}
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {}
Then the ApplicationComponent is built in my Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
plantLog()
drawDagger()
}
private fun drawDagger() {
Injector.initializeApplicationComponent(this)
}
// ...
}
object Injector {
lateinit var applicationComponent: ApplicationComponent
private set
fun initializeApplicationComponent(context: Context) {
applicationComponent = DaggerApplicationComponent.builder()
.contextModule(ContextModule(context))
.networkModule(NetworkModule())
.realmModule(RealmModule())
.build()
}
// ...
}
Then I have an ActivityModule that is used in the ActivityComponent (which has ApplicationComponent as a dependency):
#Module
open class ActivityModule(private val activity: AppCompatActivity) {
#Provides #ActivityScope #ActivityContext
fun provideContext(): Context = activity
#Provides #ActivityScope
fun provideFragmentManager(): FragmentManager = activity.supportFragmentManager
}
#ActivityScope #Component(dependencies = arrayOf(ApplicationComponent::class), modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(activity: MainActivity)
}
Finally, I create a new ActivityComponent in the MainActivity and #Inject the ArticleService:
class MainActivity : AppCompatActivity() {
#Inject lateinit var service: ArticleService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerActivityComponent.builder()
.applicationComponent(Injector.applicationComponent)
.activityModule(ActivityModule(this))
.build().inject(this)
service.getNewsArticles()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(
{ response -> onNext(response) },
{ error -> onError(error) })
}
// ...
}
But when I try to build I get the following error, even though I believe the provideArticleService() function in NetworkModule is annotated correctly:
ArticleService cannot be provided without an #Provides- or
#Produces-annotated method.
You're missing the provision methods to inherit to your Activity scoped component. Either use subcomponents instead of component dependency, or define the provision methods in your application component.
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {
ArticleService articleService();
}