An example of a modular android application using the Navigation component and Koin (DI)

Developer, hello!





In this article, I want to share an example of a modular android application using NavComponent (JetPack) and Koin (DI).





We have many different android projects in our company that should use each other's features - this is a kind of ecosystem. To achieve this, we need to develop these features as independent and flexible as possible.





Requirements for features can be formulated as follows:





  1. A feature should be able to replace logic and UI independently of each other.





  2. To call a feature, it should be enough to know its interface (label) and the necessary input / output parameters.





  3. The feature itself must hide its implementation from other features.





  4. Support feature on-off out of the box.





For me, a feature is a logically combined system of components that performs some function in an application. For example, authorization, purchase. A feature contains its own UI graph (a chain of fragments), which should be stored in the navigation backStack, and after exiting the feature, return the user to the feature's call point.





A conditional scheme of an application consisting of two features:





The diagram shows an application that has a MainFragment (M) and two features (A and B). From M you can go to features A and B. And from feature B we can get to feature A. Moreover, after the completion of feature A, we must return to the point of call: if we got to feature A from M, then we return to M, and if called feature A from B, then into B. In this case, the navigation stack must be preserved. A1, A2, B1, B2 - feature fragments.





: 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 and AppNavigator can be extended, here I showed the simplest example. For example, you definitely need to pass parameters and return a result. This can also be done via ModuleNavInfo. For example, to return the result of a feature, you can add StateFlow and subscribe to it. Everything turns out concisely and in one place. You can also configure interaction using internal services that can be hidden from the outside world in BL modules.





The example code is available on github .





Thanks for attention!








All Articles