I would like to try Hilt DI in the android library.
It is a dependency on another project, with its own submodule. The very first problem I've encountered is the requirement of marking Application with #HiltAndroidApp. Now I do not have anything that extends Application in my library ofc but would like to utilize Hilt and its predefined components.
Is it possible or should I go with Dagger only in such a case? I've found a solution for Dagger, where library dependency injection is made totally independently (the client is unaware of the library's DI): Dagger solution, would love to hear any opinion on that, maybe someone already put a great effort into that issue and can share his insights.
If you're trying to include Hilt in an android library, then you should expect the android app (client of your library) to mark its Application with #HiltAndroidApp.
You should include your whole setup (entry points, modules, dependencies, ... whatever you want to have in your library) in the library module, and make the requirement for the client of the library to use the #HiltAndroidApp to use your library correctly.
You don't need to include #HiltAndroidApp in library module to inject the dependencies in library modules to app module or any dynamic feature modules.
This sample has only core library module, app, and dynamic feature modules. Dynamic feature module implementation is optional.
Result of injecting from core library module to App's Activity and Fragment is as
Project dependency Structure
feature_hilt_camera feature_hilt_photos (Dynamic Feature Modules)
| | |
| ----App----
| |
core(android-library)
In core library module have a dagger module as
#InstallIn(ApplicationComponent::class)
#Module
class CoreModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#Provides
fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
#Provides
fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()
#Provides
fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()
#Provides
fun provideAnotherDependency() = AnotherDependency()
}
And inject to Activity as
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
/**
* Injected from [CoreModule] with #Singleton scope
*/
#Inject
lateinit var coreDependency: CoreDependency
/**
* Injected from [CoreModule] with no scope
*/
#Inject
lateinit var coreActivityDependency: CoreActivityDependency
/**
* Injected from [MainActivityModule] with no scope
*/
#Inject
lateinit var toastMaker: ToastMaker
/**
*
* Injected from [MainActivityModule] with #ActivityScoped
* * To inject this there should be #Binds that gets Context from an Application
*/
#Inject
lateinit var mainActivityObject: MainActivityObject
/**
* Injected via constructor injection with no scope
*/
#Inject
lateinit var sensorController: SensorController
/**
* Injected via constructor injection with #Singleton scope
*
* ### Unlike Tutorial 9-2 This can be injected because MainActivity's component does not
* depend on any component with another scope
*/
#Inject
lateinit var singletonObject: SingletonObject
#Inject
lateinit var anotherDependency: AnotherDependency
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.tvInfo).text =
"CoreModule #Singleton coreDependency: ${coreDependency.hashCode()}\n" +
"CoreModule no scope coreActivityDependency: ${coreActivityDependency.hashCode()}\n" +
"CoreModule no scope anotherDependency: ${anotherDependency.hashCode()}\n" +
"MainActivityModule #ActivityScoped mainActivityObject: ${mainActivityObject.hashCode()}\n" +
"MainActivityModule no scope toastMaker: ${toastMaker.hashCode()}\n" +
"Constructor no scope sensorController: ${sensorController.hashCode()}\n"
"Constructor #Singleton singletonObject: ${singletonObject.hashCode()}"
}
}
and it's same for HomeFragment which is in app module
#AndroidEntryPoint
class HomeFragment : Fragment() {
/**
* Injected from [CoreModule] with #Singleton scope
*/
#Inject
lateinit var coreDependency: CoreDependency
/**
* Injected from [CoreModule] with no scope
*/
#Inject
lateinit var coreActivityDependency: CoreActivityDependency
#Inject
lateinit var homeFragmentObject: HomeFragmentObject
/**
* This dependency cannot be injected since this fragment's component does not depend on CoreComponent
* unlike Tutorial 9-2 counterpart
*/
#Inject
lateinit var mainActivityObject: MainActivityObject
#Inject
lateinit var fragmentObject: FragmentObject
}
If you also wish to inject to dynamic feature modules you need a provision module in your library module as
/**
* This component is required for adding component to DFM dependencies
*/
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface CoreModuleDependencies {
/*
🔥 Provision methods to provide dependencies to components that depend on this component
*/
fun coreDependency(): CoreDependency
fun coreActivityDependency(): CoreActivityDependency
fun coreCameraDependency(): CoreCameraDependency
fun corePhotoDependency(): CorePhotoDependency
}
and dynamic feature module you will use this interface as dependent component
In camera dynamic feature module have a component like this
#Component(
dependencies = [CoreModuleDependencies::class],
modules = [CameraModule::class]
)
interface CameraComponent {
fun inject(cameraFragment1: CameraFragment1)
fun inject(cameraFragment2: CameraFragment2)
fun inject(cameraActivity: CameraActivity)
#Component.Factory
interface Factory {
fun create(coreComponentDependencies: CoreModuleDependencies,
#BindsInstance application: Application): CameraComponent
}
}
and inject it to your dynamic feature fragment with
private fun initCoreDependentInjection() {
val coreModuleDependencies = EntryPointAccessors.fromApplication(
requireActivity().applicationContext,
CoreModuleDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreModuleDependencies,
requireActivity().application
)
.inject(this)
}
Full sample that in image is here, and you check out implementation for both libraries and dynamic feature modules in this sample project.
It's possible to integrate Hilt into your library, but you will have to handle the case where the app is not a Hilt application. You can handle this case by annotating your Activity/Fragment with #OptionalInject and then checking OptionalInjectCheck#wasInjectedByHilt() to check if the Activity/Fragment was injected by Hilt or not.
#OptionalInject
#AndroidEntryPoint
public final class MyFragment extends Fragment {
...
#Override public void onAttach(Activity activity) {
super.onAttach(activity); // Injection will happen here, but only if the Activity used Hilt
if (!OptionalInjectCheck.wasInjectedByHilt(this)) {
// Get Dagger components the previous way and inject manually
}
}
}
Note that doing this will not make your library simpler (it'll actually get more complex since you need to support both Hilt and non-Hilt applications). The main benefit would be to your clients that use Hilt, since they wouldn't need to do any component/module setup to get your library up and running in their app.
Related
I'm new to dagger and hilt, I'm trying to use Hilt to get my adapter injected to my fragment using a builder (the fragment is actually in a fragment pager adapter so there's multiple instance of it and the adapter), it seems set up correctly, I build the component before super in onCreate but the adapter is null when trying to access it later in the fragment, I wonder if maybe I need to provide it somehow later on so for instance I have my adapter in my fragment
#AndroidEntryPoint
public class CardHolderFragment extends Fragment {
public CardAdapter cardAdapter;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
DaggerCardAdapterComponent.builder()
.cardAdapterDependencies(() -> layoutIdentifier)
.build().inject(this);
super.onCreate(savedInstanceState);
cardAdapter.notifyDataSetChanged(); // CardAdapter.notifyDataSetChanged()' on a null object reference
so my question is if the component is setup correctly should this work or am I missing a step?
What I feel like doing is something like cardAdapter = DaggerCardAdapterComponent.getCardAdapter but I can't put a provides methods in a component, I had a look through the dagger migration guide here which is a little complicated for me as I only had a few weeks with dagger before switching to hilt, but the guide mentions
Finally, you may have to redesign some things if they were configured differently for different activity or fragment components. For example, you could use a new interface on the activity to provide the object.
so I tried this which makes me implement the interface and then return a card adapter but I wasn't sure what I should be returning here, any help welcome
heres my card adapter component
#Component(dependencies = CardAdapterDependencies.class, modules = {TypeFactoryModule.class,
ItemTouchListenerModule.class, GlideModule.class})
public interface CardAdapterComponent {
void inject(CardHolderFragment cardHolderFragment);
#Component.Builder
interface Builder {
Builder cardAdapterDependencies(CardAdapterDependencies cardAdapterDependencies);
CardAdapterComponent build();
}
interface HasCardAdapter{
CardAdapter getCardAdapter();
}
}
and heres my card adapter constructor
#Inject
public CardAdapter(
ItemTouchListener onItemTouchListener,
String layoutIdentifier,
TypeFactory typeFactory,
RequestManager glide
) {
this.onItemTouchListener = onItemTouchListener;
this.typeFactory = typeFactory;
this.layoutIdentifier = layoutIdentifier;
this.glide = glide;
this.elements = new ArrayList<>();
}
First of all you don't need to use #AndroidEntryPoint if you are building your dependent component, even in my case at dynamic feature modules using #AndroidEntryPoint caused it not to compile.
Edit: In your situation building a dependent component or building component with builder does not look necessary. If so, you can use the answer below will help, if you don't need dependent component I will add another edit to do it simple way.
You should use EntryPointAccessors.fromApplication(), EntryPointAccessors.fromActivity() or EntryPointAccessors.fromFragment() to create an interface that has provision methods with #InstallIn and #EntryPoint annotations. I explained here how to build with dependent components and dynamic feature modules using Hilt and you can find the full sample in this git repo.
First create a module with dependencies you provide:
#Module
#InstallIn(ApplicationComponent::class)
class CoreModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#Provides
fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
}
You can change ApplicationComponent to ActivityComponent or FragmentComponent depending on your structure.
Then build a dependencies interface with #EntryPoint and #InstallIn annotations and provision methods which is how you provide the dependencies to dependent components.
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface CoreDependencies {
/*
* Provision methods to provide dependencies to components that
* depend on this component
*/
fun coreDependency(): CoreDependency
fun coreActivityDependency(): CoreActivityDependency
}
Now build the component you should create, it's CardAdapterComponent for you
#Component(
dependencies = [CoreDependencies::class],
modules = [CameraModule::class]
)
interface CameraComponent {
fun inject(cameraFragment1: CameraFragment1)
fun inject(cameraFragment2: CameraFragment2)
fun inject(cameraActivity: CameraActivity)
#Component.Factory
interface Factory {
fun create(coreDependencies: CoreDependencies, #BindsInstance application: Application): CameraComponent
}
}
I used factory pattern, you can use builder if you wish, both the same.
To inject your dependent component to Activity you need to get component using EntryPoints.
private fun initHiltDependencyInjection() {
val coreComponentDependencies = EntryPointAccessors.fromApplication(
applicationContext,
CoreDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreComponentDependencies,
application
)
.inject(this)
}
To inject to the Fragment:
private fun initCoreDependentInjection() {
val coreComponentDependencies = EntryPointAccessors.fromApplication(
requireActivity().applicationContext,
CoreDependencies::class.java
)
DaggerCameraComponent.factory().create(
coreComponentDependencies,
requireActivity().application
)
.inject(this)
}
I am working on android library module and I want to test the standalone activity in my module. I was following the article https://medium.com/androiddevelopers/write-once-run-everywhere-tests-on-android-88adb2ba20c5 to use roboelectric and androidx test with espresso. I recently introduced dagger 2 to my library project.
With that my Activity looks like this:
class XYZLibBaseActivity : AppCompatActivity(){
#Inject
lateinit var resourceProvider: ResourceProvider
override fun onCreate(savedInstanceState: Bundle?) {
//creating the dagger component
DaggerXYZLibComponent.factory().create(application).inject(this)
super.onCreate(savedInstanceState)
}
}
My component declaration is
#Component(modules = [ResourceProviderModule::class])
interface XYZLibComponent{
#Component.Factory
interface Factory{
fun create(#BindsInstance application: Application):XYZLibComponent
}
fun inject(xyzLibBaseActivity: XYZLibBaseActivity)
}
and dagger module is
#Module
class ResourceProviderModule {
#Provides
fun provideResourceProvider(application: Application): ResourceProvider{
return ResourceProviderImpl(application.applicationContext)
}
}
This works perfectly fine and I don't want the underlying application to use dagger 2.
Now I wan to test my activity without depending on the underlying application or application class. How can I inject mock ResourceProvider in the activity?
One of many options is
create 2 flavors in your gradle config: real and mock
in both flavors, define a boolean buildConfigField flag
In your provideResourceProvider, return a corresponding implementation based on the flag's value
I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.
My top level component looks like
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
val activityComponentBuilder: ActivityComponent.Builder
}
#Module(subcomponents = [ActivityComponent::class])
interface AppModule {
#Binds
fun mockWebService(mockWebService: MockWebService): MockWebService
}
Next my subcomponent looks like
#ActivityComponent
#Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(sharedViewModel: SharedViewModel)
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun storage(storage: Storage): Builder
fun build(): ActivityComponent
}
}
In my activity module I bind two objects
#Binds
abstract fun controller(controller: Controller): Controller
#Binds
abstract fun navigator(navigator: Navigator): Navigator
Each object has an #Inject constructor
class Navigator #Inject constructor(private val storage: Storage)
class Controller #Inject constructor(
private val webService: MockWebService,
private val navigator: Navigator,
private val storage: Storage
) {
Inside my shared view model I try to build my component and inject the fields
#Inject
lateinit var navigator: Navigator
#Inject
lateinit var controller: Controller
init {
MainApplication.component.activityComponentBuilder
.storage(InMemoryStorage.from(UUID.randomUUID().toString()))
.build()
.inject(this)
}
But dagger won't build. I get an error
[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {
MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at
But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?
#Binds is used to let dagger know the different implementations of an interface. You don't need #Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have #Inject constructor, which means dagger can instantiate them and we don't need to write extra #Provides functions for those classes.
#Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add #XScope with #Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.
As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).
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
How do you use dagger from Kotlin?
I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1
Here is all I need:
AppDependencies
GenericActivityDependencies
PerActivityDependency
Here are my main dependencies:
App
#Module
class ApplicationModule(private val application: Application) {
#Provides
#Singleton
fun provideContext(): Application = this.application
}
#Singleton
#Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
val app: Application
}
Why do I need to once provide the dependency in the module and another time define it in the component?
Activity Module
#Module
class ActivityModule(private val activity: Activity) {
#PerActivity
#Provides
#ActivityContext
fun provideContext(): Context = activity
}
#Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
#ActivityContext
interface ActivityComponent {
fun inject(activity: MainActivity)
}
HomeModule
#Module
class LandingModule {
#PerActivity
#Provides
fun provideSomethig(): Something {
return Something()
}
}
#SomeActivity
#Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
val something: Something
}
By this point, I have written more code than there needs to be in my whole activity.
I get errors like can't inherit from a scopes component
Can't generate Dagger gencode
Subcomponent needs a different scope
How do I achieve this?
Is there a better di for kotlin?
Is there a sample somewhere I could follow that has per activity module?
I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context
}
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun provideApplication(): Application= application
}
The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,
Why the heck do I need to once provide the dependency in the module
and another time define it in the component. This is plain stupid.
is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
You are not exposing anything here but you can still get the activity context from the container thru inject.
Now let's proceed to scoping.
Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:
#PerActivity
#Component(dependencies = [AppComponent::class],
modules = [ActivityModule::class])
interface ActivityComponent{
fun inject(activity: MainActivity)
}
The corresponding module should only be scoped with PerActivity as well.
class ActivityModule(activity:Activity) {
#PerActivity
#Provides
fun provideActivity() = activity
}
Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.
As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.
I have written a small guide to learning dagger 2. If you are interested, you can go check it out.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.
I agree, but don't be discouraged by that fact, because once you learn to master it you'll learn to appreciate it and really take advantage. I've been using version 2.2 for a while without any issues. I've only needed to define one annotation, add a pair of additional dependencies (AutoDagger among them, which takes care of that component feature), and use the following structure:
/* Dagger */
implementation "com.google.dagger:dagger:2.2"
// Fix: github.com/rharter/auto-value-gson/issues/43#issuecomment-219994018
kapt 'com.squareup:javapoet:1.9.0'
kapt "com.google.dagger:dagger-compiler:2.2"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
/* Autodagger */
kapt "com.github.lukaspili.autodagger2:autodagger2-compiler:1.1"
implementation "com.github.lukaspili.autodagger2:autodagger2:1.1"
DaggerScope.java
#Retention(RetentionPolicy.RUNTIME)
#Scope
#interface DaggerScope {
}
YourApp.kt
#AutoComponent(modules = [YourApp.Module::class])
#AutoInjector
#DaggerScope
class YourApp : Application() {
...
#Singleton #dagger.Module
inner class Module(private val app : YourApp) {
#Provides #AutoExpose(YourApp::class) fun application(): Application = app
#Provides #AutoExpose(YourApp::class) fun context(): Context = app
...
// Stuff like your database or base service can go here
}
}
SomeActivity.kt
#AutoComponent(dependencies = [YourApp::class],
modules = [SomeActivity.Module::class]) // you are free to add other modules here
#AutoInjector
#DaggerScope
class SomeActivity : AppCompatActivity() {
...
#dagger.Module
inner class Module() {
#Provides #AutoExpose(SomeActivity::class) fun something(): Something {
return some way of creating Something
}
/* specific deps for SomeAcitivity's actions, like specific services.
You can also access DAOs as you've got access to the DB */
}
}
You can also mimic this structure with a Fragment instead of an Activity.
Hope this helps you!