Exploring Dynamic Feature Modules at Swiggy

Sarabjeet Singh
Swiggy Bytes — Tech Blog
10 min readNov 22, 2019

--

https://undraw.co/

This blog is a part of our App Size Reduction Blog Series at Swiggy, we will be discussing Google Play’s Dynamic Delivery which is one of the App Bundle features released to help developers to reduce app size and streamline the release process. We will be talking about the requirements and basic implementation of Dynamic Feature modules (DFM) along with how we shipped Swiggy Daily’s Helpcenter as a dynamic feature module, the problems we encountered and the solutions we came up with.

What is Dynamic Delivery?

Dynamic delivery allows you to download the modules on-demand in order to optimize the app install size from the play store. Users can later download and install the components on-demand after they’ve already installed the base APK of the app.

For example, consider a text messaging app with functionality that allows you to capture and send picture messages. But we know that only a small percentage of users send picture messages. It may make sense to include picture messaging as a downloadable dynamic feature module. That way, the initial app download is smaller for all users and only those users who send picture messages need to download that additional component.

Prerequisites Before you start

  • Modular CodeBase: Keep in mind that in order to use on-demand delivery one needs a modularized code base and this type of modularization requires more effort and possibly refactoring the app’s existing code. So carefully consider which of your app’s features would benefit the most from being available to users on-demand. At Swiggy we have divided our codebase into independent and interchangeable modules which give us advantages on build time improvement, better reusability across other apps and fine-grained dependency control.
  • Deciding on Feature: Deciding which feature to ship on Demand should be considered properly, you don’t want to ship a feature that provides the main functionality for your app since the user has to wait for the module to get downloaded before they can use the app. This will result in a drop in user conversion numbers. At Swiggy, we decided to ship our Help and Support Section as an on-demand module because only a small number of users use this feature that too in cases of any grievance. In the future, we are planning to ship OnBoarding as a dynamic module since we can uninstall the modules that are not needed. This helps us in further optimizing the app size on the user device.

Now that we have some basic understanding of what dynamic delivery is and how it changes the dependencies for our project, we are ready for some hands-on code.

let's dive in

Creating a dynamic feature module

You can either create a new dynamic module from Android Studio File options or convert an exciting module or library into a dynamic feature module.

Creating a new dynamic feature module

This a strait-forward step where android studio generates all the required files for your dynamic feature module, follow these steps

  1. Select File > New > New Module from the menu bar. In the Create New Module dialog, select Dynamic Feature Module and click Next.
  2. On the Configure your new module screen, give your module a name
  3. On the Module Download screen, specify the module title
  4. Hit Finish and wait for the project to sync. And we got ourself our first dynamic feature module :)

Converting a module/lib into a DFM

We need to do some work to convert an existing module to the dynamic feature module, follow the below steps.

1. Changes in module build.gradle file

// Replace this plugin in your module/lib gradle file
apply plugin: 'com.android.library'
// with this plugin
apply plugin: 'com.android.dynamic-feature'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':app')
}
  • com.android.dynamic-feature the plugin includes the Gradle tasks and properties required to configure and build an app bundle that includes your dynamic feature module.
  • If you are using Data Binding in your module don’t forget to enable it and add this proguard rule to keep the BindingMapper class for our feature module in our case helpmodule
-keep class in.swiggy.android.helpmodule.DataBinderMapperImpl { *; }
  • What not to include:
    Signing configurations: App bundles are signed using signing configurations of the base module.
    The minifyEnabled property: You can enable code shrinking for your entire app project from only the base module’s build configuration.
    versionCode and versionName: Gradle uses app version information that the base module provides.

2. Changes in module AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="in.swiggy.android.helpmodule">
<dist:module
dist:instant="false"
dist:onDemand="true"
dist:title="@string/title_helpdfm">
<dist:fusing dist:include="true" />
</dist:module>
<application> <activity ............

</application>

</manifest>
  • <dist:module This new XML element defines attributes that determine how the module is packaged and distributed as APKs.
  • dist:instant="true | false"Specifies whether the module should be available through Google Play Instant as an instant experience.
  • dist:onDemand="true | false"Specifies that the module should be available as an on demand download.
  • dist:title="@string/feature_name"Specifies a user-facing title for the module You need to include the string resource for this title in the base module’s strings.xml file
  • <dist:fusing dist:include="true | false" /> Specifies whether to include the module in multi-APKs that target devices running Android 4.4 (API level 20) and lower.

3. Changes in Base module build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {

.......
buildTypes {

release {
zipAlignEnabled true
minifyEnabled true
shrinkResources false
debuggable false
useProguard true
}
}
.......
// add name of the module here
dynamicFeatures = [":helpmodule"]

.......
}
  • Add Dynamic feature module dependencies to the base module using dynamicFeatures properties in build.gradle
  • We cannot use shrinkResources if we have dynamic feature modules present, since enabling it removes the dynamic feature from the apk. There is an ongoing discussion on whether will it be enabled in future

Dependency Injection Graph

With dynamic feature modules, the way modules usually depend on each other is inverted. Instead of the app module the dynamic feature modules depend on the app module.

Example of a Dagger graph in a project with dynamic feature modules

In Dagger, components need to know about their subcomponents. This information is included in a Dagger module added to the parent component (like the SubcomponentsModule module in Using Dagger in Android apps). Unfortunately, with the reversed dependency between the app and the dynamic feature module, the subcomponent is not visible from the app module because it's not in the build path.

