পোস্টটি পড়া হয়েছে 18 বার
android dynamic feature delivery

Mastering Android Dynamic Feature Delivery (2/3)

In my last post, I discussed the basic understanding of Android on-demand feature module delivery and its project setup. Now, in this post, we will implement the core functionalities of the dynamic module.

3 parts of this blog series

Full Source Code — GitHub.Com

Create dynamic module Activity

My dynamic module is named ‘customer_support’ module. However, it’s not my objective to develop a real customer support chat or audio/video call feature in this sample project. For the sake of simplicity, I have included six large images (~18 MB) in CustomerSupportActivity.kt. Using these large images, we will clearly understand the effect of the dynamic module.

Android on demand dynamic feature devlivery module example tutorial
Our Sample dynamic module app UI design

MainActivity.kt (app module)

In my app module, I have only one activity — MainActivity.kt. I simply added a banner image of the eCommerce app and then included a Customer Support button. When a user taps on the Customer Support button for the first time, they will see an alert dialog to confirm downloading the dynamic module. Upon clicking the Download button, MainActivity.kt will start downloading the customer_support module from the Google Play Store. After downloading and installing, MainActivity.ktwill receive a few callbacks. Based on those callbacks, the activity will redirect the user to CustomerSupportActivity.kt.

Add a simple button in MainActivity to open the dynamic module Activity. After clicking the button, we will check if the dynamic module is already downloaded. If yes, then open the dynamic module Activity. Otherwise, we will request to download the dynamic module using our utility class.

When our utility class tries to download and completes the process, successfully installing the new module, our app module’s MainActivity will be notified by a callback.

class MainActivity : ComponentActivity(), DynamicModuleListener {

    private val CUSTOMER_SUPPORT_DYNAMIC_MODULE = "customer_support"
    private lateinit var dynamicModuleDownloadUtil: DynamicModuleDownloadUtil
    private var logState = mutableStateOf("Activity Log:\n")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        dynamicModuleDownloadUtil = DynamicModuleDownloadUtil(baseContext, this)

        // ui codes
    }

override fun onDownloading() {
        logState.value += "${getCurrentTimestamp()}: Downloading...\n"
    }

    override fun onDownloadCompleted() {
        logState.value += "${getCurrentTimestamp()}: Module download completed.\n"
    }

    override fun onInstallSuccess() {
        logState.value += "${getCurrentTimestamp()}: Module install Success!\n"
        startCustomerSupportActivity()
    }

    override fun onFailed() {
        logState.value += "${getCurrentTimestamp()}: Module download or installation failed.\n"
    }

    private fun openCustomerSupportFeature() {
        if (dynamicModuleDownloadUtil.isModuleDownloaded(CUSTOMER_SUPPORT_DYNAMIC_MODULE)) {
            logState.value += "${getCurrentTimestamp()}: Module is already downloaded.\n"
            startCustomerSupportActivity()
        } else {
            dialogState.value = true
        }
    }

    private fun startCustomerSupportActivity() {
        val intent = Intent()
        intent.setClassName(
            "com.hellohasan.hasanerrafkhata",
            "com.hellohasan.customer_support.CustomerSupportActivity"
        )
        startActivity(intent)
    }
}

DynamicModuleDownloadUtil.kt

You can simply copy and paste this Util class in your project.

const val TAG = "dynamic_module_util"

interface DynamicDeliveryCallback {
    fun onDownloading()
    fun onDownloadCompleted()
    fun onInstallSuccess()
    fun onFailed(errorMessage: String)
}

class DynamicModuleDownloadUtil(context: Context, private val callback: DynamicDeliveryCallback) {

    private lateinit var splitInstallManager: SplitInstallManager
    private var mySessionId = 0

    init {
        if (!::splitInstallManager.isInitialized) {
            splitInstallManager = SplitInstallManagerFactory.create(context)
        }
    }

    fun isModuleDownloaded(moduleName: String): Boolean {
        return splitInstallManager.installedModules.contains(moduleName)
    }

    fun downloadDynamicModule(moduleName: String) {
        val request = SplitInstallRequest.newBuilder()
            .addModule(moduleName)
            .build()

        val listener = SplitInstallStateUpdatedListener { state -> handleInstallStates(state) }
        splitInstallManager.registerListener(listener)

        splitInstallManager.startInstall(request)
            .addOnSuccessListener { sessionId ->
                mySessionId = sessionId
            }
            .addOnFailureListener { e ->
                Log.d(TAG, "Exception: $e")
                handleInstallFailure((e as SplitInstallException).errorCode)
            }

        splitInstallManager.unregisterListener(listener)
    }

    private fun handleInstallFailure(errorCode: Int) {
        when (errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                callback.onFailed("No internet found")
            }

            SplitInstallErrorCode.MODULE_UNAVAILABLE -> {
                callback.onFailed("Module unavailable")
            }

            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> {
                callback.onFailed("Active session limit exceeded")
            }

            SplitInstallErrorCode.INSUFFICIENT_STORAGE -> {
                callback.onFailed("Insufficient storage")
            }

            SplitInstallErrorCode.PLAY_STORE_NOT_FOUND -> {
                callback.onFailed("Google Play Store Not Found!")
            }

            else -> {
                callback.onFailed("Something went wrong! Try again later")
            }
        }
    }

    private fun handleInstallStates(state: SplitInstallSessionState) {
        if (state.sessionId() == mySessionId) {
            when (state.status()) {
                SplitInstallSessionStatus.DOWNLOADING -> {
                    callback.onDownloading()
                }

                SplitInstallSessionStatus.DOWNLOADED -> {
                    callback.onDownloadCompleted()
                }

                SplitInstallSessionStatus.INSTALLED -> {
                    Log.d(TAG, "Dynamic Module downloaded")
                    callback.onInstallSuccess()
                }

                SplitInstallSessionStatus.FAILED -> {
                    callback.onFailed("Installation failed")
                }

                SplitInstallSessionStatus.CANCELED -> {
                    callback.onFailed("Installation Cancelled")
                }
            }
        }
    }

}

Full Source code of the project

There is another Kotlin file for the alert dialog composable widget. You can easily understand the full flow after exploring the entire project. Please check here for the full source code:

Android Dynamic Feature Module Full Source Code

In the next post, we will explore the internal app sharing testing procedure. I will also share a demo video of internal app sharing. Check out the next part here.

2 thoughts on “Mastering Android Dynamic Feature Delivery (2/3)

Leave a Reply

Your email address will not be published. Required fields are marked *