iOS Test-Driven Development by Tutorials!

Make your iOS apps more testable, maintainable and
scalable—with the power of test-driven development.

Home Android & Kotlin Tutorials

Android App Bundles: Play Feature Delivery

Learn how to configure your app for Play Feature Delivery which uses advanced capabilities of app bundles, allowing certain features of your app to be delivered conditionally or downloaded on demand.

Version

  • Kotlin 1.5, Android 5.0, Android Studio 2020.3.1

In 2018, Android introduced the possibility of delivering apps not only in APK format but also Android App Bundle (AAB). As of August 2021, Android now requires developers to release all their apps as AABs only. Play Feature Delivery uses the advanced capabilities of app bundles.

Play Feature Delivery lets apps larger than 150 MB enable and download features without including them in the main package.

In this tutorial, you’ll build PlayFeatureDelivery, an app that shows pictures of cats and dogs. During the process you’ll learn about:

  • Dynamic feature modules.
  • On-Demand, install-time, conditional and instant delivery types.
  • Configuring, deploying and downloading on-demand modules.
  • Testing On-Demand modules locally.
  • Configuring and deploying instant apps.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Open Android Studio Arctic Fox or later and import the starter project. Then, build and run the project. You’ll see the following screen:

Home Screen

The app shows two CardViews, one for dogs and one for cats. Currently, nothing happens when you tap the CardViews. Don’t worry: You’ll add functionality to handle that in this tutorial.

Before you get started, take a moment to learn more about Play Feature Delivery.

Understanding Play Feature Delivery

Play Feature Delivery lets you customize the delivery of features in your app, which is especially helpful for larger apps. It helps you customize the delivery of such features.

You can set some features as available at install-time and make others available as users needs them. You can also provide features according to devices.

For example, say you have an app with a feature that requires augmented reality functionality. Currently, not all devices have the hardware to support this. Instead of shipping this feature to all devices, you can target devices with augmented reality support.

This way, only devices with augmented reality support will download the augmented reality feature. By targeting only those users who can use that feature, you’ll reduce your app’s download size. Users will only download features they can use on their device and ones they’ll need.

Enabling Play Feature Delivery

By default, AABs support Play Feature Delivery. Before you begin, you need to learn more about Dynamic Feature Modules.

Dynamic Feature Modules let you separate features from your base app. They provide a variety of configuration options to customize when features are available on your app. You’ll create your first dynamic feature module next.

Creating a Dynamic Feature Modules

To create a Dynamic Feature Module, go to File ▸ New ▸ New Module. You’ll see this:

Creating New Dynamic Feature Module

As you can see, there are many types of modules you can create for your project. Select the Dynamic Feature option and you’ll see the configuration options. You need to:

  1. First, specify your app base module. By default, it’s the app module, but if your name is different, you specify the name here.
  2. Then, set the module name. In this case, it’s features:dogs. You’ll add functionality to fetch dog images on this module.
  3. Appending features: Before the module name creates a package with that name which helps you group all your feature modules into one package.
  4. Then, add the package name for your module. Notice the package name is like the app module but the name is different.
  5. Set the language for your module as Kotlin. You have a choice of Java or Kotlin.
  6. Finally, specify the Minimum SDK version for your module. Always make sure the version is the same as that of your base module. If you don’t, you’ll have to deal with build failures.

Specifying Delivery Type

Click Next and you’ll see the final step for creating your dynamic feature module:

Configuring Delivery Type

In this step, you have to specify your module title and install-time inclusion which is a drop down list. The list has three options:

  1. Do not include module at install-time (on-demand only).
  2. Include module at install-time.
  3. Only include module at install-time for devices with specified features.

Select the second option, which makes your module available when a user downloads the app. You’ll take a closer look at the other options later in this tutorial.

Only devices running Android 5.0 (API level 21) and higher support downloading and installing features on demand. To make your feature available to earlier versions of Android, enable Fusing when creating a feature module.

Click Finish and wait for Gradle sync to complete. Your project now looks like this:

Projects Structure

You have the app and shared modules as well as the features package, which includes the dogs modules. Later, you’ll add one more module to this package.

