I am following google sample for using dynamic delivery feature in my app. my sample app works fine on pie but crashes on marshmallow 6.0.1 custom rom as my tab does not support official update after kitkat 4.4.4. i uploaded the app (.abb) on internal test, download from play store on my mobile (os pie) and my tab (os marshmallow), i run the app on both, download the dynamic module, after successfully downloaded, i launch the module, module launches on Pie but Crashes on Marshmallow with resource not found exception. after some research someone in stackoverflow says i just update the version code and reupload the app and it works fine, so i tried with just modify the version code and test it work fine on marshmallow.
can someone help me out please what i am missing here?
Manifest (Dynamic Module)
<?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"
xmlns:tools="http://schemas.android.com/tools"
package="com.testdynamicdelivery.ondemand.kotlin_test">
<dist:module
dist:instant="false"
dist:title="@string/title_kotlin_test">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
<application
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.testdynamicdelivery.ondemand.TestKotlin">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
</application>
</manifest>
Build Gradle File (Dunamic Module)
apply plugin: 'com.android.dynamic-feature'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':app')
}
TestKotlin Activity (Dynamic Module)
class TestKotlin : AppCompatActivity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// Emulates installation of on demand modules using SplitCompat.
SplitCompat.installActivity(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_kotlin)
}
}
Application File (Base App)
class MyApplication : SplitCompatApplication() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
SplitCompat.install(this)
}
}
BaseSplitActivity (Base App)
abstract class BaseSplitActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(newBase)
SplitCompat.install(this)
}
}
Main Activity (Base App)
private const val CONFIRMATION_REQUEST_CODE = 1
private const val PACKAGE_NAME = "com.testdynamicdelivery"
private const val PACKAGE_NAME_ONDEMAND = "$PACKAGE_NAME.ondemand"
private const val KOTLIN_SAMPLE_CLASSNAME = "$PACKAGE_NAME_ONDEMAND.TestKotlin"
class MainActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
SplitCompat.install(this)
}
/** Listener used to handle changes in state for install requests. */
private val listener = SplitInstallStateUpdatedListener { splitInstallSessionState ->
when (splitInstallSessionState.status()) {
SplitInstallSessionStatus.PENDING ->{
sb.append("\nPending Feature")
setText()
}
SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION ->{
/*
This may occur when attempting to download a sufficiently large module.
In order to see this, the application has to be uploaded to the Play Store.
Then features can be requested until the confirmation path is triggered.
*/
manager.startConfirmationDialogForResult(splitInstallSessionState, this, CONFIRMATION_REQUEST_CODE)
}
SplitInstallSessionStatus.DOWNLOADED ->{
sb.append("\nFeature Downloaded.")
setText()
}
SplitInstallSessionStatus.CANCELING ->{
sb.append("\nCanceling Feature.")
setText()
}
SplitInstallSessionStatus.INSTALLED -> {
sb.append("\nFeature Installed.")
setText()
}
SplitInstallSessionStatus.CANCELED ->
{
sb.append("\nCanceled Feature.")
setText()
}
SplitInstallSessionStatus.DOWNLOADING ->
{
sb.append("\nDownloading Feature...")
progressBar.max = splitInstallSessionState.totalBytesToDownload().toInt()
progressBar.progress = splitInstallSessionState.bytesDownloaded().toInt()
setText()
}
SplitInstallSessionStatus.INSTALLING ->
{
sb.append("\nInstalling Feature...")
setText()
}
SplitInstallSessionStatus.UNKNOWN ->
{
sb.append("\nError: ${splitInstallSessionState.errorCode()} for module ${splitInstallSessionState.moduleNames()}")
setText()
}
SplitInstallSessionStatus.FAILED ->
{
sb.append("\nFailed ${splitInstallSessionState.errorCode()} , ${splitInstallSessionState.moduleNames()}")
setText()
}
}
/*if (splitInstallSessionState.sessionId() == mySessionID) {
}*/
}
/** This is needed to handle the result of the manager.startConfirmationDialogForResult
request that can be made from SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION
in the listener above. */
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CONFIRMATION_REQUEST_CODE) {
// Handle the user's decision. For example, if the user selects "Cancel",
// you may want to disable certain functionality that depends on the module.
if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(context, "You Canceled To Download!", Toast.LENGTH_LONG).show()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private lateinit var context: Context
private lateinit var manager: SplitInstallManager
private lateinit var progressBar: ProgressBar
private var mySessionID: Int = 0
private val sb = StringBuilder()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
manager = SplitInstallManagerFactory.create(this)
progressBar = findViewById(R.id.progress_bar)
sb.append("Logs: Version: ${BuildConfig.VERSION_NAME}, Code: ${BuildConfig.VERSION_CODE}")
setText()
setClickListeners()
}
private fun displayProgress() {
progressBar.visibility = View.VISIBLE
}
private fun setClickListeners()
{
btn_feature_kotlin.setOnClickListener {
if (manager.installedModules.contains("kotlin_test"))
{
sb.append("\nStarting $KOTLIN_SAMPLE_CLASSNAME")
setText()
launchActivity(KOTLIN_SAMPLE_CLASSNAME)
}
else
{
sb.append("\nDownloading Feature Kotlin")
setText()
downloadDynamicModule("kotlin_test")
}
}
}
override fun onResume() {
// Listener can be registered even without directly triggering a download.
manager.registerListener(listener)
super.onResume()
}
override fun onPause() {
// Make sure to dispose of the listener once it's no longer needed.
manager.unregisterListener(listener)
super.onPause()
}
private fun downloadDynamicModule(module: String) {
displayProgress()
val request = SplitInstallRequest
.newBuilder()
.addModule(module)
.build()
manager.registerListener(listener)
manager.startInstall(request)
.addOnFailureListener {
sb.append("\nException: ${it.message}")
setText()
println("Exception: $it")
}
.addOnSuccessListener { sessionId ->
sb.append("\nSuccess getting session id: $sessionId")
setText()
mySessionID = sessionId
}
.addOnCompleteListener {
sb.append("\naddOnCompleteListener")
setText()
}
}
private fun setText()
{
tv_logs.text = sb.toString()
scroll_view.post { scroll_view.fullScroll(ScrollView.FOCUS_DOWN) }
}
/** Launch an activity by its class name. */
private fun launchActivity(className: String) {
val intent = Intent().setClassName(BuildConfig.APPLICATION_ID, className)
startActivity(intent)
}
}
Build Gradle File (Base App)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId 'com.testdynamicdelivery.ondemand'
minSdkVersion 19
targetSdkVersion 29
versionCode 28
versionName '1.2.8'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
dynamicFeatures = [":features:kotlin_test"]
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Play Core
api 'com.google.android.play:core:1.6.4'
// Support Library
//implementation 'com.android.support:appcompat-v7:28.0.0'
// Android X Libraries
api 'androidx.appcompat:appcompat:1.1.0'
api 'androidx.core:core-ktx:1.1.0'
api 'androidx.legacy:legacy-support-v4:1.0.0'
api "androidx.lifecycle:lifecycle-extensions:2.1.0"
api 'androidx.constraintlayout:constraintlayout:1.1.3'
api 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
api 'androidx.gridlayout:gridlayout:1.0.0'
api 'androidx.cardview:cardview:1.0.0'
api 'androidx.recyclerview:recyclerview:1.1.0'
}