Based on Android App Bundle: Launch Activity from Dynamic Feature Modules, I shall reorganize the code in a more reusable manner via ViewModel and helper class.
ViewModel
- Install Dynamic Modules
class DailyQuoteViewModel(app: Application) : AndroidViewModel(app) { companion object { const val QUOTE_MAKER_MODULE = "quotemaker" } private val splitInstallManager = SplitInstallManagerFactory.create(getApplication()) private var sessionId = 0 var needSplitCompatInstall = !isQuoteMakerInstalled() private val listener = SplitInstallStateUpdatedListener { state -> if (state.sessionId() == sessionId) { when (state.status()) { SplitInstallSessionStatus.FAILED -> { Timber.d("Module install failed with ${state.errorCode()}") processCallbacks(false) } SplitInstallSessionStatus.INSTALLED -> { processCallbacks(true) } else -> Timber.d("Status: ${state.status()}") } } } init { splitInstallManager.registerListener(listener) } override fun onCleared() { splitInstallManager.unregisterListener(listener) super.onCleared() } private fun isQuoteMakerInstalled() = splitInstallManager.installedModules.contains(QUOTE_MAKER_MODULE) private fun requestQuoteMakerInstall() { val request = SplitInstallRequest .newBuilder() .addModule(QUOTE_MAKER_MODULE) .build() splitInstallManager .startInstall(request) .addOnSuccessListener { id -> sessionId = id } .addOnFailureListener { exception -> processCallbacks(false) when ((exception as SplitInstallException).errorCode) { SplitInstallErrorCode.NETWORK_ERROR -> { Timber.e(exception, "splitInstallManager failed - network error") } else -> { Timber.e(exception, "splitInstallManager failed") } } } } private val callbacks = ArrayDeque<(success: Boolean) -> Unit>() private fun processCallbacks(success: Boolean) { while (callbacks.isNotEmpty()) { callbacks.remove().invoke(success) } } fun getQuoteMakerModule(callback: (success: Boolean) -> Unit) { if (isQuoteMakerInstalled()) { callback(true) } else { if (callbacks.size == 0) { requestQuoteMakerInstall() } callbacks.add(callback) } }}
ActivityFactory
- Launch Dynamic Modules's Activity
interface AddressableActivity { val className: String}object ActivityFactory { const val PACKAGE_NAME = "com.luasoftware" const val QUOTE_MAKER_PACKAGE = "$PACKAGE_NAME.quotemaker" fun createIntent(addressableActivity: AddressableActivity): Intent { return Intent(Intent.ACTION_VIEW).setClassName( BuildConfig.APPLICATION_ID, addressableActivity.className) } object QuoteMakerX: AddressableActivity { override val className: String get() = "${QUOTE_MAKER_PACKAGE}.view.QuoteMakerXActivity" const val EXTRA_QUOTE_ID = "quote_id" const val EXTRA_CONTENT = "content" const val EXTTRA_SOURCE = "source" fun createIntent(quoteId: String? = null, content: String? = null, source: String? = null): Intent { return createIntent(this).apply { putExtra(EXTRA_QUOTE_ID, quoteId) putExtra(EXTRA_CONTENT, content) putExtra(EXTTRA_SOURCE, source) } } }}
Usage in Activity
class DailyQuoteActivity : AppCompatActivity() { private val viewModel by viewModels<DailyQuoteViewModel>() override fun attachBaseContext(base: Context) { // if need to access language resources from dynamic modules /* val configuration = Configuration() configuration.setLocale(Locale.forLanguageTag("en")) val context = base.createConfigurationContext(configuration) super.attachBaseContext(context) */ super.attachBaseContext(base) // required to access resources (e.g. drawable) from dynamic modules SplitCompat.install(this) } fun startModuleActivity() { viewModel.getQuoteMakerModule { success -> if (success) { val intent = ActivityFactory.QuoteMakerX.createIntent(quoteId = "ID", content = "CONTENT", source = "SOURCE") startActivity(intent) } } } fun loadDrawableFromModule() { viewModel.getQuoteMakerModule { success -> if (success) { val moduleResourceId = ... if (viewModel.needSplitCompatInstall) { Timber.d("SplitCompat.install") // you can access drawable via applicationContext without SplitCompat.install val d = AppCompatResources.getDrawable(applicationContext, moduleResourceId) // need this to access drawable from module during first install SplitCompat.install(context) viewModel.needSplitCompatInstall = false } // access drawable via activity context require SplitCompat.install // , else Resources.NotFoundException exception is thrown val d = AppCompatResources.getDrawable(context, moduleResourceId) imageView.setImageResource(moduleResourceId) } } }}
References: