Manage availability for on-demand Google Play services modules

  • Some Google Play services SDK functionalities are provided as on-demand modules to conserve device storage and memory.

  • While Google Play services usually handles module installation automatically, the ModuleInstallClient API allows for more control over this process.

  • ModuleInstallClient enables checking module availability, requesting installations (deferred or urgent), monitoring progress, and handling errors.

  • You can simulate module install API results for testing purposes using the FakeModuleInstallClient in conjunction with a dependency injection framework like Hilt.

As described in the Overview of Google Play services article, SDKs powered by Google Play services are backed by on-device services on Google-certified Android devices. To conserve storage and memory across the entire fleet of devices, some services are provided as modules that are installed on-demand when your app requires the relevant functionality. For example, ML Kit provides this option when using models in Google Play services.

In most cases, the Google Play services SDK downloads and installs the necessary modules automatically when your app uses an API that requires them. However, you might want to have more control over the process, such as when you want to improve the user experience by installing the module in advance.

The ModuleInstallClient API gives you the ability to:

  • Check if the modules are already installed on the device.
  • Request to install the modules.
  • Monitor the installation progress.
  • Handle errors during the installation process.

This guide shows you how to use ModuleInstallClient to manage modules in your app. Note that the following code snippets use the TensorFlow Lite SDK (play-services-tflite-java) as an example, but these steps are applicable for any library that's integrated with OptionalModuleApi.

Before you begin

To prepare your app, complete the steps in the following sections.

App prerequisites

Make sure that your app's build file uses the following values:

  • A minSdkVersion of 23 or higher

Configure your app

  1. In your top-level settings.gradle file, include Google's Maven repository and the Maven central repository within the dependencyResolutionManagement block:

    dependencyResolutionManagement{
    repositories{
    google()
    mavenCentral()
    }
    }
    
  2. In your module's Gradle build file (usually app/build.gradle), add the Google Play services dependencies for play-services-base and play-services-tflite-java:

    dependencies{
    implementation'com.google.android.gms:play-services-base:18.10.1'
    implementation'com.google.android.gms:play-services-tflite-java:16.5.0'
    }
    

Check if modules are available

Before you try to install a module, you can check if it's already installed on the device. This helps you avoid unnecessary installation requests.

  1. Get an instance of ModuleInstallClient:

    Kotlin

    valmoduleInstallClient=ModuleInstall.getClient(context)

    Java

    ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
  2. Check the availability of a module using its OptionalModuleApi. This API is provided by the Google Play services SDK that you are using.

    Kotlin

    valoptionalModuleApi=TfLite.getClient(context)
    moduleInstallClient
    .areModulesAvailable(optionalModuleApi)
    .addOnSuccessListener{
    if(it.areModulesAvailable()){
    // Modules are present on the device...
    }else{
    // Modules are not present on the device...
    }
    }
    .addOnFailureListener{
    // Handle failure...
    }

    Java

    OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
    moduleInstallClient
    .areModulesAvailable(optionalModuleApi)
    .addOnSuccessListener(
    response->{
    if(response.areModulesAvailable()){
    // Modules are present on the device...
    }else{
    // Modules are not present on the device...
    }
    })
    .addOnFailureListener(
    e->{
    // Handle failure...
    });

Request a deferred install

If you don't need the module immediately, you can request a deferred install. This allows Google Play services to install the module in the background, potentially when the device is idle and connected to Wi-Fi.

  1. Get an instance of ModuleInstallClient:

    Kotlin

    valmoduleInstallClient=ModuleInstall.getClient(context)

    Java

    ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
  2. Send the deferred request:

    Kotlin

    valoptionalModuleApi=TfLite.getClient(context)
    moduleInstallClient.deferredInstall(optionalModuleApi)

    Java

    OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
    moduleInstallClient.deferredInstall(optionalModuleApi);

Request an urgent module install

