0% found this document useful (0 votes)
91 views85 pages

Effective DI With Multi-Modular Project

This document discusses an effective approach to dependency injection (DI) with a multi-modular architecture. It recommends initially moving all code and resources into a core module, then identifying features that can be separated into independent modules. Related code and resources for each feature would be moved into separate feature modules. This modular structure allows for faster build times, better encapsulation and separation of concerns, and access to modularized delivery of features. The document emphasizes that modules should only have dependencies in one direction to avoid circular dependencies.

Uploaded by

kdjklsah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
91 views85 pages

Effective DI With Multi-Modular Project

This document discusses an effective approach to dependency injection (DI) with a multi-modular architecture. It recommends initially moving all code and resources into a core module, then identifying features that can be separated into independent modules. Related code and resources for each feature would be moved into separate feature modules. This modular structure allows for faster build times, better encapsulation and separation of concerns, and access to modularized delivery of features. The document emphasizes that modules should only have dependencies in one direction to avoid circular dependencies.

Uploaded by

kdjklsah
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Effective DI with

Multi-Modular Architecture
Adit Lal
@aditlal

Gojek is a Super App


We have 18+ products from food tech to fin-tech
to hyper local delivery and massage services

$6.3B
GoPay

16.5m KM
Ride Sharing
Solve DI with
Multi-Modular codebase
Dependency Graphs
Monolith

app

common auth

Expensive
Object core
What could possibly go wrong?
Modularise?
First rule
You don’t need to modularise
If you don’t have to
Feature modules

Feature A Feature B

app

Expensive
Object core
Thoughts on modularisation

• Separation of concerns
• Build performance
• Dynamic features ?
Thoughts on modularisation

Feature module
• Owned by single team ? Set of team members
• Encapsulates single feature
• Single entry point
• Smaller is better
• Cannot depend on other feature modules
Thoughts on modularisation

App module
• No feature-specific code
• No infrastructure-specific code.
• Creates the dagger component
How?

• First , move all code and resource into a core module


• Identify features that can be in independent module.
• Move related code and its resources to these feature modules.
Thoughts on modularisation

• First , move all code and resource into a core module


• Identify features that can be in independent module.
• Move related code and its resources to these feature modules.

Vertical dependencies should only have one direction.


Ownership?

app

Feature n-2 Feature n-1 Feature n Feature n+1 Feature n+2 Feature z
… … … … …

common auth

core
Ownership?

app

Feature n-2 Feature n-1 Feature n Feature n+1 Feature n+2 Feature z
… … … … …

common auth

core
Ownership?
IoC Principle

Inversion
of control

Dependency
Inversion

Dependency
Injection
IoC Principle

• Declare Components that can be reused.


• High level modules should not depend on low level modules
• Build abstractions so any implementation can be easily changed with
new one.
• Dependencies can be injected into components.
Should I do Modularise to use Dagger?
Don’t do it for Dagger , do it for yourself
Dagger and modular code?

• Faster build time ( with & w/o dagger)


• Better separation and encapsulation of your code.
• Access to Modularised Delivery.
• Time spent today to modularise is time you are saving tomorrow.
Scoping w/o Modules
App
Scoping w/o Modules
App

Expensive
Object
MainComponent

MainModule
Scoping w/o Modules
App

Feature 1

SubF1Component
dep
end
s

F1Module
Expensive
depends Object
MainComponent

MainModule
Scoping
App Feature 2

SubF2Component
Feature 1

SubF1Component F2Module
dep
end
s

F1Module en ds
d s dep

de
p en
Expensive
depends Object
MainComponent

MainModule
Circular … what … no
Playing Jenga
Scoping
App
Feature 1
MainComponent depen
ds

SubF1Component

s
p end
de

Core
Expensive
Object
Scoping - KISS (keep it simple stupid)
App
Feature 1
MainComponent

F1Component

ds
depends p en
de

Core Feature 2
Expensive
CoreComponent
Object depends

F2Component
Vertical dependencies should only have one direction.
Disclaimer
Core

@Module
class CoreModule {
@Provides
@Singleton
fun provideExpObj(): ExpensiveObject = ExpensiveObject()

}
Core

