Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to @Inject an abstract BaseActivity and its subclasses using dagger.android?

I'm using the new dagger.android package from Dagger 2 to inject Android dependencies in my project.

  • I need all my Activities to be a subclass of an abstract BaseActivity
  • In my BaseActivity I have member variables to be injected. This way:

    abstract class BaseActivity : AppCompatActivity() {
        @Inject
        lateinit var prefs: MyPreferenceDataStore
        ...// more @Injected members
    }
    
  • I do it because I want subclasses of BaseActiviy can have access to injected members of BaseActivity:

    class SubClassActivity : BaseActivity() {
        override fun onCreate(savedInstanceState: Bundle) {                
            val x = prefs.getXXX //use prefs variable from parent class
        }
    }
    
  • This is my ApplicationComponent:

    @Singleton @Component(modules = arrayOf(
                    ApplicationModule::class,
                    ActivityBindingModule::class,
                    AndroidSupportInjectionModule::class
                ))
    interface ApplicationComponent {
        @Component.Builder interface Builder {
            @BindsInstance
            fun application(application: Application): Builder
            fun build(): ApplicationComponent
        }
        fun inject(app: AndroidApplication)
    }
    
  • The ApplicationModule class has simple @Provides annotated methods:

    @Module
    class ApplicationModule {
        @Singleton @Provides
        fun providesMyPreferenceDataStore(context: Context): MyPreferenceDataStore {
            return MyPreferenceDataStoreImpl(context)
        }
        // more @Provides annotated methods
    }
    

    I think the problem is in my ActivityBindingModule

    @Module
    abstract class ActivityBindingModule {
    
        @PerActivity
        @ContributesAndroidInjector(
            modules = arrayOf(BaseActivityModule::class
        ))
        abstract fun bindBaseActivity(): BaseActivity
    
        @PerActivity
        @ContributesAndroidInjector(
            modules = arrayOf(
                BaseActivityModule::class
        ))
        abstract fun bindSubClassActivity(): SubClassActivity
    }
    

This is what I have tried so far:

  • Make the bindSubClassActivity() method not to depend of BaseActivityModule::class, didn't work.

  • Move the providesMyPreferenceDataStore from ApplicationModule to the BaseActivityModule, so that the class is:

    @Module
    class BaseActivityModule {
        @PerActivity @Provides
        fun providesMyPreferenceDataStore(context: Context): MyPreferenceDataStore {
            return MyPreferenceDataStoreImpl(context)
        }
    }
    

And this is the error I'm getting:

Error: [dagger.android.AndroidInjector.inject(T)] com.example.BaseActivity cannot
be provided without an @Provides-annotated method.
This type supports members injection but cannot
be implicitly provided.
like image 261
Jorge E. Hernández Avatar asked Jan 30 '18 13:01

Jorge E. Hernández


People also ask

Can we inject in abstract class?

First, an abstract class isn't component-scanned since it can't be instantiated without a concrete subclass. Second, setter injection is possible in an abstract class, but it's risky if we don't use the final keyword for the setter method. The application may not be stable if a subclass overrides the setter method.

How do you inject a dagger in fragment?

onCreate() , an activity attaches fragments that might want to access activity bindings. When using fragments, inject Dagger in the fragment's onAttach() method. In this case, it can be done before or after calling super. onAttach() .

What is constructor injection in dagger?

inject. Inject annotation to identify which constructors and fields it is interested in. Use @Inject to annotate the constructor that Dagger should use to create instances of a class. When a new instance is requested, Dagger will obtain the required parameters values and invoke this constructor.


1 Answers

I didn't understand exactly what you try to do but this solutions based in what i understand

AppComponent should look like this

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    AppModule::class,
    ActivityModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }
}

your base activity which will inject all the objects

abstract class BaseActivity : DaggerAppCompatActivity() {

    @Inject
    lateinit var prefs: SharedPreferences

    //other objects to inject

}

The activity that will inherit from it eg:MainActivity

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        prefs.getBoolean("s", true)
    }

}

And Activity module

@Module
abstract class ActivityModule {

    @ContributesAndroidInjector
    abstract fun bindMainActivity(): MainActivity

    @ContributesAndroidInjector
    abstract fun bindBaseActivity():BaseActivity

}

AppModule

@Module
class AppModule {

    @Singleton
    @Provides
    fun providesMyPreferenceDataStore(application: Application): SharedPreferences {
        return application.getSharedPreferences("test", Context.MODE_PRIVATE)
    }

}
like image 97
Ahmed Abdelmeged Avatar answered Oct 04 '22 21:10

Ahmed Abdelmeged