The shared module contains all shared functionality, like making network calls, and code that all modules will need, making it easy to share code across. It also prevents you from writing repetitive code across all modules.

Modularizing Your Android App

Currently, your app has several modules making it a multi-modular architecture. For a project with a large number of features and a large team, modularizing your app has the following benefits:

  • You can achieve a separation of concerns by having modules that only have data, domain or presentation logic.
  • Teams can work on features in parallel without affecting another team’s work.
  • Your codebase is easier to test.
  • It’s also easier to reuse common code.
  • Configuring delivery types is easier with this architecture in place.

This list highlights some of the advantages of modularizing your app. There’s no definitive way of creating the module’s structure because it’s dependent on the individual team. The team has to weigh the pros and cons of modularizing the app before starting.

Next, you’ll look at the manifest file Android Studio generates when you create the dog’s module.

Manifest Configurations

Navigate to features/dogs. Open AndroidManifest.xml and you’ll see:

<!--1-->
  <dist:module
    dist:instant="false"
    dist:title="@string/title_dogs">
<!--2-->
    <dist:delivery>
      <dist:install-time />
    </dist:delivery>
<!--3-->
    <dist:fusing dist:include="true" />
  </dist:module>

Here’s a code breakdown:

  1. Here, you specify your module and if your app is an instant app.
  2. Then, you set the delivery type for your module to install-time while creating the module.
  3. You add the fusing behavior for your app. Setting it to true lets Android 4.4 and lower devices include this module. These devices don’t support dynamic feature modules.

Android Studio creates this configuration for you when you create the modules. If you need to change them, you can always edit your AndroidManifest.xml.

You’ve seen how to configure your AndroidManifest.xml, but what configurations options are available? You’ll dive into these options in the next section.

Looking at Delivery Types

Dynamic feature modules have the following delivery types:

  1. On-Demand delivery
  2. Install-time delivery
  3. Conditional delivery
  4. Instant delivery

First, take a closer look at On-Demand delivery.

On-Demand Delivery

This method of delivery is for features that aren’t critical when the user first installs the app. The app ships without these features, but users can request them when they need to use them. When the user requests these features, the app downloads them from the Play Core library and installs the module in your app.

Take a look at the manifest configuration for on-demand modules:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:dist="http://schemas.android.com/apk/distribution"
  package="com.raywenderlich.android.cats">

  <dist:module
    dist:instant="false"
    dist:title="@string/title_cats">
    <dist:delivery>
      <dist:on-demand />
    </dist:delivery>
    <dist:fusing dist:include="true" />
  </dist:module>
</manifest>

Notice the only change you make to make your module on-demand is setting the delivery type to on-demand. However, you need to specify how downloading the modules will happen and handle errors if they occur.

You’ll do that later on in this tutorial.

Install-time Delivery

When you set your module delivery to install-time, your module is on the app when the user first downloads it. This delivery method is useful for modules critical for app functionality, like the onboarding process, sign up and sign in. You can also uninstall these modules since the user won’t use them again on your app.

Your manifest file will look like this:

<dist:module
  dist:instant="false"
  dist:title="@string/title_dogs">
  <dist:delivery>
    <dist:install-time />
  </dist:delivery>
  <dist:fusing dist:include="true" />
</dist:module>

Conditional Delivery

For this type of delivery, you configure the module to be available if the device meets certain conditions. For example, you might develop a feature that isn’t available to users in a specific country. Using dist:conditions, you set the conditions so the module will only be available for the set countries.

The manifest configuration for conditional deliver looks like this:

<dist:module
  dist:instant="false"
  dist:title="@string/title_dogs">
  <dist:delivery>
    <dist:install-time >
      <dist:conditions>
        <dist:user-countries dist:exclude="true">
          <dist:country dist:code="KE"/>
        </dist:user-countries>
      </dist:conditions>
    </dist:install-time>
  </dist:delivery>
  <dist:fusing dist:include="true" />
</dist:module>

Other conditions you can add include:

  • Device API level.
  • Hardware features like camera and augmented reality.

Instant Delivery

For this type of delivery, you let users try your app without installing it. The manifest file looks like this:

