Dividir um aplicativo Android monolítico em módulos não é novo, e essa forma de organizar o código está se tornando mais comum. Já tocamos neste tema na reunião dedicada às melhores práticas de trabalho com módulos entre colegas. Coletamos essa experiência, testamos em nosso projeto e queremos compartilhar as conclusões e conselhos a que chegamos. Portanto, este artigo pode ser útil tanto para quem está pensando na divisão, quanto para quem já começou.
Os desenvolvedores geralmente pensam em usar a multimodularidade para acelerar os tempos de construção. Mas isso não era o mais importante para nós. Além da velocidade de construção, a multimodularidade também oferece uma arquitetura mais rígida e a capacidade de reutilizar recursos entre projetos.
, . , , . Gradle - , Buck Bazel. , 300 .
. . - Android Wear. , .
, Java, , internal
. : api
+ impl
. , . .
, , Dagger, . , , . Kotlin, — , .
, , .
-, . , . . , AppComponent
, ( KAPT) . , — , Gradle Android Gradle Plugin, , .
-, . . . , . Kotlin Multiplatform . , .
-, . . , , . , .
( , ) — . Maven-. , .
Git- . - .
: , , , .
:
App- — , Feature-.
Feature- — , , -. , , - (, UI- , , UI). Feature- API Feature- Core-.
Feature- API , API . internal , API, «» Feature-. , .API
Impl
, , .
.
Core- — , , Feature-. , . Core- . : module-injector.
Module-injector — , . , . .
— API
Impl
Feature-, Feature- . internal
- Kotlin.
Example- , App-. ( ) , . .
:
Module-Injector
, . , . , , . , , Dagger ( DI-). , :
interface ComponentHolder<C : BaseAPI, D : BaseDependencies> {
fun init(dependencies: D)
fun get(): C
fun reset()
}
interface BaseDependencies
interface BaseAPI
. Feature- . ( , ) internal, .
- , , , Kotlin ( 2020-) module-injector
. . .
, , — . : , . UI, , — .
Feature- , - , Core-. , :
, API Feature-. Feature-: :feature_purchase_api
:feature_purchase_impl
. API- , :module-injector
. API-.
, . , .
ComponentHolder
-:
object PurchaseComponentHolder : ComponentHolder<PurchaseFeatureApi, PurchaseFeatureDependencies> {
private var purchaseComponentHolder: PurchaseComponent? = null
override fun init(dependencies: PurchaseFeatureDependencies) {
if (purchaseComponentHolder == null) {
synchronized(PurchaseComponentHolder::class.java) {
if (purchaseComponentHolder == null) {
purchaseComponentHolder = PurchaseComponent.initAndGet(dependencies)
}
}
}
}
override fun get(): PurchaseFeatureApi {
checkNotNull(purchaseComponentHolder) { "PurchaseComponent was not initialized!" }
return purchaseComponentHolder!!
}
override fun reset() {
purchaseComponentHolder = null
}
}
:
- .
-
init()
, . -
get()
, API . -
reset()
, , .
. , .
PurchaseFeatureApi PurchaseFeatureDependencies , .
ComponentHolder
Dagger-:
@Component(dependencies = [PurchaseFeatureDependencies::class], modules = [PurchaseModule::class])
@PerFeature
internal abstract class PurchaseComponent : PurchaseFeatureApi {
companion object {
fun initAndGet(purchaseFeatureDependencies: PurchaseFeatureDependencies): PurchaseComponent {
return DaggerPurchaseComponent.builder()
.purchaseFeatureDependencies(purchaseFeatureDependencies)
.build()
}
}
}
initAndGet()
, ComponentHolder
. , Dagger . DI- .
, app :
@Module
class AppModule {
@Singleton
@Provides
fun provideScannerFeatureDependencies(featurePurchase: PurchaseFeatureApi): ScannerFeatureDependencies {
return object : ScannerFeatureDependencies {
override fun dbClient(): DbClient = CoreDbComponent.get().dbClient()
override fun httpClient(): HttpClient = CoreNetworkComponent.get().httpClient()
override fun someUtils(): SomeUtils = CoreUtilsComponent.get().someUtils()
override fun purchaseInteractor(): PurchaseInteractor = featurePurchase.purchaseInteractor()
}
}
// -
@Provides
fun provideFeatureScanner(dependencies: ScannerFeatureDependencies): ScannerFeatureApi {
ScannerFeatureComponentHolder.init(dependencies)
return ScannerFeatureComponentHolder.get()
}
...
}
provideScannerFeatureDependencies()
ScannerFeatureDependencies
, provideFeatureScanner()
ComponentHolder
- .
, app
- . , . app
- , . app
- .
, .
, ComponentHolder
reset()
, . UI, reset()
Lifecycle Observer
- ( Activity, ):
public override fun onPause() {
super.onPause()
...
if (isFinishing) {
AntitheftFeatureComponentHolder.reset()
}
}
, , . get()
.
// get() Feature-:
object PurchaseComponentHolder : ComponentHolder<PurchaseFeatureApi, PurchaseFeatureDependencies> {
private var purchaseComponentHolder: PurchaseComponent? = null
...
override fun get(): PurchaseFeatureApi {
checkNotNull(purchaseComponentHolder) { "PurchaseComponent was not initialized!" }
return purchaseComponentHolder!!
}
override fun reset() {
purchaseComponentHolder = null
}
// get() app-:
// @Singleton Provider, , get() Dagger-
@Provides
fun provideFeatureScanner(dependencies: ScannerFeatureDependencies): ScannerFeatureApi {
ScannerFeatureComponentHolder.init(dependencies)
return ScannerFeatureComponentHolder.get()
}
}
, DI- app- (, Singleton
Dagger). init()
, , reset()
.
API- — Provider<T>
:
class GlobalNavigator @Inject constructor(
// Provider get()
private val featureScanner: Provider<ScannerFeatureApi>,
private val featureAntitheft: Provider<AntitheftFeatureApi>,
private val context: Context
) : Navigator {
...
featureScanner.get().scannerStarter().start(context) //
...
}
UI
, API , . API , UI-, Activity, , View.
Activity API :
interface AntitheftStarter {
fun start(context: Context)
}
Activity , Intent:
internal class AntitheftStarterImpl @Inject constructor() : AntitheftStarter {
override fun start(context: Context) {
val intent = Intent(context, AntitheftActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
FragmentManager , API , . : Cicerone, Navigation Component FragmentManager.
, , . , .
Core-ui
UI- , , UI- . UIKit. , Application.
, theme.xml
, (, Example-, , ). core-ui
, , , . UIKit (api
implementation
) Feature-, UI. .
Core-strings
, , .
, , , : . . , . .
Core-native
C++-. JNI-, Java-. , SDK. , , ABI
. , .