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();
}
Related
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.
I’m trying to create an basic architecture with Dagger 2 for my study project but I have encountered several problems with it…
The current error daggers tell me
FeedMeApplicationComponent.java:7: error: [Dagger/IncompatiblyScopedBindings] .FeedMeApplicationComponent (unscoped) may not reference scoped bindings:
I only have this problem when I add the ActivityMainModule as a module of the application
and ActivityMainModule contains a sub component only related to the MainActivity.
I don’t understand why I cannot add this module of sub component to the Application Graph :confusing
Those are my Dagger classes…
class FeedMeApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerFeedMeApplicationComponent.factory().create(this)
}
}
#Component(modules = [AndroidInjectionModule::class, NetworkModule::class, NutritionModule::class, ActivityMainModule::class])
interface FeedMeApplicationComponent : AndroidInjector<FeedMeApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): FeedMeApplicationComponent
}
override fun inject(instance: FeedMeApplication?)
}
#Module
object NetworkModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionService(retrofit: Retrofit): NutritionService {
return retrofit.create(NutritionService::class.java)
}
#Singleton
#Provides
#JvmStatic
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl("https://api.edamam.com/api")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
#JvmStatic
fun provideOkHttp(): OkHttpClient {
return OkHttpClient()
.newBuilder()
.addInterceptor(ApiInterceptor())
.build()
}
private class ApiInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
request
.addHeader("api_id", “abc")
.addHeader("app_key", “123")
return chain.proceed(request.build())
}
}
}
#Module
object NutritionModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionRepository(nutritionService: NutritionService): NutritionRepository {
return NutritionRepository(nutritionService)
}
}
#Module(subcomponents = [MainActivityComponent::class], includes = [MainModule::class])
abstract class ActivityMainModule {
#Binds
#IntoMap
#ClassKey(MainActivity::class)
abstract fun bindAndroidInjector(factory: MainActivityComponent.Factory): AndroidInjector.Factory<*>
}
#Module
object MainModule {
#Singleton
#Provides
#JvmStatic
fun provideMainViewModelFactory(nutritionRepository: NutritionRepository): MainViewModel.Factory {
return MainViewModel.Factory(nutritionRepository)
}
#Provides
#JvmStatic
fun provideMainViewModel(
viewModelFactory: MainViewModel.Factory,
fragmentActivity: FragmentActivity
): MainViewModel {
return ViewModelProviders.of(fragmentActivity, viewModelFactory)
.get(MainViewModel::class.java)
}
}
#ActivityScope
#Subcomponent
interface MainActivityComponent : AndroidInjector<MainActivity> {
#Subcomponent.Factory
interface Factory : AndroidInjector.Factory<MainActivity> {}
}
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel.liveDataFoodAnalysis.observe(this, Observer { food ->
Log.d("Food answer", food.uri)
})
mainViewModel.getFoodAnalysisResponse("egg")
}
}
You will have to annotate FeedMeApplicationComponent with #Singleton. because both NetworkModule and NutritionModule define #Provides functions scoped with #Singleton, any component that uses these modules must also specify its own scope as #Singleton.
From Dagger docs: -
Since Dagger 2 associates scoped instances in the graph with instances
of component implementations, the components themselves need to
declare which scope they intend to represent. For example, it wouldn’t
make any sense to have a #Singleton binding and a #RequestScoped
binding in the same component because those scopes have different
lifecycles and thus must live in components with different lifecycles.
To declare that a component is associated with a given scope, simply
apply the scope annotation to the component interface.
In the following code, the field serviceUtil is not being injected by Dagger:
AppController.kt
class App : Application() {
#Inject
lateinit var serviceUtil: ServiceUtil
init {
DaggerAppComponent
.builder()
.build()
.inject(this)
}
override fun onCreate() {
super.onCreate()
context = this
}
fun startService() {
serviceUtil.startService()
}
companion object {
lateinit var context: App
}
}
AppComponent.kt
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: Application)
}
ServiceUtilModule.kt
#Module
class ServiceUtilModule {
#Provides
fun provideServiceUtil() : ServiceUtil {
return ServiceUtil()
}
}
From my main activity I call:
App.context.startService()
You mistyped here
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: Application)
}
You should pass you App class as argument, not the basic one.
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: App)
}
I am newbie in dependency injection with Dagger in Android
I have this error Unable to start activity ComponentInfo{MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property dispatchingAndroidInjector has not been initialized
I can't know what is the exact error in my code, Should I change something ? Although in another project I work on, they have the same code except AndroidInjection.inject(this) and the code is working perfectly
Thanks a lot for your help
Here is my files:
AppComponent
#Singleton
#Component(
modules = [AndroidSupportInjectionModule::class,
AppModule::class, NetworkModule::class,
ActivityBuilder::class]
)
interface AppComponent : AndroidInjector<Application> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
AppModule
#Module(includes = [ViewModelModule::class])
class AppModule {
#Provides
fun provideContext(application: MyApplication): Context {
return application.applicationContext
}
#Provides
fun provideHandler(): Handler {
return Handler()
}
#Provides
#Singleton
fun provideSharedPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
}
#Provides
#Singleton
fun provideSharedPreferenceUtils(context: Context): SharedPreferenceUtils {
return SharedPreferenceUtils(provideSharedPreferences(context))
}
}
ActivityBuilder
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
}
NetworkModule
#Module(includes = [AppModule::class])
// Safe here as we are dealing with a Dagger 2 module
#Suppress("unused")
class NetworkModule {
#Provides
#Singleton
fun provideCache(context: Context): Cache {
return Cache(context.cacheDir, CACHE_SIZE)
}
#Provides
#Singleton
fun provideOkHttpClient(cache: Cache, context: Context): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.addInterceptor { chain ->
var request = chain.request()
request = if (hasNetwork(context)!!)
request.newBuilder().header("Cache-Control", "public, max-age=" + 5).build()
else
request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7).build()
chain.proceed(request)
}
.build()
}
/**
* Provides the Quotes service implementation.
* #param retrofit the Retrofit object used to instantiate the service
* #return the Quote service implementation.
*/
#Provides
#Singleton
fun provideQuotesAPI(retrofit: Retrofit): QuotesAPI {
return retrofit.create(QuotesAPI::class.java)
}
/**
* Provides the Retrofit object.
* #return the Retrofit object
*/
#Provides
#Singleton
fun provideRetrofitInterface(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(okHttpClient)
.build()
}
}
ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(QuotesListViewModel::class)
internal abstract fun bindsQuotesListViewModel(quotesListViewModel: QuotesListViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(QuoteListItemViewModel::class)
internal abstract fun bindsQuoteListItemViewModel(quoteListItemViewModel: QuoteListItemViewModel): ViewModel
}
MyApplication
class MyApplication : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
override fun onCreate() {
super.onCreate()
// StateSaver.setEnabledForAllActivitiesAndSupportFragments(this, true)
// initialise dependency injection
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
MainActivity
class MainActivity : DaggerAppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding
private lateinit var menu: Menu
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}
Finally, after several attempts, I found my error, in AppComponent
I should call MyApplication class and not Application
#Singleton
#Component(
modules = [AndroidSupportInjectionModule::class,
AppModule::class, NetworkModule::class,
ActivityBuilder::class]
)
interface AppComponent : AndroidInjector<MyApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: MyApplication): Builder
fun build(): AppComponent
}
AppComponent:
#PerApplication
#Component(modules = arrayOf(AppModule::class))
interface AppComponent {
}
AppModule:
#Module(subcomponents = arrayOf(ActivityComponent::class))
class AppModule(private val context: Context) {
#Provides #PerApplication
fun provideContext() = context
#Provides #PerApplication
fun provideSharedPreferences(context: Context)
= context.getSharedPreferences("${context.packageName}.preferences", Context.MODE_PRIVATE)
#Provides #PerApplication
fun provideMoshi() = Moshi.Builder().build()
#Provides #PerApplication
fun provideRetrofit(moshi: Moshi): Retrofit {
return Retrofit.Builder()
.baseUrl("https://reqres.in")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.build()
}
}
ActivityComponent:
#PerActivity
#Subcomponent(modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(activity: MainActivity)
#Subcomponent.Builder
interface Builder {
fun activityModule(module: ActivityModule): Builder
fun build(): ActivityComponent
}
}
ActivityModule:
#Module
class ActivityModule(private val activity: FragmentActivity) {
#Provides #PerActivity
fun provideActivity() = activity
#Provides #PerActivity
fun provideTestApi(retrofit: Retrofit) = retrofit.create(TestApi::class.java)
}
How do I get a Builder instance of ActivityComponent into MainActivity?
Activities are created by Dagger, so I can't inject a provider into the constructor like the example given in the official docs.
I tried to use the pre dagger-2.7 way of creating subcomponents by add the following line to AppComponent:
fun activityComponent(module: ActivityModule): ActivityComponent
and in MainActivity:
(application as App).appComponent.activityComponent(ActivityModule(this)).inject(this)
which gave me:
Error:[com.example.injection.ActivityComponent.inject(com.example.modules.main.MainActivity)] com.example.api.TestApi cannot be provided without an #Provides-annotated method.