<dist:module
  dist:instant="true"
  dist:title="@string/title_cats">
  <dist:delivery>
    <dist:on-demand />
  </dist:delivery>
  <dist:fusing dist:include="true" />
</dist:module>

In a dynamic feature module, you make a module an instant module by setting the dist:instant to true.

There are some things to note with instant feature modules:

  • Your app base module has to be instant-app enabled. In your base module, AndroidManifest.xml, you have to add <dist:module dist:instant="true">. If you use Android Studio to create you module, it will add <dist:module dist:instant="true"> for you.
  • Google Play limits the size of your base module plus instant enabled feature module to at most 10 MB.
  • An instant module can’t have background services or send notifications when running in the background.
  • You can’t have instant modules which are on-demand modules.

Now that you know the different configuration options available for the different delivery types, it’s time to get your hands dirty with the install-time dogs’ module.

Configuring Install-Time Modules

To start, you need to include your shared module in the dogs build.gradle highlighted in the image below.

dogs module gradle file

To do this, add the following code to your dependencies in your dogs module gradle file. Then select Sync Now which appears at the top:

implementation project(":shared")

In your dogs module, create a new file by right-clicking features/dogs/java/com.raywenderlich.android.dogs in Android Studio and selecting New ▸ Kotlin File/Class. Call it DogsActivity.

Place the following code below the package name package com.raywenderlich.android.dogs
and import the corresponding packages by pressing option-return on Mac or Alt+Enter on a PC. If any function has red squiggly lines, re-import its respective package.

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.raywenderlich.android.shared.R
import com.raywenderlich.android.shared.databinding.ActivityCatsDogsBinding
import com.raywenderlich.android.shared.presentation.adapters.DogsCatsAdapter
import com.raywenderlich.android.shared.presentation.states.UIModel
import com.raywenderlich.android.shared.presentation.states.UIState
import com.raywenderlich.android.shared.presentation.viewmodels.CatsDogViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel

class DogsActivity : AppCompatActivity() {
  // 1
  private val catsDogViewModel: CatsDogViewModel by viewModel()
  private val catsDogsAdapter = DogsCatsAdapter()
  private lateinit var binding: ActivityCatsDogsBinding

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityCatsDogsBinding.inflate(layoutInflater)
    setContentView(binding.root)
    // 2
    catsDogViewModel.getDogs()
    binding.rv.adapter = catsDogsAdapter
    observeDogs()
  }

  // 3
  private fun observeDogs() {
    lifecycleScope.launch {
      catsDogViewModel.dogs.flowWithLifecycle(lifecycle).collect { value: UIState ->
        when (value) {
          is UIState.ShowData<*> -> {
            binding.animationView.cancelAnimation()
            binding.animationView.visibility = View.GONE
            populateData(value.data as List<UIModel>)
          }
          is UIState.Error -> {
            Toast.makeText(applicationContext, value.message, Toast.LENGTH_SHORT).show()
            binding.animationView.cancelAnimation()
            binding.animationView.visibility = View.GONE
          }
          UIState.Loading -> {
            binding.animationView.apply {
              setAnimation(R.raw.dog_animation)
              playAnimation()
              visibility = View.VISIBLE
            }

          }
        }
      }
    }
  }

  // 4
  private fun populateData(data: List<UIModel>) {
    catsDogsAdapter.submitList(data)
  }
}

Here’s what’s happening in this class:

  1. First, you define your top level variables.
  2. Then you call getDogs() from CatsDogViewModel to fetch a list of dog images.
  3. You observe the state of the network call and handle each state.
  4. Finally, you submit the list of dog images to DogsCatsAdapter.

Your dog’s module is ready to show some cute dog images! Grrrrrr!

But first, don’t forget to do something almost all developers forget: Adding your activity in the manifest file!

Open your dogs/AndroidManifest.xml and add DogsActivity inside the manifest element:

<application>
  <activity
    android:name="com.raywenderlich.android.dogs.DogsActivity"
    android:label="@string/title_dogs"
    android:parentActivityName="com.raywenderlich.android.playfeaturedelivery.MainActivity"/>
  </application>

Next, inside MainActivity, replace TODO - Add Dogs Card Click Listener with the following code and import the package for Intent.

