Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 815
Jonathan Avatar asked Dec 14 '22 19:12

Jonathan


1 Answers

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

like image 163
aleksandrbel Avatar answered Dec 17 '22 23:12

aleksandrbel