@Module
class CoreModule {
@Provides
@Singleton
fun provideExpObj(): ExpensiveObject = ExpensiveObject()

}
Core
@Component(modules = [CoreModule::class])
@Singleton
interface CoreComponent {
@[Link] interface Builder {
fun build(): CoreComponent
}
fun provideExpensiveObject(): ExpensiveObject
}
Core
@Component(modules = [CoreModule::class])
@Singleton
interface CoreComponent {
@[Link] interface Builder {
fun build(): CoreComponent
}
fun provideExpensiveObject(): ExpensiveObject
}
Core
@Component(modules = [CoreModule::class])
@Singleton
interface CoreComponent {
@[Link] interface Builder {
fun build(): CoreComponent
}
fun provideExpensiveObject(): ExpensiveObject
}
Core
@Component(modules = [CoreModule::class])
@Singleton
interface CoreComponent {
@[Link] interface Builder {
fun build(): CoreComponent
}
fun provideExpensiveObject(): ExpensiveObject
}
class OurMainApplication : Application() {
private val coreComponent: CoreComponent by lazy {
DaggerCoreComponent
.builder()
.build()
}
companion object {
@JvmStatic fun coreComponent(context: Context) =
([Link] as
OurMainApplication).coreComponent
}
}
class OurMainApplication : Application() {
private val coreComponent: CoreComponent by lazy {
DaggerCoreComponent
.builder()
.build()
}
companion object {
@JvmStatic fun coreComponent(context: Context) =
([Link] as
OurMainApplication).coreComponent
}
}
class OurMainApplication : Application() {
private val coreComponent: CoreComponent by lazy {
DaggerCoreComponent
.builder()
.build()
}
companion object {
@JvmStatic fun coreComponent(context: Context) =
([Link] as
OurMainApplication).coreComponent
}
}
Extension function

fun [Link]() =
[Link](this)
Feature 1

@Module
class Feature1Module {
@Provides
fun provideString() = "test"
}
Feature 1
@Component(modules = [Feature1Module::class],
dependencies = [CoreComponent::class])
interface Feature1Component {
@[Link]
interface Builder {
fun coreComponent(coreComponent: CoreComponent): Builder
}

fun inject(activity: OtherActivity)


}
Feature 1
@Component(modules = [Feature1Module::class],
dependencies = [CoreComponent::class])
interface Feature1Component {
@[Link]
interface Builder {
fun coreComponent(coreComponent: CoreComponent): Builder
}

fun inject(activity: OtherActivity)


}
Feature 1
val coreComponent =
[Link](context)

DaggerFeature1Component
.builder()
.coreComponent(coreComponent)
.build()
.inject(this)
Feature 2

@Module
class Feature2Module {
@Provides
fun provideInt() = 1
}
Feature 2
@Component(modules = [Feature2Module::class],
dependencies = [CoreComponent::class])
interface Feature2Component {
@[Link]
interface Builder {
fun coreComponent(coreComponent: CoreComponent): Builder
}

fun inject(activity: MainActivity)


}
When dealing with component dependencies we must follow 2 simple rules:

• An un-scoped component cannot depend on scoped components.


