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
}
Related
I try to call API with Retrofit and Dagger 2 and in my case the DataModel and it's module are injected fine, but not the module of API. I'm sure I miss something, but do not understand what. Here is my Component:
#Singleton
#Component(modules = [SMModule::class, SMNetworkModule::class])
interface SMComponent {
fun inject(fragment: SMListFragment)
}
Modules:
#Module
class SMModule {
#Provides
fun provideSMModel(): SMListDataModel {
return SMListDataModel()
}
}
#Module
object SMNetworkModule {
#JvmStatic
#Singleton
#Provides
fun provideRetrofit(): Retrofit {
val okHttpBuilder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpBuilder.addInterceptor(httpLoggingInterceptor)
}
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpBuilder.build())
.build()
}
#JvmStatic
#Singleton
#Provides
fun provideSMInterface(retrofit: Retrofit): SMInterface =
retrofit.create(SMInterface::class.java)
}
DataModel:
class SMListDataModel #Inject constructor() {
#Inject
lateinit var smApi: SMAPI
override fun loadData(): DataResponse {
return smApi.getData().blockingGet()
}
}
my API:
#Singleton
class SMAPI #Inject constructor() {
#Inject
lateinit var smInterface: SMInterface
fun getData(): Single<DataResponse> {
return smInterface.getData()
}
}
and here I inject my Fragment and call DataModel:
#JvmField
#Inject
var dataModel: StocksListDataModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerSMComponent.create().inject(this)
}
override fun onResume() {
super.onResume()
val response = dataModel?.loadData()
}
and I get an error:
UninitializedPropertyAccessException: lateinit property smApi has not been initialized
inside of my SMListDataModel.
I looked at this question and there I didn't see the explicit constructor for the API and it works for them. But not for me.
I use this and it works well. (provide Room & Retrofit).
i hope it's be useful
RemoteModule class :
#Module(includes = [AppModule::class])
class RemoteModule {
#Provides
#Singleton
fun provideArticleDatabase(application: App): RoomDataBase? {
return Room.databaseBuilder(
application.applicationContext,
RoomDataBase::class.java,
"fandogh"
)
.build()
}
#Provides
fun provideArticleDao(#Nullable articleDatabase: RoomDataBase): ILocalDataSource? {
return articleDatabase.roomDao()
}
#Provides
#ApplicationScope
fun getApiInterface(retroFit: Retrofit): ApiService {
return retroFit.create(ApiService::class.java)
}
#Provides
#ApplicationScope
fun getRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
}
#Provides
#ApplicationScope
fun getOkHttpCleint(httpLoggingInterceptor: HttpLoggingInterceptor, myAuthHeaderInterceptor: MyAuthHeaderInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.callTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
// .addInterceptor {
// var request = it.request().newBuilder().addHeader("authorization", Constants.AUTHORIZATION_HEADER).build()
// return#addInterceptor it.proceed(request)
// }
.addInterceptor(myAuthHeaderInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()
}
#Provides
#ApplicationScope
fun getHttpLoggingInterceptor(): HttpLoggingInterceptor {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return httpLoggingInterceptor
}
#Singleton
#Provides
fun getAuthHeaderInterceptor(sharedPrefrences: SharedPreferences): MyAuthHeaderInterceptor {
return MyAuthHeaderInterceptor(sharedPrefrences)
}
}
ViewModelModule :
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory // this needs to be only one for whole app (therefore marked as `#Singleton`)
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun loginViewModel(viewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(ActMainViewModel::class)
abstract fun mainActivityViewModel(viewModel: ActMainViewModel): ViewModel
}
FragmentBuilder:
#Module
abstract class FragmentBuilder {
#FragmentScope
#ContributesAndroidInjector
abstract fun homeFragment(): HomeFragment?
}
ActivityBuilder :
#Module
abstract class ActivityBuilder {
#ActivityScope
#ContributesAndroidInjector
abstract fun loginActivity(): ActivityLogin?
#ActivityScope
#ContributesAndroidInjector
abstract fun mainActivity(): MainActivity?
}
AppModule :
#Module
abstract class AppModule {
#Binds
#Singleton
abstract fun application(app: App?): Application?
}
ActivityScope :
#Scope
#Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope
FragmentScope :
#Scope
#Retention(value = AnnotationRetention.RUNTIME)
annotation class FragmentScope {
}
AppComponent :
#Singleton
#ApplicationScope
#Component(
modules = [AppModule::class, AndroidSupportInjectionModule::class, RemoteModule::class, ActivityBuilder::class,
FragmentBuilder::class, ViewModelModule::class]
)
interface AppComponent : AndroidInjector<App> {
fun inject(membersFragment: MembersFragment)
override fun inject(myApplication: App)
fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: App): Builder
fun build(): AppComponent
}
}
Application :
class App : DaggerApplication() {
var appComponent: AppComponent? = null
override fun applicationInjector(): AppComponent {
appComponent = DaggerAppComponent.builder().application(this).build()
appComponent!!.inject(this)
return appComponent!!
}
}
and used in ViewModel :
class LoanViewModel #Inject constructor() : ViewModel() {
#Inject
lateinit var apiService: ApiService
}
I've just refactored my dagger code to make it scalable and move all core stuff to a separate module called di.
Now when I try to inject my dependencies in the app module I got this :
[Dagger/MissingBinding] retrofit2.Retrofit cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface ResetPasswordComponent {
^
retrofit2.Retrofit is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordNetworkModule.providerResetPasswordAPI(retrofit)
com.sahra.oms.ibshop.data.remote.service.ResetPasswordService is injected at
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl(resetPasswordService)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindUniqueIdRepository(uniqueRepositoryImpl)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueIdRepository is injected at
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel(repository)
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindCheckIdViewModel(checkUniqueIDViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sahra.oms.ibishop.di.util.ViewModelFactory(viewModelsMap)
com.sahra.oms.ibishop.di.util.ViewModelFactory is injected at
com.sahra.oms.ibshop.di.ViewModelBuilder.bindViewModelFactory(arg0)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment.viewModelFactory
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordComponent.inject(com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment)
ResetPasswordService is just a Retrofit interface.
Here is my code:
AppComponent
#Singleton
#AppScope
#Component
interface AppComponent {
fun provideContextComponent(): ContextComponent
fun provideNetworkComponent(): NetworkComponent
fun provideSharedPrefComponent(): SharedPreferencesComponent
fun inject(app: Application)
#Component.Factory
interface Factory {
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
}
}
NetworkComponent :
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class NetworkScope
#NetworkScope
#Component(
dependencies = [ContextComponent::class],
modules = [OkHttpModule::class, AuthBinderModule::class]
)
interface NetworkComponent {
fun provideOkHttp(): OkHttpClient
fun provideRetrofit(): Retrofit
fun provideGson(): GsonConverterFactory
}
OkHttpModule :
#Module
object OkHttpModule {
private const val BASE_URL = "base_url"
#Provides
#JvmStatic
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message -> Log.d("<<<network>>>", message) }).apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
}
}
#Provides
#JvmStatic
fun provideChuckInterceptor(app: Application): ChuckInterceptor = ChuckInterceptor(app)
#Provides
#JvmStatic
fun provideOkhttpCache(app: Application): Cache =
Cache(app.cacheDir, 50_000_000)
#Provides
#NetworkScope
#JvmStatic
fun provideClient(
loggingInterceptor: HttpLoggingInterceptor,
chuckInterceptor: ChuckInterceptor,
authInterceptor: Interceptor,
cache: Cache
): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.addInterceptor(chuckInterceptor)
.build()
}
#Provides
#NetworkScope
#JvmStatic
fun provideGson() = Gson()
#Provides
#JvmStatic
fun provideGsonConverter(gson: Gson) = GsonConverterFactory.create(gson)
#Provides
#NetworkScope
#JvmStatic
fun provideRetrofit(
gsonConverterFactory: GsonConverterFactory,
client: Lazy<OkHttpClient>
): Retrofit = Retrofit.Builder()
.callFactory { request -> client.get().newCall(request) }
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)
.build()
}
Here is how I try to inject the dependencies:
#FeatureScope
#Component(
dependencies = [AppComponent::class],
modules = [
ResetPasswordNetworkModule::class,
ResetPasswordModule::class,
ViewModelBuilder::class
]
)
interface ResetPasswordComponent {
fun inject(newPasswordFragment: NewPasswordFragment)
fun inject(checkUniqueIDFragment: CheckUniqueIDFragment)
#Component.Builder
interface Builder {
fun coreComponent(appComponent: AppComponent): Builder
fun build(): ResetPasswordComponent
}
}
#Module
abstract class ResetPasswordModule {
#Binds
abstract fun bindResetPasswordRepository(resetPasswordRepositoryImpl: ResetPasswordRepositoryImpl): ResetPasswordRepository
#Binds
abstract fun bindUniqueIdRepository(uniqueRepositoryImpl: UniqueRepositoryImpl): UniqueIdRepository
#Binds
#IntoMap
#ViewModelKey(CheckUniqueIDViewModel::class)
abstract fun bindCheckIdViewModel(checkUniqueIDViewModel: CheckUniqueIDViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(NewPasswordViewModel::class)
abstract fun bindNewPasswordViewModel(newPasswordViewModel: NewPasswordViewModel): ViewModel
}
#Module
object ResetPasswordNetworkModule {
#Provides
#JvmStatic
#FeatureScope
fun provideUserAPI(
retrofit: Retrofit
): ResetPasswordService = retrofit.create(ResetPasswordService::class.java)
}
and here is my repository code:
class ResetPasswordRepositoryImpl #Inject constructor(
private val resetPasswordService: ResetPasswordService
) : ResetPasswordRepository {
}
Fragment :
class NewPasswordFragment{
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
}
class NewPasswordViewModel #Inject constructor(
private val repository: ResetPasswordRepository
)
Thanks in advance.
Your app component factory looks like this:
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
This provides access to an instance of NetworkComponent, so any #Provides, #Binds, or #Inject that requires a NetworkComponent can get one. It does not, however, give direct access to NetworkComponent's object graph.
NetworkComponent already exposes Retrofit, so you can certainly get one if you have access to the component. However, this process is not automatic, and in your setting requires a #Provides method.
#Provides
fun provideRetrofit(component: NetworkComponent): Retrofit = component.provideRetrofit()
This is more convoluted that it needs to be. A better way to accomplish this is to make NetworkComponent a dependency of AppComponent (or just use its modules and drop the network component entirely), then expose Retrofit in AppComponent.
// using multiple scoped dependencies requires Dagger 2.27
#AppScope
#Component(dependencies = [ContextComponent::class, NetworkComponent::class, SharedPreferencesComponent::class])
interface AppComponent {
fun provideRetrofit(): Retrofit
// ...
}
I've started using Dagger 2 with Android Injector and faced an issue
I have 5 modules, that are composed into one component
#Singleton
#Component(modules = [AndroidInjectionModule::class,
ActivityBuilder::class,
AndroidSupportInjectionModule::class
, ApplicationModule::class,NetworkModule::class])
interface ApplicationComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
fun inject(app: DeliveryApplication)
fun service(): DeliveryApi
}
Network Module look like
#Module
class NetworkModule {
#Singleton
#Provides
fun providesDeliveryApi(retrofit: Retrofit):DeliveryApi = retrofit.create(DeliveryApi::class.java)
#Singleton
#Provides
fun providesRetrofit(okHttpClient: OkHttpClient):Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
#Singleton
#Provides
fun providesOkHttpClient(context:Context): OkHttpClient {
/* val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}*/
val cacheSize = (5 * 1024 * 1024).toLong()
val myCache = Cache(context.cacheDir, cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.addInterceptor { chain ->
var request = chain.request()
request = if (hasNetwork(context)!!)
// setting 5 Mb Cabacity
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()
return okHttpClient
}
#Singleton
#Provides
fun provideImageLoader(context: Context) : ImageLoader {
return PicassoImageLoader(Picasso.with(context))
}
application Module look like
const val SCHEDULER_MAIN_THREAD = "mainThread"
const val SCHEDULER_IO = "io"
#Module
class ApplicationModule {
#Provides
#Named(SCHEDULER_MAIN_THREAD)
fun provideAndroidMainThreadScheduler() : Scheduler = AndroidSchedulers.mainThread()
#Provides
#Named(SCHEDULER_IO)
fun provideIoScheduler() : Scheduler = Schedulers.io()
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
}
ActivityBuilder is pretty simple: just combination of modules for Activities
and FragmentsModule for fragments
#Module
abstract class ActivityBuilder {
#PerActivity
#ContributesAndroidInjector(modules = [FragmentsModule::class, DeliveriesModule::class])
abstract fun bindMainActivity(): DeliveriesActivity
#PerActivity
#ContributesAndroidInjector(modules = [FragmentsModule::class, DeliveriesDetailsModule::class])
abstract fun bindDeliveryDetailsActivity(): DeliveryDetailsActivity
}
#Module
abstract class FragmentsModule {
#ContributesAndroidInjector
abstract fun provideDeliveriesListFragment(): DeliveriesListFragment
#ContributesAndroidInjector
abstract fun provideDeliveryDetailsFragment(): DeliveryDetailsFragment
}
Delivery Module which used to provide all instances of the DeliveryActivity
#Module
class DeliveriesModule {
#Provides
#PerActivity
fun providesDeliveryRepository(deliveryApi: DeliveryApi): DeliveryRepository {
return DeliveryDownloader(deliveryApi)
}
#Provides
#PerActivity
fun providesDeliveryListUseCases(deliveryRepository: DeliveryRepository): DeliveryListUseCase {
return DeliveryListInteractor(deliveryRepository)
}
#Provides
#PerActivity
fun provideDeliveriesListFragment(): DeliveriesListFragment {
return DeliveriesListFragment()
}
}
ViewModelFactory where the problem here, the compiler says that DeliveryListUseCase cannot be provided without an #Provides-annotated method
class ViewModelFactory #Inject constructor(private val application: Application,
#Named(SCHEDULER_IO) val subscribeOnScheduler: Scheduler,
#Named(SCHEDULER_MAIN_THREAD) val observeOnScheduler: Scheduler,
private val deliveryListUseCase: DeliveryListUseCase) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when {
modelClass.isAssignableFrom(DeliveryListViewModel::class.java) ->
DeliveryListViewModel(application,deliveryListUseCase,subscribeOnScheduler,observeOnScheduler) as T
else -> BaseViewModel(application) as T
}
}
}
Seems like I've figured out what was wrong
the problem. the DeliveriesModule which is a module of DeliveriesActivity so i have to make another Module for DeliveriesListFragment and provide the DeliveryListUseCase
Thank you, #Blackbelt, for giving me a hint
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.
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();
}