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.

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

Select File > New > New Module from the menu bar. In the Create New Module dialog, select Dynamic Feature Module and click Next. On the Configure your new module screen, give your module a name On the Module Download screen, specify the module title 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.

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.

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.

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.

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

Specifies a user-facing title for the module <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

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.