binding.dogsCard.setOnClickListener {
  val intent = Intent()
  intent.setClassName(BuildConfig.APPLICATION_ID, "com.raywenderlich.android.dogs.DogsActivity")
  startActivity(intent)
}

As you can see, you create a new Intent and specify the full class name for your DogsActivty. If you don’t do this, the app won’t find your activity since it’s in a different module.

Build and run. Tap DOGS and you’ll see this loading animation:

Dog Images Loading Screen

When the loading completes, you’ll see:

Dogs Images

This image shows an example of an install-time module. See how it displayed the cute dogs instantly!

What if you need to show cute images of cats on-demand? You’ll learn how to do that in the next section.

Configuring On-Demand Delivery Modules

You’ll follow the same steps you did to create your dogs module. Create a new cats module by going to File ▸ New ▸ New Module as shown below:

Creating New Dynamic Feature Module

Click Next. This time, on your final step, select the Do not include module at install-time (on-demand only) option.

Configuring Delivery Type

Now, your modules will look like this:

All Modules Structure

Inside your features package, you’ll find two dynamic feature modules: One is on-demand, and the other one is install-time.

Next, add the functionality to display cat images. Add the shared module to cats build.gradle:

implementation project(":shared")

Do a Gradle sync to see your changes.

Inside cats, create a new file by right-clicking features/cats/java/com.raywenderlich.android.cats in Android Studio and selecting New ▸ Kotlin File/Class. Name it CatsActivity.

In CatsActivity, below the package name package com.raywenderlich.android.cats, add:

import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.google.android.play.core.splitcompat.SplitCompat
import com.raywenderlich.android.shared.R
import com.raywenderlich.android.shared.databinding.ActivityCatsDogsBinding
import com.raywenderlich.android.shared.presentation.adapters.DogsCatsAdapter
import com.raywenderlich.android.shared.presentation.states.UIModel
import com.raywenderlich.android.shared.presentation.states.UIState
import com.raywenderlich.android.shared.presentation.viewmodels.CatsDogViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel

class CatsActivity : AppCompatActivity() {
  private val catsDogViewModel: CatsDogViewModel by viewModel()
  private val catsDogsAdapter = DogsCatsAdapter()
  private lateinit var binding: ActivityCatsDogsBinding

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityCatsDogsBinding.inflate(layoutInflater)
    setContentView(binding.root)
    catsDogViewModel.getCats()
    binding.rv.adapter = catsDogsAdapter
    observeCats()

  }

  private fun observeCats() {
    lifecycleScope.launch {
      catsDogViewModel.cats.flowWithLifecycle(lifecycle).collect { value: UIState ->
        when (value) {
          is UIState.ShowData<*> -> {
            binding.animationView.cancelAnimation()
            binding.animationView.visibility = View.GONE
            populateData(value.data as List<UIModel>)
          }
          is UIState.Error -> {
            binding.animationView.cancelAnimation()
            binding.animationView.visibility = View.GONE
            Toast.makeText(applicationContext, value.message, Toast.LENGTH_SHORT).show()
          }
          UIState.Loading -> {
            binding.animationView.apply {
              setAnimation(R.raw.cat_animation)
              playAnimation()
              visibility = View.VISIBLE
            }
          }
        }
      }
    }
  }

  private fun populateData(data: List<UIModel>) {
    catsDogsAdapter.submitList(data)
  }

  override fun attachBaseContext(base: Context?) {
    super.attachBaseContext(base)
    SplitCompat.install(this)
  }
}

This class is similar to DogsActivity with one slight difference: The CatsActivity overrides attachBaseContext(base: Context?). To access modules code and resources, you must enable SplitCompat in your app which is why you have the SplitCompat.install(this) line.

Don’t forget to add your activity to cats/AndroidManifest.xml:

  <application>
    <activity
      android:name=".CatsActivity"
      android:label="@string/title_cats"
      android:parentActivityName="com.raywenderlich.android.playfeaturedelivery.MainActivity"/>
  </application>

Build and run. Tap CATS. Nothing happens because this is an on-demand module. The app needs to download the module first before you can view it on the app.

You’ll add the logic to download this module in the next section.

