The story
I recently see many people have trouble with Google Dagger 2 library. To be honest, I think those Googlers should be carefully considering there are so many newbies like me can’t understand what their document exactly wants to explain.
- Every time, when I try to read the documents.*
That’s the reason that I decided to write a sample by using Dagger2, which we can reuse it in the future as a boilerplate without rewriting too many codes for it. Let’s start creating an Android application sample.
Create a new project
It’s pure and straightforward. Open the Android Studio
IDE and create a Kotlin
project.
Modify build.gradle
file of the App module
- add
kapt
plugin - add dagger gradle implementations
1 | // dagger implementations |
Create application-level DI graph
Let’s create an ApplicationComponent
and an application-level injector to help create the entire application DI graph first. Keep this diagram in mind. We need to think of the scope of our injection graph carefully.
I also create an Injectable
interface to describe a particular fragment that could be injected by AndroidInjector
from Dagger. We can see the detail later.
Implement HasAndroidInjector
You need to implement the HasAndroidInject
interface to let Dagger know how to correctly inject your android components included Activity,
Fragment,
and Service
these native components. We already create an application-level component in the previous section. It would make sense to implement HasAndroidInjector
in your Application:
1 | class SampleApplication: Application(), HasAndroidInjector { |
In this way, we can get rid of writing injections in every activity or fragment. You might notice why we use Any
type instead of using the Activity
type. That’s because Dagger changes the way to inject android components - check this out
. The reason to use Any
type is to make it more generic.
Are you still with me now? Ok, it seems we have everything we need at this moment. Let’s run this sample application and see what would happen. Okay, it crashed.
It doesn’t work !!
Crash logs:
1 | Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class at dagger.android.DispatchingAndroidInjector.inject(DispatchingAndroidInjector.java:136) |
The log said Dagger
can’t do injection without an injector factory.
Then, what’s the injector factory
? Well, you would need to create AndroidInjector. Factory
for each Activity to let Dagger know how to create the Activity instance. To create an injector factory,
you would need to create Subcomponent
for each Activity,
check my post about Subcomponent
- here
Subcomponent for each Activity
All activities would need to create their own Subcomponents
and Modules.
Subcomponents are the factories that create AndroidInjectors
for a concrete subtype of a core Android type such as Activity
/Fragment.
Modules are the resources that a specific Activity.class
needs. A Module can descript how to create the entry of Activity.class
and Subcomponent. Factory
to the map of injector factories used by DispachAndroidInjector.
Dagger-Android uses this entry to build an Activity Subcomponent
and perform injections for Activity.class.
MainActivityComponent.kt
1 |
|
MainActivityModule.kt
1 | ( |
A tip to the subcomponents and modules if you don’t have any other resources needed to inject into your activities. You can just create a simple module with @ContributesAndroidInjector
annotation. Here’s an example.
1 |
|
Dagger compiler would compile it into this way:
Generated code of AltMainActivityModule.kt by Dagger:
1 | ( |
You can see if we use the annotation @ContributeAndroidInjector
for our module, Dagger almost does the same thing for us. After having these two foundational parts for our MainActivity,
now we put our MainActivityModule
into ApplicationComponent.
After doing this, everything should be ok. You can see the App runs well:
Activities with Dagger support
Ok, it’s time to verify if we are still on the correct road. Let’s create a Car
model as a data model and try to inject it into MainActivity
to see if it works as what we expect. I create a Model
package and put this Car
data class inside. It primarily provides a vehicle model name. You can put any vehicle model name here for testing injection. For convenience, I create a ResourceModule
to provide the Car
resource to ApplicationComponent.
Here is the source code –
Car.kt
1 | class Car { |
ResourceModule.kt
1 | ( |
We inject the Car
into the MainActivity.
1 | class MainActivity : AppCompatActivity() { |
Successfully show the car model name
Everything is so far so good, isn’t it? The next question would be: how to handle Fragments
in the project? Let’s do it. To make it simple, I am not going to use the NavigationController by Google support library. Instead, I just create a simple Fragment
and put it in another Activity.
Our current sample App looks like this:
New Fragment introduced in the project
Let’s do the same thing we did on MainActivity
for DetailActivity
beforehand. Then, here comes a question you might be curious:
Question: Which scope does your fragment want to bind?
Question about Fragment scope
It’s not just to define modules for our activities, and you have to decide where to install modules for your Fragments. You can make your Fragment component
as a subcomponent of a Fragment component,
an Activity component,
or the Application component
- it depends on what bindings your Fragment requires. For example, your fragments might need resources from an Activity, and then this fragment’s life cycle may come with the Activity. It would make sense to bind with your ActivityComponent.
You should think about what your fragment wants to do first. In this sample, it’s more convenient that we just bind a fragment with ActivityComponent.
You might notice I create an interface called Injectable
which is an easier way for us to annotate which fragment should be injected by Dagger:
In AppInjectr.kt
We have this:
handleActivity function
1 | private fun handleActivity(activity: Activity) { |
We check if the fragment is an Injectable
object. If this Fragment implements Injectable
, we can use AndroidSupportInjection
to handle the Fragment. In this way, we don’t have to write code in a Fragment’s onAttach
function like this for each Fragment:
1 | public class FragmentSample: Fragment() { |
How does AndroidSupportInjection.inject()
work? Check the source code comments in AndroidSupportInjection.java
:
1 | Injects {if an associated { AndroidInjector} implementation can be found, otherwise throws an { IllegalArgumentException}. Uses the following algorithm to find the appropriate { AndroidInjector} to fragment} |
In our case, we’ve implemented the HasAndroidInjector
at the Application level. According to the comment, it seems that we don’t need to implement the HasAndroidInjector
in DetailActivity
because this fragment eventually finds the Application
has implemented the HasAndroidInjector.
It sounds pretty reasonable. Doesn’t it? Ok, except this, what a Fragment
needs just like what an Activity
needs, it would needs Subcomponent and module for creating its own DI graph.
DetailFragmentComponent.kt
1 |
|
DetailFragmentModule.kt
1 |
|
We want to bind this fragment within DetailActivity,
so we also need to update DetailActivityComponent
itself.
1 | @Subcomponent ( |
It looks very similar to Activity’s part. It’s time to verify if we correctly handle the Fragment Injection now. But, it shows errors.
We see this error:
1 | Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<idv.chauyan.dagger_boilerplate.ui.detail.ui.detail.DetailFragment |
It’s bizarre. How come we already implement the HasAndroidInjector
and two essentials for this DetailFragment,
but it still shows this error? If you use the breakpoint, you can see Dagger does find the SampleApplication,
which implemented the HasAndroidInjector,
but it obviously can’t find the injector factory from the map of SampleApplication.class
& ApplicationComponent.
In the DispatchAndroidInjector.java
:
1 | public boolean maybeInject(T instance) { |
Although we have HasAndroidInjector
in Application-level, we still need to implement that in Activity-level. In our case, we put the FragmentModule
in ActivityComponent,
which means Activity should take care of Fragment Injection stuff.
DetailActivity.kt
1 | class DetailActivity : AppCompatActivity(), HasAndroidInjector { |
Ok, we still need to test it a little bit to avoid any beautiful fantasy. Let’s update the Car
model and provide a car model detail string to show:
Car.kt
1 | class Car { |
Inject it into DetailFragment
:
1 | class DetailFragment : Fragment(), Injectable { |
New Fragment introduced in the project
It works very well. Ok, you can find the whole project at HERE!. I am going to dive a little bit deeper into Dynamic-Feature-Module
in the next post.
Happy coding. Enjoy.