Manage availability for on-demand Google Play services modules
Stay organized with collections
Save and categorize content based on your preferences.
Page Summary
-
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
ModuleInstallClientAPI allows for more control over this process. -
ModuleInstallClientenables 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
FakeModuleInstallClientin 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
minSdkVersionof23or higher
Configure your app
In your top-level
settings.gradlefile, include Google's Maven repository and the Maven central repository within thedependencyResolutionManagementblock:dependencyResolutionManagement{ repositories{ google() mavenCentral() } }In your module's Gradle build file (usually
app/build.gradle), add the Google Play services dependencies forplay-services-baseandplay-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.
Get an instance of
ModuleInstallClient:Kotlin
valmoduleInstallClient=ModuleInstall.getClient(context)
Java
ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
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.
Get an instance of
ModuleInstallClient:Kotlin
valmoduleInstallClient=ModuleInstall.getClient(context)
Java
ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
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.
Get an instance of
ModuleInstallClient:Kotlin
valmoduleInstallClient=ModuleInstall.getClient(context)
Java
ModuleInstallClientmoduleInstallClient=ModuleInstall.getClient(context);
(Optional) Create an
InstallStatusListenerto 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
InstallStatusListenerto 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();
Configure the
ModuleInstallRequestand add theOptionalModuleApito 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();
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.
Add dependency:
In your module's Gradle build file (usually
app/build.gradle), add the Google Play services dependencies forplay-services-base-testingin your test.dependencies{ // other dependencies... testImplementation'com.google.android.gms:play-services-base-testing:16.2.0' }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); } }
Inject the
ModuleInstallClientin the activity:Kotlin
@AndroidEntryPoint classMyActivity:AppCompatActivity(){ @InjectlateinitvarmoduleInstallClient:ModuleInstallClient ... }
Java
@AndroidEntryPoint publicclass MyActivityextendsAppCompatActivity{ @InjectModuleInstallClientmoduleInstallClient; ... }
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... }