Dagger has a mechanism called component dependencies that you can use to solve this issue. Instead of the child component being a subcomponent of the parent component, the child component is dependent on the parent component. With that, there is no parent-child relationship; now components depend on others to get certain dependencies. As an example, a LoginComponent defined in a login dynamic feature module depends on AppComponent defined in app module to provide the dependencies

@Component(dependencies = [AppComponent::class])
interface LoginComponent {
fun inject(activity: LoginActivity)
}

A DFM can access Dagger components from the application module. The application module can access components from libraries it depends on. But not the other way around.

But at Swiggy, we had one more challenge which we needed to solve before we go on to build our dependency graph. Our feature modules are not dependent on the app module either. So we can’t depend on the common component. We came up with a different approach to pass Dependencies to our feature modules i.e. using Component Builder Pattern

@FragmentScope
@Component(modules = [AndroidSupportInjectionModule::class])
interface HelpCenterComponent {
fun inject(helpCenterFragment: HelpCenterFragment) @Component.Builder
interface Builder{
fun build(): HelpCenterComponent @BindsInstance
fun provideSharedPref(pref: SharedPreferences): Builder
@BindsInstance
fun provideGson(gson: Gson): Builder
}
}

As shown above our HelpCenterFragement need SharedPreferences and Gson objects as a dependency to work. Hence, we created a common Interface named IHelpDependencyProvider which would be a part of a common module on which both the app and our dynamic feature module depends

interface IHelpDependencyProvider {    fun provideSharedPreference(): SharedPreferences    fun provideGson(): Gson}

Our Application class will extend this interface and provide the dependencies which we can request in our Fragments onAttach method and casting the applicationcontext to above interface

override fun onAttach(context: Context) {
super<androidx.fragment.app.Fragment>.onAttach(context)
SplitCompat.install(context)
val provider = (context.applicationContext as IHelpDependencyProvider)
DaggerHelpCenterComponent.builder()
.provideSharedPref(provider.provideSharedPreference())
.provideGson(provider.provideGson())
.build()
.inject(this)
}
Downloading HelpCentre on Demand Module in Swiggy Daily

Downloading dynamic feature modules

With Google Play’s Dynamic Delivery, the app can download dynamic feature modules on demand to devices running Android 5.0 (API level 21) and higher. You can also use this API to download on-demand modules for your Android Instant Apps.

Showing the downloading state when the module is downloading in Swiggy Daily App

Include the Play Core Library in your app’s build.gradle

dependencies {
implementation 'com.google.android.play:core:1.6.4'
}

Note: Do not forget to add Google’s Maven Repository in project’s build.gradle.

Request an on-demand dynamic feature module

There are two ways to request an on-demand module:

  • Using SplitInstallManager.startInstall(): Your app needs to be in the foreground to submit the request. When your app requests an on-demand module, the Play Core Library employs a “fire-and-forget” strategy in an async fashion. That is, it sends the request to download the module to the platform, but it does not monitor whether the installation succeeded. So you are responsible for monitoring the request state (explained later).
Request on-demand module

Note: To have immediate access to the module’s code and resources, your app needs to enable SplitCompat.

  • Using SplitInstallManager.deferredInstall(): If you want to defer installation for when the app is running in the background. Request for deferred install is best-effort and you cannot track its progress. So, before trying to access a module you have specified for deferred installation, you should check whether the module has been installed or not (explained later).
splitInstallManager.deferredInstall(Arrays.asList("dynamicModule"));

Note: It’s okay to request a dynamic feature module that’s already installed on the device. The API instantly considers the request as completed if it detects the module is already installed.

Additionally, after a module is installed, Google Play keeps it updated automatically. That is, when you upload a new version of your app bundle, the platform updates all installed APKs that belong to your app

Monitor the Request State of an on-demand module

To be able to update a progress bar, fire an intent after installation, or gracefully handle a request error, you need to listen for state updates from the asynchronous SplitInstallManager.startInstall() task. Before you can start receiving updates for your install request, register a listener and get the session ID for the request, as shown below.

Monitoring the request state for on-demand module

Check the available SplitInstallErrorCode and SplitInstallSessionStatus to gracefully handle the state change and errors

Handling the error states for module install in Swiggy Daily App

Managing dynamic feature modules

  • To cancel a request before it is installed, you can do so by invoking the cancelInstall() method using the request’s session ID.
splitInstallManager.cancelInstall(mySessionId);
Set<String> installedModules = splitInstallManager.getInstalledModules();
splitInstallManager.deferredUninstall(Arrays.asList("moduleName"));

How Swiggy Daily uses on Demand Modules

We have successfully shipped the HelpCentre and Support Section as a dynamic feature module in the Swiggy Daily App. We saw an app size reduction of 100kb in our base APK. We have different flows from where we fire install request for HelpCentre on-demand module.

  • Account Section: When the users navigate to Account Section of Daily App we trigger an install request for dynamic feature module, see the below image to see how we handle the intermediate states and error states
Account Section
  • Order Details: In some cases when the user directly navigates to the order details screen and taps on Help Button, we display a bottom sheet to show the intermediate states and error states
Order Detail Screen
  • Plan Settings: In this case when the user navigates to his plan detail screen and opens the plan settings, we fire the install request for the HelpCentre module and show the intermediate and error states to the user.
Plan Settings Bottom Sheet

Common Errors we encountered during downloading DFM

References

Acknowledgements

I would like to thank my colleagues for helping me implement this cool feature, you can reach out to them at Supriya Saha, Sunil Kumar. And thanks to Amrit Sanjeev for the guidance :)

I’m Sarabjeet (savvisingh) from front-end team at Swiggy. If you enjoyed learning about this and want to be a part of an exciting tech culture then head to https://careers.swiggy.com/ we are hiring

--

--