Un exemple d'application Android modulaire utilisant le composant Navigation et Koin (DI)

Développeur, bonjour!





Dans cet article, je souhaite partager un exemple d'application Android modulaire utilisant NavComponent (JetPack) et Koin (DI).





Nous avons de nombreux projets Android différents dans notre entreprise qui devraient utiliser les fonctionnalités de chacun - c'est une sorte d'écosystème. Pour y parvenir, nous devons développer ces fonctionnalités de manière aussi indépendante et flexible que possible.





Les exigences relatives aux fonctionnalités peuvent être formulées comme suit:





  1. Une fonctionnalité doit pouvoir remplacer la logique et l'interface utilisateur indépendamment l'une de l'autre.





  2. Pour appeler une fonction, il devrait suffire de connaître son interface (étiquette) et les paramètres d'entrée / sortie nécessaires.





  3. La fonctionnalité elle-même doit cacher son implémentation aux autres fonctionnalités.





  4. Prise en charge de la fonction marche-arrêt prête à l'emploi.





Pour moi, une fonctionnalité est un système logiquement combiné de composants qui remplit une fonction dans une application. Par exemple, autorisation, achat. Une fonctionnalité contient son propre graphique d'interface utilisateur (une chaîne de fragments), qui doit être stockée dans le backStack de navigation, et après avoir quitté la fonctionnalité, renvoyer l'utilisateur au point d'appel de la fonctionnalité.





Un schéma conditionnel d'une application composé de deux fonctionnalités:





Le diagramme montre une application qui a un MainFragment (M) et deux fonctionnalités (A et B). De M, vous pouvez accéder aux fonctionnalités A et B.Et à partir de la fonctionnalité B, nous pouvons accéder à la fonctionnalité A. De plus, après l'achèvement de la fonctionnalité A, nous devons revenir au point d'appel: si nous arrivons à la fonctionnalité A à partir de M, alors on retourne à M, et si on appelle la fonction A à partir de B, alors dans B. Dans ce cas, la pile de navigation doit être préservée. A1, A2, B1, B2 - fragments de caractéristiques.





: 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 et AppNavigator peuvent être étendus, j'ai montré ici l'exemple le plus simple. Par exemple, vous devez absolument passer des paramètres et renvoyer un résultat. Cela peut également être fait via ModuleNavInfo. Par exemple, pour renvoyer le résultat d'une fonctionnalité, vous pouvez ajouter StateFlow et vous y abonner. Tout se passe de manière concise et en un seul endroit. Vous pouvez également configurer l'interaction à l'aide de services internes qui peuvent être masqués du monde extérieur dans les modules BL.





L'exemple de code est disponible sur github .





Merci pour l'attention!








All Articles