Navigating through runtime only modules and thinking outside of the box with androidx.startup and koin (decoupled multi-module projects)
By: Maxwell Mapako
Tags: Koin, Android App Development, Kotlin, Dependency Injection, Clean Code

Let’s set up a few case scenarios which we need solutions to, at the very least we want to be able to get a static fragment class definition and a bunch of intents to launch activities or services .e.t.c

We’ll only focus on the following modules:

discover-movie (feature module that is only available at run time)

discover-series (feature module that is only available at run time)

navigation (module for handling how to navigate between modules)

Reflection

Reflection is commonly used by programs which require the ability to examine or modify the runtime behaviour of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible. Source

While reflection is powerful it does have some drawbacks and limitations which you can read more about at the source above and here

Example:

Let’s define some extensions to manage some class loading stuff (full code extensions code can be found here):

Now to define our components targets (full code extensions code can be found here):

Each of our target have to know the fully qualified package name of the component we need to access, as you can imagine if we change class names, or minify then we might end up mismatching class package names.

Accessing our components:

Model to hold fragment definition and params

Accessing navigation targets

In the sample above we’re calling NavigationTargets and getting specific components specifically in this case only fragments in the form of Class<out Fragment> because I’m using the default fragment factory as the constructors for each of the feature fragments are empty

Service Loader

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means. Source

Typically using a service loader in android as is introduces some performance penalties unless you use R8 shrinker to optimize/rewrite the implementation to avoid the performance penalties. Since service loader is no longer supported by R8 for dynamic feature modules we won’t be covering this topic

Dependency Injection

Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLID’s dependency inversion and single responsibility principles. Source

Example:

Applying the Interface Segregation rule we can define an interface in our navigation module that we’ll use to expose fragments or intents similar to our Reflection approach.

Notice that we’re not defining more than we need in this contract, this is because we’d rather define what we need and extend this interface if we need to access more modules. Full code

Define navigation targets that we’ll use in various parts of the system:

Contract for navigation targets, we don’t really need to define invoke at this point, it could be an extension function or whatever you want to use. Full code

Take not that each of the defined navigation targets here extend the INavigationFeature. Full code

An extension that we’ll showcase later to get fragment definitions

Let’s hook up the Provider for each of our navigation targets in the appropriate feature module and register them in our dependency injector.

discover-show feature implements contract from navigation module and provides details of implementation. Full code

Let’s continue to define our DI modules

Note how we’re setting our featureModule is set to provide a type of NavShow.Provider. Full code

DynamicFeatureModuleHelper is just a helper class that implements the detail of how how the koin should load or unload modules

Registering the specific module to the application global dependency injection registry, since this is a run time only module we can leverage androidx.startup to load this features modules dependencies.

Feature initializer to initialize our declared DI modules 💁 Full code

Manifest for FeatureInitializer, this exists in our feature module manifest. Full code

Finally accessing a component from anywhere within the application 😋