Story
I recently encountered an issue that I need to separate the App module (Android project) into several library modules to fulfill the requirements of the Dynamic Feature Module
in the future. In the beginning, I thought that should be an easy job, how hard it can be?
How hard it can be?
We need to move files to different library modules and solve the import path errors, change the implementation path in build.gradle
file for different library modules. Then, it’s done easy.
So easy huh?
Too young too dumb
Unfortunately, if we need to use some 3rd-party libraries like Dagger
to help us, the DI graph would be straightforward if we only have one App Module.
If we only have one module, then we only need to create one component for this App Module.
Once you move files to different library modules or dynamic feature modules (DFMs), there are two main problems you need to solve in this case:
- How do you create different components to let different modules provide resources for consumers?
- How do you offer different data for an injected object in a module at runtime?
I think the first issue would be much easier to solve. You have several weapons for this issue. In Dagger2, you can use either Component Dependency
or Subcomponent
to solve it.
What’s the difference between these two ways? Let’s see the scenario first.
1 | class Father { |
We have two classes Father
and Son,
and these two classes have the same injected object Car
because Father
owns the car and Son
can borrow this car from his father. What the components look like if we use Dependency
?
Dependency Way for Dagger Components
In the FatherComponent,
we need to give it a customized scope, FatherScope,
to differentiate from the SonComponent.
1 |
|
In the SonComponent
:
1 |
|
You can see theSonComponent
sets up a component dependency by including the FatherComponent
; and FatherComponent
exposes his car from CarModule
via the function getCar()
. By using component dependency, all classes which inject into SonComponent
can use any resources exposed from FatherComponent.
But, there are two things you need to know about Component Dependency
first:
You need to use different
Scope
for various components, cause they have different life cycles.Child components can only use exposed resources from parent components.
SubComponent Way for Dagger Components
What about the Subcomponent
way? Let’s see the code example first.
FatherComponent
1 |
|
SonComponent
1 |
|
There’re a few changes in these two components. First, you need to create a builder for SonComponent
as well, which’s purpose is to create a SonComponent
instance. And in the FatherComponent
, we need to do two things:
- Let modules in
FatherComponent
know who the child component is. You will need to add thesubcomponents
keyword. - Declare the
SonComponent
builder here to create your ownSonComponent.
The way you use it would be like this:
1 | val fatherComponent = DaggerFaComponent.builder() |
The example shows how we are going to solve the first question. Now, it’s time to go on the second question, how do we change module provides dynamically? Let’s use a configuration data class to solve this.
Dynamically change the content for the injected object
What’s the purpose of using the different content for an injected object at runtime? The possible use cases are running different environments when you are testing, especially when you have several debug modules with various base URLs running on different test servers for the Retrofit
library. You would agree that creating different Retrofit instances is tedious. Let’s do only one Retrofit instance and apply for different base URLs.
First, create your own Configuration
data class:
1 | class Configuration (mode: Mode) { |
It’s a pure configuration class, and we use it to know what current mode is. So, how can we use it?
Inject it
Let’s use it as a resource provider in components.
1 |
|
We can directly put it into the application-level module and provide it to the entire App when launching the App. Now you can inject this Configuration
into where you want to use in this App. How do we give different URLs based on different mode when we are using Retrofit? In your module, you need to do something like this:
1 | // In one of your modules: |
We can create only one Retrofit
with an OkHttpClient.
Then, according to our configuration setting, we give it different URLs. Let me dive a little bit deeper into it. We can use Interceptor
to change the URL we want to use based on the currentMode
value in the Configuration
class.
1 | /** |
URLInterceptor
1 | class URLInterceptor (private val url: String?): Interceptor { |
Here we replace the old URL with a new one. Now we are all set. Let’s see how to use it:
1 | class MainActivity: AppCompatActivity() { |
And now, we can dynamically change the runtime object content via this configuration object. Happy coding, enjoy.