Desenvolvedor, olá!
Neste artigo, quero compartilhar um exemplo de um aplicativo android modular usando NavComponent (JetPack) e Koin (DI).
Temos muitos projetos Android diferentes em nossa empresa que devem usar os recursos uns dos outros - esse é um tipo de ecossistema. Para conseguir isso, precisamos desenvolver esses recursos da forma mais independente e flexível possível.
Os requisitos para recursos podem ser formulados da seguinte forma:
Um recurso deve ser capaz de substituir a lógica e a IU independentemente uma da outra.
Para chamar um recurso, deve ser suficiente conhecer sua interface (rótulo) e os parâmetros de entrada / saída necessários.
O próprio recurso deve ocultar sua implementação de outros recursos.
Suporte para recurso on-off fora da caixa.
Para mim, um recurso é um sistema logicamente combinado de componentes que executa alguma função em um aplicativo. Por exemplo, autorização, compra. Um recurso contém seu próprio gráfico de IU (uma cadeia de fragmentos), que deve ser armazenado na backStack de navegação e, após sair do recurso, retornar o usuário ao ponto de chamada do recurso.
Um esquema condicional de um aplicativo que consiste em dois recursos:
O diagrama mostra um aplicativo que possui um MainFragment (M) e dois recursos (A e B). De M você pode ir para os recursos A e B. E do recurso B podemos chegar ao recurso A. Além disso, após a conclusão do recurso A, devemos retornar ao ponto de chamada: se obtivermos o recurso A de M, então voltamos para M, e se chamado de recurso A de B, então para B. Nesse caso, a pilha de navegação deve ser preservada. A1, A2, B1, B2 - fragmentos de recursos.
: API, BL, UI.
. A -> B - .
API , . Lint : API .
BL - . API . internal - , DI , . Lint : BL APP .
UI . internal - , DI , NavGraph, nested graph. Lint : UI APP .
:
(app)
app DI
startKoin { modules( listOf( AppKoinModule.create(), FeatureAImplKoinModule.create(), FeatureAUiKoinModule.create(), FeatureBImplKoinModule.create(), FeatureBUiKoinModule.create() ) ) }
, , app DI, , . C UI - , root app .
root (app). , , .
:
appNavigator.navigateTo(FeatureADestination::class.java)
( )
interface FeatureADestination : ModuleNavInfo
ModuleNavInfo , . FeatureADestination UI .
interface ModuleNavInfo {
fun getNavigationStartPointResId(): Int
fun isFeatureAvailable(): Boolean
}
navigator, . , DI ModuleNavInfo:
class KoinAppNavigator : AppNavigator {
private val navigationDestinationInternal = MutableLiveEvent<EventArgs<ModuleNavInfo>>()
override val navigationDestination =
navigationDestinationInternal as LiveData<EventArgs<ModuleNavInfo>>
private val navigationIntDestinationInternal = MutableLiveEvent<EventArgs<Int>>()
override val navigationResDestination: LiveData<EventArgs<Int>>
get() = navigationIntDestinationInternal
override fun <T : ModuleNavInfo> navigateTo(
moduleNavInfo: Class<T>
) {
val destination = KoinJavaComponent.get(moduleNavInfo) as ModuleNavInfo
navigationDestinationInternal.postValue(EventArgs(destination))
}
override fun navigateTo(destination: Int) {
navigationIntDestinationInternal.postValue(EventArgs(destination))
}
override fun <T : ModuleNavInfo> resolveModule(moduleNavInfo: Class<T>): ModuleNavInfo? {
return try {
KoinJavaComponent.get(moduleNavInfo)
} catch (e: Exception) {
null
}
}
override fun <T : ModuleNavInfo> isCanNavigateTo(moduleNavInfo: Class<T>): Boolean {
return resolveModule(moduleNavInfo) != null
}
}
ModuleNavInfo e AppNavigator podem ser estendidos, aqui mostrei o exemplo mais simples. Por exemplo, você definitivamente precisa passar parâmetros e retornar um resultado. Isso também pode ser feito por meio do ModuleNavInfo. Por exemplo, para retornar o resultado de um recurso, você pode adicionar StateFlow e assiná-lo. Tudo acontece de forma concisa e em um só lugar. Você também pode configurar a interação usando serviços internos que podem ser ocultados do mundo externo nos módulos BL.
O código de exemplo está disponível no github .
Obrigado pela atenção!