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:
Android Library Modules which consist of Data, Domain and View Modeland can be used in either dynamic feature modules or main app,
Dynamic Feature Modules which only contains code for UI, such as Fragment and Activity,
Common UI and Common Code to be used both in Dynamic Feature Modules and both main apps,
Below are full diagrams
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.
As mentioned above, the way Google design dynamic feature module is different than standard library, as mentioned here:
In the standard project structure that uses libraries, the base com.android.application module depends oncom.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?
Make sure when creating dynamic feature module, avoid creating dependency with main apps as much as possible, by doing the followings:
Avoid accessing android.R on main apps, if use by both, put it inside Common UI module,
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,
Use flavour to duplicate file that is using main app package, e.g. AndroidManifest.xml
Use the same app name for both app,
Delete .idea every time you change project from consumer to partner app,
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
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:
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 partner, updatesettings.gradleto includecommonand forgotpassword feature
Next, we also update partner’sbuild.gradle similar to consumer’s build.gradle
Pay attention toinclude ‘: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.MainActivity, and 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
Now update AndroidManifest.xml file in main directory, remove all except manifest tag.
As you can see above we’re using different dist:onDemand for consumer and partner, we 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.
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.:
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.