Dynamic Feature Module

Introduction

Just recently we have one project which has 2 apps, one for consumer and the other one is for partner. During planning, we decided that: the app for consumer should be as small as possible in size. Thus we follow guide from Google using dynamic feature modules.

The grand design of the app architectures is of course modularisation, and each module should be able to fit in both apps. After some discussions, we decided the following should be presented in our architectures:

  1. Android Library Modules which consist of DataDomain and View Modeland can be used in either dynamic feature modules or main app,
  2. Dynamic Feature Modules which only contains code for UI, such as Fragment and Activity,
  3. Common UI and Common Code to be used both in Dynamic Feature Modules and both main apps,

Below are full diagrams

Full project diagram

Diagram C, D, and B are standard android library modules, which means we can easily import them on the main app and use it directly, however diagram A and B is a bit different, you can’t access feature modules from the main app, it works the other way around.

In this article, we’re going to focus more on part A and B and how we can reuse feature modules in both apps.

Problems

As mentioned above, the way Google design dynamic feature module is different than standard library, as mentioned here:

https://medium.com/androiddevelopers/patterns-for-accessing-code-from-dynamic-feature-modules-7e5dca6f9123

In the standard project structure that uses libraries, the base com.android.application module depends on com.android.library modules, which means you can use any classes defined in the library from the base module freely.

With DFMs however, the base com.android.application is a dependency of com.android.dynamic-feature modules, which means you can use any classes defined in the base module and its libraries in the DFM, but you can’t reference any code defined in the DFM from the base application at compile time.

Now this is fine if you only have one app, however in our project, this DFM design is forcing us to do something creative 😌. A naive approach to this problem is to copy Activity/Fragment from consumer project and paste in partner app, but that’s not what we want, right?

right!

Approach

Make sure when creating dynamic feature module, avoid creating dependency with main apps as much as possible, by doing the followings:

  1. Avoid accessing android.R on main apps, if use by both, put it inside Common UI module,
  2. Pass BuildConfig.ApplicationID as an intent to dynamic feature module instead of using it directly. BuildConfig.ApplicationID are mostly use to call another feature module,
  3. Use flavour to duplicate file that is using main app package, e.g. AndroidManifest.xml
  4. Use the same app name for both app,
  5. Delete .idea every time you change project from consumer to partner app,
  6. Since we’re sharing the module, you can only open consumer app or partner app, never open 2 apps at the same times.

Try it out!

Let’s try by creating first app called consumer, create new android module called common, and dynamic feature module called forgotpassword

App directory structures

Now open forgotpassword feature module’s manifest, and make sure you put dist:onDemand=”true”

If we run the app through internal app sharing, we will see the downloading process of feature module like this:

Main Activity
Downloading module
Forgot Password Activity

Note: running the app directly from Android Studio will not show “downloading module” screen. You have to use internal app sharing to test dynamic feature module.

So far so good, now close the consumer app, and let’s create another app, called partnerupdate settings.gradle to include common and forgotpassword feature

Next, we also update partner’s build.gradle similar to consumer’s build.gradle

Pay attention to include ‘:app’ in partner’s settings.gradle, my recommendation is that you should use the same name as in consumer app, but if something or other things force you to use different name, don’t worry you still can reuse the module which i’ll explain later.

Now if you check forgotpassword’s AndroidManifest.xml you will see warning/errors because it can’t find its parent that is com.adrena.dfm.consumer.MainActivityand because of that, clicking home button in Forgot Password Activity won’t do anything.

Lets fix this by creating flavor. Create a new file called flavors.gradle and put it inside consumer and partner directory.

Note: when adding flavor, all of your dynamic feature modules should also implement the same flavor.

Update all your consumer, partner and feature module’s build.gradle

Now navigate to Project, go to forgotpassword, and create a new folder called consumer and partner, and create AndroidManifest.xml in each directory

forgotpassword project directory with flavors

Now update AndroidManifest.xml file in main directory, remove all except manifest tag.

Update consumer’s AndroidManifest.xml inside consumer and partner directory

As you can see above we’re using different dist:onDemand for consumer and partnerwe also updated parent activity to point to consumer/partner package name

Now try changing Build Variants to partnerDebug and run the app, you should be able to go back to main activity now.

Partner build variants

Don’t forget to remove .idea directory (hidden), inside consumer app / partner app if you want to switch project, otherwise AndroidStudio might still pointing to wrong project.

And last, if you’re using different app name, you can use flavorImplementation to point to different app name, e.g.:

consumerImplementation project(':app')

partnerImplementation project(':partner')

Summary

By using flavor, we’re able to reuse feature module in 2 apps. This tutorial is pretty simple because we only change AndroidManifest.xml file. In real life project, you can use this approach if you’re using dependency injection and need AppComponent (if you are using Dagger 2) in feature modules.

Source code available here:

https://github.com/gotamafandy/DFMReuse