If your app needs the module immediately, you can request an urgent install. This will try to install the module as quickly as possible, even if it means using mobile data.

  1. Get an instance of ModuleInstallClient:

    Kotlin

    valmoduleInstallClient=ModuleInstall.getClient(context)

    Java

    ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
  2. (Optional) Create an InstallStatusListener to monitor the install progress.

    If you want to display the download progress in your app's UI (for example, with a progress bar), you can create an InstallStatusListener to receive updates.

    Kotlin

    innerclassModuleInstallProgressListener:InstallStatusListener{
    overridefunonInstallStatusUpdated(update:ModuleInstallStatusUpdate){
    // Progress info is only set when modules are in the progress of downloading.
    update.progressInfo?.let{
    valprogress=(it.bytesDownloaded*100/it.totalBytesToDownload).toInt()
    // Set the progress for the progress bar.
    progressBar.setProgress(progress)
    }
    if(isTerminateState(update.installState)){
    moduleInstallClient.unregisterListener(this)
    }
    }
    funisTerminateState(@InstallStatestate:Int):Boolean{
    returnstate==STATE_CANCELED||state==STATE_COMPLETED||state==STATE_FAILED
    }
    }
    vallistener=ModuleInstallProgressListener()

    Java

    staticfinalclass ModuleInstallProgressListenerimplementsInstallStatusListener{
    @Override
    publicvoidonInstallStatusUpdated(ModuleInstallStatusUpdateupdate){
    ProgressInfoprogressInfo=update.getProgressInfo();
    // Progress info is only set when modules are in the progress of downloading.
    if(progressInfo!=null){
    intprogress=
    (int)
    (progressInfo.getBytesDownloaded()*100/progressInfo.getTotalBytesToDownload());
    // Set the progress for the progress bar.
    progressBar.setProgress(progress);
    }
    // Handle failure status maybe...
    // Unregister listener when there are no more install status updates.
    if(isTerminateState(update.getInstallState())){
    moduleInstallClient.unregisterListener(this);
    }
    }
    publicbooleanisTerminateState(@InstallStateintstate){
    returnstate==STATE_CANCELED||state==STATE_COMPLETED||state==STATE_FAILED;
    }
    }
    InstallStatusListenerlistener=newModuleInstallProgressListener();
  3. Configure the ModuleInstallRequest and add the OptionalModuleApi to the request:

    Kotlin

    valoptionalModuleApi=TfLite.getClient(context)
    valmoduleInstallRequest=
    ModuleInstallRequest.newBuilder()
    .addApi(optionalModuleApi)
    // Add more APIs if you would like to request multiple modules.
    // .addApi(...)
    // Set the listener if you need to monitor the download progress.
    // .setListener(listener)
    .build()

    Java

    OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
    ModuleInstallRequestmoduleInstallRequest=
    ModuleInstallRequest.newBuilder()
    .addApi(optionalModuleApi)
    // Add more API if you would like to request multiple modules
    //.addApi(...)
    // Set the listener if you need to monitor the download progress
    //.setListener(listener)
    .build();
  4. Send the install request:

    Kotlin

    moduleInstallClient
    .installModules(moduleInstallRequest)
    .addOnSuccessListener{
    if(it.areModulesAlreadyInstalled()){
    // Modules are already installed when the request is sent.
    }
    // The install request has been sent successfully. This does not mean
    // the installation is completed. To monitor the install status, set an
    // InstallStatusListener to the ModuleInstallRequest.
    }
    .addOnFailureListener{
    // Handle failure...
    }

    Java

    moduleInstallClient.installModules(moduleInstallRequest)
    .addOnSuccessListener(
    response->{
    if(response.areModulesAlreadyInstalled()){
    // Modules are already installed when the request is sent.
    }
    // The install request has been sent successfully. This does not
    // mean the installation is completed. To monitor the install
    // status, set an InstallStatusListener to the
    // ModuleInstallRequest.
    })
    .addOnFailureListener(
    e->{
    // Handle failure...
    });