Downloading an On-Demand Module

To download an on-demand module, you’ll use the Play Core Library. You’ll see it’s already in the project in the app-level build.gradle.

Initiating Download of On-Demand Modules

Locate setupModulesDownload() inside MainActivity in app module. Then replace TODO - Create Split Install Request with:

val catsModuleInstallRequest = SplitInstallRequest.newBuilder()
    .addModule(CATS_MODULE)
    .build()

In the code above, you create a new SplitInstallRequest builder. It requests the download of CATS_MODULE.

Now, resolve the IDE imports when IDE prompts you. Declare the companion object block globally within your MainActivity to resolve the error on CATS_MODULE:

  companion object {
    private const val DOGS_MODULE = "dog"
    private const val CATS_MODULE = "cats"
  }

Here, you specify your module names with the names you used while creating the modules. Your SplitInstallRequest is ready for use!

You need to register a listener that will notify your app of the download’s progress. To achieve this, first define a SplitInstallManager below ActivityMainBinding and import the corresponding packages:

private val splitInstallManager: SplitInstallManager by lazy { SplitInstallManagerFactory.create(this) }

SplitInstallManager handles requests for downloading modules.

Next, inside setupModulesDownload(), replace TODO - Register Listener with the following code and import respective packages:

// 1
splitInstallManager.registerListener {
  // 2
  when (it.status()) {
    SplitInstallSessionStatus.DOWNLOADING -> {
      binding.progressIndicator.visibility = View.VISIBLE
      Toast.makeText(applicationContext, "Downloading", Toast.LENGTH_SHORT).show()
    }
    SplitInstallSessionStatus.INSTALLED -> {
      binding.progressIndicator.visibility = View.GONE
      Toast.makeText(applicationContext, "Module Download Completed", Toast.LENGTH_SHORT).show()
      // 3
      val intent = Intent()
      intent.setClassName(BuildConfig.APPLICATION_ID, "com.raywenderlich.android.cats.CatsActivity")
      startActivity(intent)
    }
  }
}

Here, you:

  1. Register a listener with your SplitInstallManager. Your app will now receive updates when the download reaches all the states.
  2. Then, you check for the various states.
  3. When the state is SplitInstallSessionStatus.INSTALLED, you navigate to CatsActivity, which is in the on-demand cats module.

Refer to SplitInstallErrorCode in Android’s documentation. It provides several states you can handle, for example NETWORK_ERROR. These states help you to properly redirect the user or notify the user of the error.

With this set, you need to start the download of your module when you tap the CATS card. To do this, below registerListener, add:

binding.catsCard.setOnClickListener {
  splitInstallManager.startInstall(catsModuleInstallRequest)
}

Here, you start the download of your module with the SplitInstallRequest you created. Now, open Run/Debug Configurations as shown below:

Run/Debug Configurations

Use the bundle option to test this. Build and run.

Bundle Run Configurations

Testing with default configurations can lead to weird scenarios. You should always build using the APK from the bundle option so you can test whether your modules have the expected behavior.

Build and run. The tap CATS. Nothing happens because SplitInstallManager needs to download the module from Play Store.

Note: To learn more about on-demand configurations , check out the official documentation.

Deploying On-Demand Modules

Deploying on-demand modules is similar to how you usually deploy normal AAB. Please see Android’s documentation on this subject. The only difference is that the Play Core Library will now start downloading on-demand modules when you download an APK from Play Store.

Once you have your app on the Internal testing track, or any other track, and install the app, you’ll see:

Donwnloading on-demand module

Open the app and tap CATS. You’ll see a ProgressDialog indicating that your module is being downloaded.

Once the download is complete, you’ll see a Toast with a Module Download Complete message. The app will redirect to CatsActivity where you’ll fetch the cat images and display them.

On-Demand Module Download Complete

Cat Images

Note: To test your on-demand modules, you need to have your app on Google Play Store.

Congratulations! You added an on-demand module, uploaded it to Google Play Store and tested it on a real device! In the next section, you’ll learn some of the methods for testing your on-demand modules locally.

Testing On-Demand Modules Locally

Testing on-demand modules is essential to ensure you have the correct working flow. You can test them locally before deploying them to Google Play Store.

