Can't Provide Qualified String with Dagger2 - android

I'm trying to follow the sample here
I have already used Dagger2 with the AndroidInjector successfully but now I was experimenting the new DaggerApplication and DaggerAppCompatActivity.
I get the error:
Error:(5, 1) error: [dagger.android.AndroidInjector.inject(T)]
java.lang.String cannot be provided without an #Inject constructor or
from an #Provides- or #Produces-annotated method.
I don't think that the new Dagger classes are causing the issue.
If I remove the #DeviceModel in BuildModule.kt the code compiles.
Any suggestion?
Here the code:
The AppComponent.kt
#Component(modules = arrayOf(
AndroidSupportInjectionModule::class,
AppModule::class,
MainActivitySubComponent.MainActivityModule::class
))
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
}
The AppModule.kt
#Module(subcomponents = arrayOf(MainActivitySubComponent::class))
class AppModule {
}
The BuildModule.kt
#Module
class BuildModule {
#Provides
#DeviceModel
fun provideModel(): String {
return MODEL
}
}
The DeviceModel.kt
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class DeviceModel
The MainActivitySubComponent.kt
#Subcomponent(modules = arrayOf(BuildModule::class))
interface MainActivitySubComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
#Module(subcomponents = arrayOf(MainActivitySubComponent::class))
abstract class MainActivityModule {
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
internal abstract fun bind(builder: MainActivitySubComponent.Builder): AndroidInjector.Factory<out Activity>
}
}
The *MainActivity.kt**
class MainActivity : DaggerAppCompatActivity() {
#Inject
#DeviceModel
lateinit var model: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(model, model);
}
}
The App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<App> {
return DaggerAppComponent.builder().create(this)
}
}

When using Dagger with Kotlin objects, we have to consider how Kotlin actually creates properties in the JVM.
#Inject
#DeviceModel
lateinit var model: String
With a property like this, Kotlin compiles three Java elements: a private backing field, a getter, and a setter (due to var rather than val). With no further clues as to your intent, Kotlin places your DeviceModel annotation on the getter.
The solution is to specify that the field is to be annotated with #field:[annotation].
#Inject
#field:DeviceModel
lateinit var model: String
You may also need to use #JvmField to make the backing field public for Dagger, which would preclude using lateinit, and of course, require you to initialize the String.
#Inject
#field:DeviceModel
#JvmField
var model: String
Update: I'm not sure why, but in my testing, the field was private, which is why I suggested #JvmField. According to the Kotlin documentation, the field should be created with the same visibility as the setter.
Note that in general, Kotlin is smart enough to apply custom annotations with #Target(AnnotationTarget.FIELD) to the backing field, but this would prevent you from also using it on the fun providesModel

Related

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Android MVVM Dagger 2 - ViewModelFactory Injection

I am trying to perform viewmodel injection with dagger 2 in Android Kotlin project. So far my project looks like this. I have AppComponent looking like this
#AppScope
#Component(modules = [
ViewModelModule::class,
AndroidSupportInjectionModule::class,
AppModule::class,
BuildersModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application : App) : Builder
fun build() : AppComponent
}
fun inject(app: App)
}
My appModule:
#Module class AppModule
I created also Builders module for providing my Views:
#Module
abstract class BuildersModule {
#ContributesAndroidInjector
abstract fun providesMainActivity() : MainActivity
#ContributesAndroidInjector()
abstract fun providesModeMenuActivity(): ModeMenuActivity
}
My view model facotory is taken from example on github project
#AppScope
class ViewModelFactory
#Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?:
creators.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("unknown model class " + modelClass)
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
I bound factory in my ViewModelModule like this:
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(ModeMenuViewModel::class)
abstract fun bindModeMenuViewModel(modeMenuViewModel: ModeMenuViewModel): ModeMenuViewModel
#Binds
abstract fun bindsViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Now everything builds until I add ViewModelFacotory injection in one of activities like this:
class ModeMenuActivity: AppCompatActivity() {
#Inject
lateinit var vmFactory: ViewModelProvider.Factory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
val binding: ActivityModeMenuBinding = DataBindingUtil.setContentView(this, R.layout.activity_mode_menu)
val viewModel = ViewModelProviders.of(this, vmFactory).get(ModeMenuViewModel::class.java)
binding.ViewModel = viewModel
}
}
When I build code afret adding #Inject I get following error:
C:\Users\Maciej\AndroidStudioProjects\AndroidMVVM\app\build\tmp\kapt3\stubs\debug\com\example\maciej\androidmvvm\di\AppComponent.java:8: error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent {
^
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
com.example.maciej.androidmvvm.ViewModelModule.ViewModelFactory.<init>(creators)
com.example.maciej.androidmvvm.ViewModelModule.ViewModelFactory is injected at
com.example.maciej.androidmvvm.ViewModelModule.ViewModelModule.bindsViewModelFactory(factory)
android.arch.lifecycle.ViewModelProvider.Factory is injected at
com.example.maciej.androidmvvm.ui.common.ModeMenu.ModeMenuActivity.vmFactory
com.example.maciej.androidmvvm.ui.common.ModeMenu.ModeMenuActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.maciej.androidmvvm.di.AppComponent ? com.example.maciej.androidmvvm.di.BuildersModule_ProvidesModeMenuActivity.
So far I couldn't find anything at forums so I would be thankfull if you could show my what am I doing wrong. Also I noticed that when I try to attach view model to my binding object in Activity I get type missmatch (it shows incomplete package name as required type)
Your binding is a no op since you're returning the same type you're passing as argument, so change return type like this
Before:
#Binds
#IntoMap
#ViewModelKey(ModeMenuViewModel::class)
abstract fun bindModeMenuViewModel(modeMenuViewModel: ModeMenuViewModel): ModeMenuViewModel
After:
Binds
#IntoMap
#ViewModelKey(ModeMenuViewModel::class)
abstract fun bindModeMenuViewModel(modeMenuViewModel: ModeMenuViewModel): ViewModel
Finally I found solution. Dagger had problems with my packages names started from big letters. When I changed all packages names to camel case everything started to work.

Android Dagger 2.10 to 2.14.1 - Inject Custom Class

I am using Dagger 2.14.1 in my Android application and migrated recently from 2.10. The new version makes it much easier to inject Activities and Fragments, but I can't find a way to inject a custom class where I cannot change the constructor as well. With 2.10 I could write custom inject functions and then use them to simply inject any class:
Simplified Dagger 2.10 Injection with Kotlin:
#Singleton
#Component(dependencies = [], modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
fun application(): App
fun inject(authenticationActivity: AuthenticationActivity)
fun inject(converters: Converters)
// ...
}
class App : Application() {
companion object {
#JvmStatic
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
// This class is used by the Room database framwork and I cannot change the constructor and do class Converters #Inject constructor(private val gson: Gson) {
class Converters {
#Inject
protected lateinit var gson: Gson
init {
App.component.inject(this)
}
// ...
}
Simplified Dagger 2.14.1 Injection with Kotlin doesn't provide me with a ApplicationComponent Object to inject my custom classess:
#Singleton
#Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun inject(converters: Converters) // doesn't work!
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// From where do I get my component to call .inject() from another class?
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
In short: the DaggerApplicationComponent.builder().create(this) returns an AndroidInjector<App!> Object. This Object only has one .inject() functions which only accepts my App Class. So I cannot inject anything else.
Of course there is a lot missing, but I wanted to only post the relevant code. My Dagger 2.14.1 implementation works and all my dependencies in Activities, Fragments and View Models get injected.
I am quite new to Dagger and the more I use it the more I love it, but I couldn't figure out on how to inject custom classes where I cannot change the constructor. Thanks for you help!
Try this:
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
companion object {
#JvmStatic
lateinit var instance: App
}
#Inject
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
instance = this
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
Then
object Injector {
#JvmStatic fun get(): ApplicationComponent = App.instance.component
}
Now you can do
class Converters {
#Inject
protected lateinit var gson: Gson
init {
Injector.get().inject(this)
}

Dagger: A binding with matching key exists in component

I am using Dagger 2.16 and was following this article for my dagger implementation. Everything was working fine with this implementation until I had only one Activity(HomeActivity). As soon as I started implementing Dagger in SplashScreenActivity. I started getting this error. Here is some code from my project
AppComponent.kt
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ServiceBuilder::class,
BroadcastRecieverBuilder::class])
interface AppComponent : AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApp>()
}
AppModule.kt
#Module()
class AppModule {
#Provides
#Singleton
fun provideContext(application: MyApp): Context {
return application
}
#Provides
#Singleton
fun provideRestService(retrofit: Retrofit): RestService {
return retrofit.create(RestService::class.java)
}
...
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [HomeActivityModule::class])
#PerActivity
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [SplashScreenModule::class])
#PerActivity
abstract fun bindSplashActivity(): SplashScreenActivity
}
BaseActivity.kt
abstract class BaseActivity<V : BaseView, P : MvpBasePresenter<V>> :
MvpActivity<V, P>(), BaseView, HasSupportFragmentInjector {
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var mPresenter: P
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun createPresenter(): P = mPresenter
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentInjector
}
}
I have my own BaseActivity instead of DaggerActivity because I what to inherit from mosby's MvpActivity.
SplashScreenModule.kt
#Module
abstract class SplashScreenModule {
#Binds
#PerActivity
internal abstract fun splashPresenter(splashPresenter: SplashScreenPresenter): BasePresenter<*>
}
HomeActivityModule.kt
#Module
abstract class HomeActivityModule {
#Binds
#PerActivity
internal abstract fun homePresenter(homePresenter: HomeActivityPresenter): BasePresenter<*>
#ContributesAndroidInjector(modules = [DownloadFragmentModule::class])
#PerFragment
internal abstract fun downloadsFragment(): DownloadsFragment
}
Now when I build this, I get an error as follows
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.realtime.app.MyApp> {
^
A binding with matching key exists in component: com.realtime.dagger.ActivityBuilder_BindHomeActivity.HomeActivitySubcomponent
java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> is injected at
dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
dagger.android.DispatchingAndroidInjector<android.support.v4.app.Fragment> is injected at
com.realtime.core.BaseActivity.fragmentInjector
com.realtime.splashScreen.SplashScreenActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.realtime.dagger.AppComponent → com.realtime.dagger.ActivityBuilder_BindSplashActivity.SplashScreenActivitySubcomponent
I have gone through other similar que like this but couldn't relate it to what I am facing. What am I missing?
Update: For now I am not inheriting BaseActivity in SplashScreenActivity so that I can avoid injecting fragmentInjector: DispatchingAndroidInjector<Fragment>. It is working for now as I don't have any fragment in SplashScreenActivity.
It works for HomeActivity because it binds a fragment:
#ContributesAndroidInjector
fun downloadsFragment(): DownloadsFragment
SplashScreenActivity does not.
AndroidInjection uses DispatchingAndroidInjector to handle runtime injections, which basically contains a Map of classes to their component builders. This map needs to be injected like everything else. In the case of HomeActivity the fragment declaration in the module generates a binding for the map, which can then be injected.
Since there is no Fragment on the splash activity Dagger does not know about any bindings, let alone any map. Which is why it complains that it cannot be provided.
You can read more here about multibindings.
To prevent this from happening, you should register AndroidInjectionModule on your AppComponent, which just contains the declarations for the empty maps.
While it contains the declaration for android.app.Fragment it does not for android.support.v4.app.Fragment, which is where the error comes from.
So to fix this specific error you should add AndroidSupportInjectionModule to your component, which also includes the support bindings, providing an empty map when there are no fragments in an activity.
#Component(modules = [AndroidSupportInjectionModule::class, /* ... */])
interface AppComponent { /* ... */ }

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

Categories

Resources