Test your app with FakeModuleInstallClient

Google Play services SDKs provide the FakeModuleInstallClient to allow you to simulate the results of the module install APIs in tests using dependency injection. This helps you to test your app's behavior in different scenarios without needing to deploy it to a real device.

App prerequisites

Configure your app to use Hilt dependency injection framework.

Replace ModuleInstallClient with FakeModuleInstallClient in test

To use FakeModuleInstallClient in your tests, you need to replace the ModuleInstallClient binding with the fake implementation.

  1. Add dependency:

    In your module's Gradle build file (usually app/build.gradle), add the Google Play services dependencies for play-services-base-testing in your test.

    dependencies{
    // other dependencies...
    testImplementation'com.google.android.gms:play-services-base-testing:16.2.0'
    }
    
  2. Create a Hilt module to provide ModuleInstallClient:

    Kotlin

    @Module
    @InstallIn(ActivityComponent::class)
    objectModuleInstallModule{
    @Provides
    funprovideModuleInstallClient(
    @ActivityContextcontext:Context
    ):ModuleInstallClient=ModuleInstall.getClient(context)
    }

    Java

    @Module
    @InstallIn(ActivityComponent.class)
    publicclass ModuleInstallModule{
    @Provides
    publicstaticModuleInstallClientprovideModuleInstallClient(
    @ActivityContextContextcontext){
    returnModuleInstall.getClient(context);
    }
    }
  3. Inject the ModuleInstallClient in the activity:

    Kotlin

    @AndroidEntryPoint
    classMyActivity:AppCompatActivity(){
    @InjectlateinitvarmoduleInstallClient:ModuleInstallClient
    ...
    }

    Java

    @AndroidEntryPoint
    publicclass MyActivityextendsAppCompatActivity{
    @InjectModuleInstallClientmoduleInstallClient;
    ...
    }
  4. Replace the binding in test:

    Kotlin

    @UninstallModules(ModuleInstallModule::class)
    @HiltAndroidTest
    classMyActivityTest{
    ...
    privatevalcontext:Context=ApplicationProvider.getApplicationContext()
    privatevalfakeModuleInstallClient=FakeModuleInstallClient(context)
    @BindValue@JvmField
    valmoduleInstallClient:ModuleInstallClient=fakeModuleInstallClient
    ...
    }

    Java

    @UninstallModules(ModuleInstallModule.class)
    @HiltAndroidTest
    class MyActivityTest{
    ...
    privatestaticfinalContextcontext=ApplicationProvider.getApplicationContext();
    privatefinalFakeModuleInstallClientfakeModuleInstallClient=newFakeModuleInstallClient(context);
    @BindValueModuleInstallClientmoduleInstallClient=fakeModuleInstallClient;
    ...
    }

Simulate different scenarios

With FakeModuleInstallClient, you can simulate different scenarios, such as:

  • Modules are already installed.
  • Modules are not available on the device.
  • The installation process fails.
  • The deferred install request is successful or fails.
  • The urgent install request is successful or fails.

Kotlin

@Test
funcheckAvailability_available(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset()
valavailableModule=TfLite.getClient(context)
fakeModuleInstallClient.setInstalledModules(api)
// Verify the case where modules are already available...
}
@Test
funcheckAvailability_unavailable(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset()
// Do not set any installed modules in the test.
// Verify the case where modules unavailable on device...
}
@Test
funcheckAvailability_failed(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset()
fakeModuleInstallClient.setModulesAvailabilityTask(Tasks.forException(RuntimeException()))
// Verify the case where an RuntimeException happened when trying to get module's availability...
}

Java

@Test
publicvoidcheckAvailability_available(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
fakeModuleInstallClient.setInstalledModules(api);
// Verify the case where modules are already available...
}
@Test
publicvoidcheckAvailability_unavailable(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
// Do not set any installed modules in the test.
// Verify the case where modules unavailable on device...
}
@Test
publicvoidcheckAvailability_failed(){
fakeModuleInstallClient.setModulesAvailabilityTask(Tasks.forException(newRuntimeException()));
// Verify the case where an RuntimeException happened when trying to get module's availability...
}

