Skip to content

Use WebexSDK as onDemand Module (using Android Dynamic Feature Module)

ciscoRankush edited this page Jun 20, 2023 · 4 revisions

Introduction

What is Dynamic Feature module?

Google Play’s app serving model uses Android App Bundles to generate and serve optimized APKs for each user’s device configuration, so users download only the code and resources they need to run your app. Play Feature Delivery uses advanced capabilities of app bundles, allowing certain features of your app to be delivered conditionally or downloaded on demand.To do that, first we need to separate these features from your base app into feature modules.

The benefits of Dynamic Delivery also allow you to modularize app features that aren't required at install time by adding dynamic feature modules to your app project and including them in your app bundle. Through Dynamic Delivery, your users can then download and install your app's dynamic features on-demand. However, creating on-demand modules requires more effort and possible refactoring of your app. So, consider carefully which of your app's features would benefit the most from being available to users on-demand.

More details on Dynamic feature module can be found here

Cisco Webex Android SDK

The Cisco Webex Android SDK makes it easy to integrate and secure messaging, meeting and calling features in your Android apps.

SDK Variants

  • Calling SDK: WebexSDK-Wxc
    • Supports only WebexCalling feature
    • It does not support CUCM calling.
  • Meeting SDK : WebexSDK-Meeting
    • This SDK supports Messaging and Meeting features It does not support CUCM Calling and Webex Calling

All the SDKs are independent of each other. Developers can use either one of them to fulfil their use case.

Prerequisites

Step 1: Create a Dynamic Feature Module

  • Select File > New > New Module from the menu bar AS. In the Create New Module dialog, select Dynamic Feature Module and click Next.
  • Select the base module from the dropdown
  • Provide the Dynamic module's name and package name. And click Next.
  • Provide Module title. It is good to use the same name which has been given while creating module.
  • Select the Install time inclusion as : on-Demand only .
  • And check the Fusing box if you want this module to be available to devices running Android 4.4 (API level 20) - and lower and include it in multi-APKs
  • Click Finish to finish the setup. Now wait for AS to finish the sync.

Step 2: Configure WebexSDK in the Dynamic Feature Module

Dynamic feature module : build.gradle

Verify if IDE has applied the configuration correctly

  • com.android.dynamic-feature plugin has to be added in module's build.gradle file.
  • What not to include:
    • Signing configurations: App bundles are signed using signing configurations of the base module.
    • The minifyEnabledproperty: 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.
    • meta-data: Currently in Gradle 7.x meta data in feature modules are not supported FULLY. Include any kind of meta data in base module only.
    • Add WebexSDK dependency for dynamic module , so that it will be downloaded with this dynamic module
    • Additionally, base module’s dependency should added to the dynamic module. implementation project(':app').

The dynamic module's build.gradle should look like

apply plugin: 'com.android.dynamic-feature'
android {
 // Configure dynamic feature module-specific settings here
}

dependencies {
    // Add dependencies specific to the dynamic feature module here
    implementation project(':app') // Add the base module's dependency to the dynamic module
 	implementation files('libs/sample.aar') // any local libs
 	implementation 'com.ciscowebex:webexsdk-wxc:3.9.0' // Webex calling SDK
}

Dynamic feature module : AndroidManifest.xml

<?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.ciscowebex.androidsdk.kitchensink.dynamicfeature"
    split="dynamicfeature">
    <dist:module
        dist:onDemand="true"
        dist:title="@string/title_module">
        <dist:fusing include="true" />
    </dist:module>
</manifest>
  • split="split_name": Defines the name of the module, used when requesting an on-demand module using the Play Core Library.
  • dist:module: XML element specifying packaging and distribution attributes for the module as APKs.
  • dist:onDemand="true|false": Specifies if the module should be available for on-demand download.
  • dist:title="@string/feature_name": Specifies a user-facing title for the module.
  • <dist:fusing include="true|false" />: Specifies whether to include the module in multi-APKs targeting Android 4.4 and lower.

NOTE: If the dynamic feature module does not generate DEX files (no code compiled into DEX format), set android:hasCode to false to avoid runtime errors.

<application 
	android:hasCode="false"> 
	...
</application>

Base module : build.gradle

Verify if IDE has applied the configuration correctly for the base module too.

  • android.dynamicFeatures property should be included in the base module’s build.gradle file android { android.dynamicFeatures = [":dynamicfeature"] }
  • Dependencies which can be re-used in other modules should use the api keyword. So that they can be shared with depedent feature modules.
  • Add play core library to your app. : implementation "com.google.android.play:core:${versions.playcore}"
android {
    signingConfigs {
        debug { ... }
		release {...}
    }
    compileSdk 32
    defaultConfig {
        applicationId "app id"
        minSdk 24
        targetSdk 32
        versionCode 1
        versionName "1.0"
	}
	
    // Add dynamic features here
 	dynamicFeatures = [":dynamicfeature"]
}

dependencies {
     implementation 'androidx.annotation:annotation:1.1.0'
	
	// Shared libs (with other feature modules)
    api 'androidx.appcompat:appcompat:1.1.0'
    api 'androidx.constraintlayout:constraintlayout:1.1.3'
    api 'com.google.android.material:material:1.0.0'

    // *** Add the line below
    implementation "com.google.android.play:core:${versions.playcore}" // Play core lib
}