• A scoped component cannot depend on a component with the same scope.
@Scope
@Retention
annotation class FeatureScope
@Component(modules = [Feature1Module::class],
dependencies = [CoreComponent::class])
@FeatureScope
interface Feature1Component {
@[Link]
interface Builder {
fun coreComponent(coreComponent: CoreComponent): Builder
}

fun inject(activity: OtherActivity)


}
@Component(modules = [Feature2Module::class],
dependencies = [CoreComponent::class])
@FeatureScope
interface Feature2Component {
@[Link]
interface Builder {
fun coreComponent(coreComponent: CoreComponent): Builder
}

fun inject(activity: MainActivity)


}
Plan
interface CoreComponentProvider {
fun provideCoreComponent(): CoreComponent
}
object CoreInjectHelper {

fun provideCoreComponent(applicationContext: Context): CoreComponent{


return if (applicationContext is CoreComponentProvider) {
(applicationContext as CoreComponentProvider).provideCoreComponent()
} else {
throw IllegalStateException(
"The context passed does not implement CoreComponentProvider"
)
}
}
}
object CoreInjectHelper {

fun provideCoreComponent(applicationContext: Context): CoreComponent{


return if (applicationContext is CoreComponentProvider) {
(applicationContext as CoreComponentProvider).provideCoreComponent()
} else {
throw IllegalStateException(
"The context passed does not implement CoreComponentProvider"
)
}
}
}
object CoreInjectHelper {

fun provideCoreComponent(applicationContext: Context): CoreComponent{


return if (applicationContext is CoreComponentProvider) {
(applicationContext as CoreComponentProvider).provideCoreComponent()
} else {
throw IllegalStateException(
"The context passed does not implement CoreComponentProvider"
)
}
}
}
class OurMainApplication : Application(), CoreComponentProvider {

private lateinit var coreComponent: CoreComponent

override fun provideCoreComponent(): CoreComponent {


if (!this::[Link]) {
coreComponent = DaggerCoreComponent
.builder()
.build()
}
return coreComponent
}
}
class OurMainApplication : Application(), CoreComponentProvider {

private lateinit var coreComponent: CoreComponent

override fun provideCoreComponent(): CoreComponent {


if (!this::[Link]) {
coreComponent = DaggerCoreComponent
.builder()
.build()
}
return coreComponent
}
}
class OurMainApplication : Application(), CoreComponentProvider {

private lateinit var coreComponent: CoreComponent

override fun provideCoreComponent(): CoreComponent {


if (!this::[Link]) {
coreComponent = DaggerCoreComponent
.builder()
.build()
}
return coreComponent
}
}
class MainActivity : AppCompatActivity() {

@Inject
lateinit var expensiveObject: ExpensiveObject

override fun onCreate(savedInstanceState: Bundle?) {


...
DaggerFeature2Component
.builder()
.coreComponent([Link]())
.build()
.inject(this)
}
}
class OtherActivity : AppCompatActivity() {

@Inject
lateinit var expensiveObject: ExpensiveObject

override fun onCreate(savedInstanceState: Bundle?) {


...
DaggerFeature1Component
.builder()
.coreComponent([Link]())
.build()
.inject(this)
}
}
[Link]
@Scope
annotation class ActivityScope
@Module(includes = [
AndroidSupportInjectionModule::class
])
abstract class AppModule {
@ActivityScope @ContributesAndroidInjector()
abstract fun contributesMainActivityInjector(): MainActivity

@ActivityScope @ContributesAndroidInjector()
abstract fun contributesOtherActivityInjector():
OtherActivity
}
@Module(includes = [
AndroidSupportInjectionModule::class
])
abstract class AppModule {
@ActivityScope @ContributesAndroidInjector()
abstract fun contributesMainActivityInjector(): MainActivity

@ActivityScope @ContributesAndroidInjector()
abstract fun contributesOtherActivityInjector():
OtherActivity
}
@Module(includes = [
AndroidSupportInjectionModule::class
])
abstract class AppModule {
@ActivityScope @ContributesAndroidInjector()
abstract fun contributesMainActivityInjector(): MainActivity

@ActivityScope @ContributesAndroidInjector()
abstract fun contributesOtherActivityInjector():
OtherActivity
}
class OurMainApplication :
Application(),
CoreComponentProvider,
HasActivityInjector {
@Inject lateinit var dispatchingActivityInjector:
DispatchingAndroidInjector<Activity>
private lateinit var coreComponent: CoreComponent

override fun onCreate() {


[Link]()
[Link]().inject(this)
}

override fun activityInjector(): AndroidInjector<Activity> =


dispatchingActivityInjector
}
class OurMainApplication :
Application(),
CoreComponentProvider,
HasActivityInjector {
@Inject lateinit var dispatchingActivityInjector:
DispatchingAndroidInjector<Activity>
private lateinit var coreComponent: CoreComponent

override fun onCreate() {


[Link]()
[Link]().inject(this)
}

override fun activityInjector(): AndroidInjector<Activity> =


dispatchingActivityInjector
}
class OurMainApplication :
Application(),
CoreComponentProvider,
HasActivityInjector {
@Inject lateinit var dispatchingActivityInjector:
DispatchingAndroidInjector<Activity>
private lateinit var coreComponent: CoreComponent

override fun onCreate() {


[Link]()
[Link]().inject(this)
}

override fun activityInjector(): AndroidInjector<Activity> =


dispatchingActivityInjector
}
class OurMainApplication :
Application(),
CoreComponentProvider,
HasActivityInjector {
@Inject lateinit var dispatchingActivityInjector:
DispatchingAndroidInjector<Activity>
private lateinit var coreComponent: CoreComponent

override fun onCreate() {


[Link]()
[Link]().inject(this)
}

override fun activityInjector(): AndroidInjector<Activity> =


dispatchingActivityInjector
}
class MainActivity : AppCompatActivity() {

@Inject
lateinit var expensiveObject: ExpensiveObject

override fun onCreate(savedInstanceState: Bundle?) {


[Link](this)
...
}
}
class MainActivity : AppCompatActivity() {

@Inject
lateinit var expensiveObject: ExpensiveObject

override fun onCreate(savedInstanceState: Bundle?) {


[Link](this)
...
}
}
[Link](this)

this

Activity Fragment Service Content Broadcast


Provider Receiver
Tip

@Inject
lateinit var expensiveObject: Lazy<ExpensiveObject>

[Link]()
Tip

• Always go for static provide methods.


• Nobody cares about name of your Scope
• Expose the application context through a component builder instead
of a module with constructor argument. Or even better: use the
new factory!
Tip

@Module
class Feature1Module {
@Provides
fun provideString() = "test"
}
Tip

@Module
abstract class Feature1Module {
@Binds
abstract fun provideString()
}
Tip
@Provides

Component
@Binds

Module
Factory Component

Create a inject object

Module

Create a inject object


Tip

@Binds
@IntoMap
@ViewModelKey(X1::class)
abstract bindUserViewModel(x1 : ObjX) : ObjX
Tl;Dr

• Provide dependencies through class constructors


• Avoid unnecessary scoping - Use scope annotations sparingly and
judiciously
Tl;Dr

• Use reusable components for expensive and immutable


dependencies
• Use @Binds instead of a @Provides method when simply delegating
one type to another.
• Write tests , switch dependencies on tests with a test component
instead of overriding modules.
Resources

• Using Dagger in multi-module by Marcos Holgado


• Dependency injection in a multi module project - Plaid - Ben Weiss
• Modularising Android Application by Marvin Ramin
Thats all folks!
Adit Lal
@aditlal

[Link]

You might also like