Professional Documents
Culture Documents
● Clear separation
● Put more thoughts in development
● Less entangled codebase
Practical Aspect
● Ownership
● Easy to reuse
● Isolated build
Feature Dependency Structure
Feature Dependency Structure
Requires
registration
Self
maintained
Application Feature
Input
Problem?
Problem?
@ApplicationScope
@Component(
modules = arrayOf(
NetworkModule::class,
FirebaseModule::class,
MapProviderModule::class,
OnlyFeature1RelatedModule::class,
ThenFeature2Module::class,
GlobalApiModule::class,
// ...
)
)
interface ApplicationComponent
Problem?
@ApplicationScope
@Component(
modules = arrayOf(
NetworkModule::class,
FirebaseModule::class,
MapProviderModule::class,
OnlyFeature1RelatedModule::class,
ThenFeature2Module::class,
GlobalApiModule::class,
// ...
)
)
interface ApplicationComponent
Solution?
@ApplicationScope
@Component(
modules = arrayOf(
NetworkModule::class,
FirebaseModule::class,
MapProviderModule::class,
),
dynamicModuleKey = arrayOf(
"Feature1",
"Feature2"
)
)
interface ApplicationComponent
Solution?
Dagger Extensions
Why Anvil and Hilt?
What do they bring to the table?
Base
Anvil
@ContributesTo(ApplicationScope::class)
@Module
class BaseModule {
@Provides
@ElementsIntoSet
fun provideFeatureSet(): Set<Feature> = emptySet()
@Provides
@ElementsIntoSet
fun provideApplicationPluginSet(): Set<ApplicationPlugin> = emptySet()
} Base
Anvil
@Scope
@MustBeDocumented
@Retention
annotation class Feature1Scope
@ContributesTo(ApplicationScope::class)
interface Feature1Dependencies {
fun sampleSharedLogger(): SampleSharedLogger
fun depsWithAppLifecycle(): DepsWithAppLifecycle
}
Feature 1
Anvil
@Feature1Scope
@MergeComponent(
scope = Feature1Scope::class,
dependencies = [Feature1Dependencies::class]
)
interface Feature1Component
Feature 1
Anvil
@ContributesTo(ApplicationScope::class)
@Module
class Feature1ModuleToApplication {
// ...
Feature 1
Anvil
@ContributesTo(ApplicationScope::class)
@Module
class Feature1ModuleToApplication {
@Provides
@IntoSet
fun provideFeature1(): Feature = Feature(
"Feature 1",
"feature1"
)
}
Feature 1
Anvil
@ContributesTo(ApplicationScope::class)
@Module
class Feature1ModuleToApplication {
@Provides
@IntoSet
fun provideFeature1ApplicationPlugin(): ApplicationPlugin =
Feature1ApplicationPlugin()
Feature 1
Anvil
@ContributesTo(ApplicationScope::class)
@Module
class Feature1ModuleToApplication {
@Provides
@ApplicationScope
fun provideDepsWithAppLifecycle(application: Application): DepsWithAppLifecycle =
DepsWithAppLifecycle(application)
Feature 1
Anvil
@ApplicationScope
@MergeComponent(scope = ApplicationScope::class)
interface ApplicationComponent
Application
Anvil your_module/build/tmp/kapt3/stubs/...
@Component(
modules = {
Feature1ModuleToApplication.class,
Feature2ModuleToApplication.class,
BaseModule.class
}, dependencies = {})
@ApplicationScope
public abstract interface ApplicationComponent
extends Feature1Dependencies, Feature2Dependencies {
Generated
Anvil
class SampleApplication : Application() {
// ...
Application
Anvil
class SampleApplication : Application() {
Application
Anvil
class SampleApplication : Application() {
@Inject
lateinit var applicationPlugins: Set<@JvmSuppressWildcards ApplicationPlugin>
applicationComponent.value.inject(this)
applicationPlugins.forEach { it.apply(this) }
}
Application
}
Anvil
class SampleApplication : Application(), FeatureDependencyProvider {
@Suppress("UNCHECKED_CAST")
override fun <T> dependencies(): T {
return applicationComponent.value as? T
?: throw IllegalStateException(
"Feature does not provide"
+ " its dependencies to the ApplicationScope.")
}
}
Application
Hilt
Hilt
Feature1
Module to contribute ApplicationComponent
Base
Define shared abstractions
Hilt
interface ApplicationPlugin {
fun apply(application: Application)
}
Base
Hilt
@InstallIn(ApplicationComponent::class)
@Module
class BaseModule {
@Provides
@ElementsIntoSet
fun provideFeatureSet(): Set<Feature> = emptySet()
@Provides
@ElementsIntoSet
fun provideApplicationPluginSet(): Set<ApplicationPlugin> = emptySet()
} Base
Hilt
@InstallIn(ApplicationComponent::class)
@Module
class Feature1ModuleToApplication {
@Provides
@IntoSet
fun provideFeature1(): Feature = Feature(
"Feature 1",
"feature1"
)
}
Feature 1
Hilt
@InstallIn(ApplicationComponent::class)
@Module
class Feature1ModuleToApplication {
@Provides
@IntoSet
fun provideFeature1ApplicationPlugin(): ApplicationPlugin =
Feature1ApplicationPlugin()
Feature 1
Hilt
@InstallIn(ApplicationComponent::class)
@Module
class Feature1ModuleToApplication {
@Provides
@Singleton
fun provideDepsWithAppLifecycle(application: Application): DepsWithAppLifecycle =
DepsWithAppLifecycle(application)
Feature 1
Hilt
@HiltAndroidApp
class SampleApplication : Application() {
@Inject
lateinit var applicationPlugins: Set<@JvmSuppressWildcards ApplicationPlugin>
applicationPlugins.forEach { it.apply(this) }
}
} Application
A new
perspective
for Dagger
by the
Extensions
Thank you!
● “Modularise in Structure” - 2019
https://bit.ly/3nBbslG
● “Building Features by Independent Dagger Components” - 2018
https://bit.ly/36TsXba
● About the ApplicationPlugin implementation
https://bit.ly/3129aCx
● Github repo for the sample application
https://github.com/yayaa/IndependentFeatures
@yahyabayramoglu