Now lets visualize the dependency graph

Base module : AndroidManifest.xml

In order to enable WebexSDK to collect logs in the SD card at the directory "/sdcard/Android/data/<application.id>/cache/logs", the base module should define a file provider for the SDK in its manifest. It is important to note that, as per the bundling rules, dynamic module providers are not visible to the base module during compile time.

    <application>
        ....
       <provider
            android:name="com.ciscowebex.androidsdk.utils.WebexSdkFileProvider"
            android:authorities="${applicationId}.library.file.provider"
            tools:node="remove">
        </provider>
        ....
    </application>

NOTE: The above entry in base module is required to resolve manifest merge issue for base module, as the WebexSdkFileProvider is part of WebexSDK lib. Adding the corresponding provider to base module will help compiler to add the entry for runtime. Please ignore Android studio error while adding above snippet. While make/build of project, manifest merger will resolve the conflict.

Step 3: Implement the Dynamic Feature Module

Request an on demand module

When your app needs to use a feature module, it can request one while it's in the foreground through the SplitInstallManager class. When making a request, your app needs to specify the name of the module as defined by the split element in the target module’s manifest. When you create a feature module using Android Studio, the build system uses the Module name you provide to inject this property into the module's manifest at compile time.

// Creates an instance of SplitInstallManager.
val installManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // Download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("dynamicfeature") // which contains Webex SDK
        .addModule("otherFeatureModulesIfAny")
        .build()

installManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

NOTE: To have access to the module's code and resources, your app needs to enable SplitCompat.

Handle dynamic module download states

int sessionId = 0;
// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.status() == SplitInstallSessionStatus.FAILED
       && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == sessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:
              //Module is downloaded successfully
              break;
        }
    }
};
// Registers the listener.
installManager.registerListener(listener);

installManager.startInstall(request)
    .addOnSuccessListener(sessionId -> { sessionId = sessionId; })
    .addOnFailureListener(exception -> {
        switch(((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                //Network Error
                break;
            case SplitInstallErrorCode.MODULE_UNAVAILABLE:
                //Module Not Available
                break;
            ...
     });

// When your app no longer requires further updates, unregister the listener.
installManager.unregisterListener(listener);

For more error details, refer here.

Managing dynamic feature modules:

To cancel a module installation request before it is installed, you can use the cancelInstall() method by providing the request's session ID:

splitInstallManager.cancelInstall(mySessionId);

To check the currently installed dynamic feature modules on the device, you can use the getInstalledModules() method:

Set<String> installedModules = splitInstallManager.getInstalledModules(); To uninstall modules, you can invoke the deferredUninstall() method and pass a list of module names:

splitInstallManager.deferredUninstall(Arrays.asList("moduleName"));

NOTE: Make sure to replace "mySessionId" with the appropriate session ID and "moduleName" with the actual name of the module you want to uninstall.

Launching the Webex Module after Successful Installation

To launch the Webex module's LoginActivity after a successful installation, you can follow these steps:

  1. Create an Activity named LoginActivity in the dynamic module. This Activity will serve as the entry point for the dynamic module and Webex SDK.
class LoginActivity : AppCompatActivity() {
    private var webex: Webex? = null
  
    override fun attachBaseContext(newBase: Context?) {
        super.attachBaseContext(newBase)
        SplitCompat.install(this) // This call will enable the split install for this activity.
    }
    
    fun getWebex(email: String? = null): Webex? {
        // Get Webex instance for OAuth
        setWebex(Webex(application, OAuthWebViewAuthenticator(clientId, clientSecret, additionalScopes, redirectUri, email)))
        return webexInstance
    }
}
  1. In the base module, where you want to launch the LoginActivity, create a function to launch the Activity by providing its class name:
private fun launchActivity(className: String) {
    Intent().setClassName(packageName, className)
        .also { intent ->
            startActivity(intent)
        }
}
  1. After a successful installation of the dynamic module, call the launchActivity() function and provide the class name of the LoginActivity.

For example:

val dynamicModuleName = "com.example.dynamicmodule.LoginActivity" // Replace with the actual package and class name of LoginActivity
launchActivity(dynamicModuleName)

Please ensure that you replace "com.example.dynamicmodule.LoginActivity" with the appropriate package and class name of your LoginActivity in the dynamic module.

Important Notes

Under the Android Dynamic Module rule, it is crucial to consider the scenario where the base module cannot directly utilize the code within the dynamic module. Here are some suggested ways to resolve this issue:

  • Communication through interfaces: Define interfaces in the base module that the dynamic module can implement. This allows the base module to interact with the dynamic module through these interfaces, ensuring proper communication and usage of code.

  • Use reflection: Utilize reflection to access the dynamic module's code from the base module. Although this approach comes with some performance overhead, it enables indirect access to the dynamic module's functionality.

  • Shared libraries: Extract common code into shared libraries that both the base module and dynamic module can depend on. By organizing code into shared libraries, you enable code reuse and facilitate communication between the modules.

By implementing these suggested solutions, you can overcome the challenge of the base module not being able to directly use the code within the dynamic module, ensuring seamless integration and collaboration between the modules.

References

Clone this wiki locally