Simulate result for a deferred install request

Kotlin

@Test
fundeferredInstall_success(){
fakeModuleInstallClient.setDeferredInstallTask(Tasks.forResult(null))
// Verify the case where the deferred install request has been sent successfully...
}
@Test
fundeferredInstall_failed(){
fakeModuleInstallClient.setDeferredInstallTask(Tasks.forException(RuntimeException()))
// Verify the case where an RuntimeException happened when trying to send the deferred install request...
}

Java

@Test
publicvoiddeferredInstall_success(){
fakeModuleInstallClient.setDeferredInstallTask(Tasks.forResult(null));
// Verify the case where the deferred install request has been sent successfully...
}
@Test
publicvoiddeferredInstall_failed(){
fakeModuleInstallClient.setDeferredInstallTask(Tasks.forException(newRuntimeException()));
// Verify the case where an RuntimeException happened when trying to send the deferred install request...
}

Simulate result for an urgent install request

Kotlin

@Test
funinstallModules_alreadyExist(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
fakeModuleInstallClient.setInstalledModules(api);
// Verify the case where the modules already exist when sending the install request...
}
@Test
funinstallModules_withoutListener(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
// Verify the case where the urgent install request has been sent successfully...
}
@Test
funinstallModules_withListener(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
// Generates a ModuleInstallResponse and set it as the result for installModules().
valmoduleInstallResponse=FakeModuleInstallUtil.generateModuleInstallResponse()
fakeModuleInstallClient.setInstallModulesTask(Tasks.forResult(moduleInstallResponse))
// Verify the case where the urgent install request has been sent successfully...
// Generates some fake ModuleInstallStatusUpdate and send it to listener.
valupdate=FakeModuleInstallUtil.createModuleInstallStatusUpdate(
moduleInstallResponse.sessionId,STATE_COMPLETED)
fakeModuleInstallClient.sendInstallUpdates(listOf(update))
// Verify the corresponding updates are handled correctly...
}
@Test
funinstallModules_failed(){
fakeModuleInstallClient.setInstallModulesTask(Tasks.forException(RuntimeException()))
// Verify the case where an RuntimeException happened when trying to send the urgent install request...
}

Java

@Test
publicvoidinstallModules_alreadyExist(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
OptionalModuleApioptionalModuleApi=TfLite.getClient(context);
fakeModuleInstallClient.setInstalledModules(api);
// Verify the case where the modules already exist when sending the install request...
}
@Test
publicvoidinstallModules_withoutListener(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
// Verify the case where the urgent install request has been sent successfully...
}
@Test
publicvoidinstallModules_withListener(){
// Reset any previously installed modules.
fakeModuleInstallClient.reset();
// Generates a ModuleInstallResponse and set it as the result for installModules().
ModuleInstallResponsemoduleInstallResponse=
FakeModuleInstallUtil.generateModuleInstallResponse();
fakeModuleInstallClient.setInstallModulesTask(Tasks.forResult(moduleInstallResponse));
// Verify the case where the urgent install request has been sent successfully...
// Generates some fake ModuleInstallStatusUpdate and send it to listener.
ModuleInstallStatusUpdateupdate=FakeModuleInstallUtil.createModuleInstallStatusUpdate(
moduleInstallResponse.getSessionId(),STATE_COMPLETED);
fakeModuleInstallClient.sendInstallUpdates(ImmutableList.of(update));
// Verify the corresponding updates are handled correctly...
}
@Test
publicvoidinstallModules_failed(){
fakeModuleInstallClient.setInstallModulesTask(Tasks.forException(newRuntimeException()));
// Verify the case where an RuntimeException happened when trying to send the urgent install request...
}

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026年09月01日 UTC.