GloballyDynamic: Dynamic delivery during development
This is the first article in a three part article series on how you can leverage GloballyDynamic to accomplish tasks such as:
- Enabling dynamic delivery for environments where it would be otherwise unavailable (e.g. Firebase App Distribution).
- Testing various outlying dynamic delivery scenarios during development.
- Making life easier when working with multiple dynamic delivery platforms (e.g. Play Feature Delivery or Dynamic Ability) for the same project.
Series outline:
- Part 1: GloballyDynamic: Dynamic delivery during development (this article)
- Part 2: GloballyDynamic: Dynamic delivery with Firebase App Distribution
- Part 3: GloballyDynamic: Multi-platform dynamic delivery with a unified client API
Dynamic Delivery is Google’s app serving model, it leverages App Bundles and bundletool to dissect monolithic APKs into multiple smaller ones that together produce a wholesome experience.
It brings with it benefits such as saving device storage space, lowering network consumption and the ability to package discrete pieces of functionality from your app to be delivered on-demand through dynamic feature modules.
While the model is great, adopting it can introduce some inconveniences:
- App bundles are not supported by all app stores / distribution platforms (e.g. Amazon App Store, Samsung Galaxy Store, Firebase App Distribution): if you distribute through any of these you are not able to reap the benefits of dynamic delivery, and you ultimately have to deliver an app with behaviour different to the one distributed on Google Play Store (provided you use dynamic feature modules)
- Different integrations for different app stores: the app stores that do support it natively all provide different client API:s (e.g. Google Play Store through Play Feature Delivery and Huawei App Gallery through Dynamic Ability), you therefore have to provide a separate client side integration for each of the platforms you distribute through.
- Unavailable for devices without a dynamic delivery compatible app store installed: emulators w/o Play Store and other devices without a compatible app store (e.g. custom devices or Amazon Fire devices) are also excluded from using dynamic delivery.
Enter GloballyDynamic: a set of tools geared towards solving these problems.
The core features provided by GloballyDynamic are the following:
- Infrastructure necessary to make dynamic delivery universally available, regardless of underlying distribution platform.
- Unified Android client API: write once, run with any underlying dynamic delivery platform.
- Tools to provide a streamlined developer experience for dynamic delivery.
The remainder of this article will shed light on how to get up and running with a development setup that enables dynamic delivery during development.
The developer tools provided by GloballyDynamic allow for testing various scenarios you can encounter while making split install requests, some of these include:
- Slow download speeds
- Broken connection to the server from which to make split install requests
- Cancellation of split install requests
- Multiple split install sessions
What makes this possible is interplay between the following components:
- GloballyDynamic Server: an http server that receives and stores app bundles from which split APK:s are subsequently generated and delivered to clients.
- GloballyDynamic Studio Plugin: an Android Studio plugin that embeds a GloballyDynamic server.
- GloballyDynamic Gradle Plugin: a Gradle plugin that intercepts app bundles produced by the Android Gradle Plugin (e.g. through
./gradlew bundle
) and uploads them to a GloballyDynamic Server. - GloballyDynamic Android Library: an Android library that downloads and installs split APK:s from a GloballyDynamic Server.
The way in which these components cooperate is illustrated below:
Follow the steps provided below to configure your project for a development setup.
1) Install the Android Studio plugin
The plugin embeds a GloballyDynamic server which will be used to receive and store app bundles, these bundles will subsequently be used in order to generate and serve split APKs (such as dynamic feature APKs) to clients making split install requests.
Find the plugin here. Or search for “GloballyDynamic” in the plugin browser in Android Studio:
2) Configure your app to use the Android Studio embedded server
We now have to make sure that app bundles produced by AGP gets intercepted and uploaded to the Android Studio embedded server, as well as instructing our app to route split install requests to the server.
The way in which this is done is through the Gradle plugin, it exposes a globallyDynamicServers {}
DSL for this purpose — basically you configure a collection of GloballyDynamic servers in the form of a NamedDomainObjectContainer<GloballyDynamicServer>
, the GloballyDynamicServer
object exposes the following configurable properties:
serverUrl
: the url to the serverusername
: the username to the serverpassword
: the password to the serverthrottleDownloadBy
: the amount of time (in ms) to throttle split APK downloads by, defaults to 0applyToBuildVariants
: what build variants the server should be used foruploadAutomatically
: whether or not to upload bundles produced by AGP automatically, defaults to true
The username/password credentials are used for authorising bundle uploads to the server.
To use the Android Studio embedded server we apply the following configuration:
For kotlin script (build.gradle.kts
) the DSL usage is a slight bit different:
Some of the properties get packaged as metadata with the application that is being built for, such as
serverUrl
andthrottleDownloadBy
—this metadata is then used by the Android library in order to communicate with the server.
With that, all infrastructure is in place and we can proceed with integrating the Android library.
3) Integrate the Android library
The library provides the following core features:
- A dynamic delivery client implementation that downloads and installs split APKs from GloballyDynamic servers (in this case the Android Studio embedded one)
- A unified API in the form of a wrapper around different dynamic delivery client APIs, such as Play Core, Dynamic Ability and the internal implementation provided by the library.
It comes in different flavours, one for each underlying dynamic delivery platform, all of which expose an identical API while delegating operations to their respective underlying platforms.
In part 3 of this series we’ll go through how to configure a project for a multi-dynamic-delivery-platform setup (w/ Google Play, Huawei App Gallery etc); for now though, we are focusing on a development setup and thereby only need to use the com.jeppeman.globallydynamic.android:selfhosted
flavour.
The API itself mirrors that of Play Core, it thereby exposes versions of SplitCompat
and SplitInstallManager
, called GlobalSplitCompat
and GlobalSplitInstallManager
respectively — these are the cornerstones of the API.
Enable GlobalSplitCompat
Just as with Play Core, we need to enable access to dynamically downloaded code and resources; this is achieved by enabling GlobalSplitCompat
, it can be enabled either by using GlobalSplitCompatApplication
, or manually calling GlobalSplitCompat.install(context)
, as illustrated below:
You should also enable it for dynamically downloaded activities as follows:
Download and install dynamic features
The flow of installing features is also almost identical to that of Play Core, we interact with the GlobalSplitInstallManager
class, an instance of which is created as follows:
val manager = GlobalSplitInstallManagerFactory.create(context)
It exposes the usual suspects, such as startInstall(...)
, for installing, registerListener(...)
for monitoring install requests, cancelInstall(...)
for canceling requests, etc.
Performing an install request and monitoring the progress of it is illustrated in the gist below:
The addOnSuccessListener
also behaves in the same fire-and-forget manner as Play Core, i.e. the callback is not invoked when a feature has been downloaded and fully installed; but rather when a request has been accepted and scheduled to start.
For more details and documentation on the Android library, refer to here.
4) Build and run the application
All we need to do now is build a bundle and install the base APK from it, this can be done either through ./gradlew installDebug
, or running the app from Android Studio with the “APK from app bundle” option selected (Run > Edit configurations
) and unchecking modules you want to test on-demand delivery for.
Once the application has been installed, you should see something like the following in the “GloballyDynamic Log” tool window in Android Studio:
All done, split APKs can now be downloaded and installed from the app.
The following video showcases this development setup in action:
The project used in the video can be found here.
Addendum: Testing different install scenarios
As mentioned in the beginning of the article, we can test different outlying install scenarios during development, such as slow download speeds, broken server connection, canceling installs etc. Details on how go about it are follows below.
Slow download speeds
As illustrated in the above example, to throttle download speeds we configure the throttleDownloadBy
property from the Gradle plugin — the Android library sends this information as a parameter when making split install requests, the server then distributes the delivery of split APKs equally in an interval over the time provided.
Broken server connection
It can sometimes happen that a client loses connection to the server while downloading split APKs during an install request — this scenario can be tested easily with the help of the Android Studio plugin. The plugin adds a “GloballyDynamic” menu item where the server can be toggled on/off, as illustrated in the image below:
Simply toggle the server off while a download is in progress, and the installation process should emit a state with a status of GlobalSplitInstallSessionStatus.FAILED
and an error code of GlobalSplitInstallErrorCode.NETWORK_ERROR
.
Cancel install requests
With the throttling of download speeds, we can give the user enough time to react to the system notification that is displayed during split install requests, through this notification requests can be canceled, as illustrated in the video below:
When pressing Cancel, a state with a status of GlobalSplitInstallSessionStatus.CANCELED
will be emitted from the ongoing installation.
Multiple install requests
Throttling also allows for having multiple install requests active simultaneously, to test this, simply make multiple calls to GlobalSplitInstallManager#startInstall(GlobalSplitInstallRequest)
.
In the next part of the series we’ll discover how we can leverage GloballyDynamic to enable dynamic delivery for Firebase App Distribution through a similar process.