The following testing options are available:

  1. bundletool is a command-line tool. It helps you generate a set of APKS for testing locally. You deploy these APKs to your test device and then you can test your apps.
  2. Globally Dynamic has a set of tools to help you test your dynamic modules locally. bundletool has an API similar to the Play Core Library which also has the SplitInstallManager. It gives you a uniform way to deliver dynamic modules on all platforms that support dynamic delivery.

Configuring Instant Apps

Android Instant Apps evolves Android apps to run instantly, without requiring installation. You’ll configure your app module to be an instant-module.

First, you need to add the Google Instant APIs to your app. To the app-level build.gradle, add:

implementation "com.google.android.gms:play-services-instantapps:17.0.1"

Next, inside app/AndroidManifest.xml above your permission declaration, add:

<dist:module dist:instant="true" />

This code enables instant app support in your app module. Next, you need to change your version code because you deploy Instant apps on a separate track. So, you need to have versions codes for your instant app and your production release.

Locate your app-level build.gradle and change it to:

defaultConfig {
  applicationId "com.raywenderlich.android.playfeaturedelivery"
  minSdkVersion rootProject.ext.minSdkVersion
  targetSdkVersion rootProject.ext.targetSdkVersion
  versionCode 1006
  versionName "1.6"
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Here, you modify the version code to 1006 or any number you prefer.

Ensure that your instant app’s versionCode is always less than that of your installable app because usually, the best UX is for users to move from an instant experience to your app. Google Play considers this an update, so you need to have a lower versioning system for your instant apps.

Your instant app is ready for deployment. Next, you’ll deploy your instant app to a physical device and also to Google Play Store.

Deploying Instant Apps to a Physical Device

To deploy your instant app to a physical device or emulator, modify your Run Configurations to:

Instant app Run Configurations

As you can see, you set the app to deploy as an instant app. Now, uninstall the previous versions of your app. Then build and run.

Home Screen

The app runs as before, but as an instant app. When you exit the app and check your installed apps, it won’t be there.

Deploying Instant Apps to Google Play Store

By default, Google Play doesn’t have Instant Apps enabled. To enable it, go to the Google Play Console and select your app. Navigate to Security▸Advanced Settings:

Console Advanced Settings

Then click Advanced Settings and navigate to Release types. You’ll see:

Adding new release type

Click Add Release Type and select Google Play Instant from the drop-down list that appears.

Google Play Instant  Releases Active

As you can see, Google Play Instant is active now.

Note: Failure to do the above steps will result in your app not having the instant app experience even though you’ve already set your modules to instant-modules.

Creating a Play Instant Release

Now you’re ready to do a Play Instant release.

Build a signed AAB for your app. Head over to Play Console and choose the Internal testing track. At the top right end, change your track from Standard to Instant apps only.

Release Types Dropdown list

Click Create new release to add a new release that your instant app will use.

Uploading new AAB

Upload the AAB that you signed. When uploading is complete, you’ll see:

Bundle Release Details

Click Save, then click Review release since it will be active.

More release details

This screen shows you information about your release, like supported devices, and also contains information from your AAB.

Click Start rollout to Internal testing and confirm when prompted by Google Play Console. Your instant app experience is now available to all your testers in the Internal testing track.

Woot woot! Your cats and dogs are ready to de delivered instantly.:] Download the app from the link on the Internal testing track, and you’ll see:

Instant app instant screen

Tap TRY NOW to start the download. After it completes, you’ll see the home screen with the two CardViews. Tapping them won’t do anything because you haven’t set the other modules to be instant.

Downloading Instant App

Home Screen

As before, when you check the list of installed apps, the PlayFeatureDelivery app isn’t there because it’s an instant app and wasn’t installed. However, your app will be cached, and the user won’t have to download the instant experience again.

Note: Check out official documentation to learn more considerations and things you need to do for other functionalities for instant app.

Where To Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Congratulations! You learned a lot about Play Feature Delivery and can now add dynamic feature modules and configure their delivery options as you wish. Check UX Guidelines. to learn more on how best to handle downloading of this feature modules and gives users if your app the best experience.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Reviews

More like this

Contributors

Comments