0

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'
}
MaKi
  • 263
  • 5
  • 11

2 Answers2

2

It is a know issue that some custom ROMs derived from Cyanogen 6 and 6.0.1 have issues loading resources from split APKs.

Not much you can do on your end except not enabling splitting by screen density and language.

Could you please file a bug with Google with the list of devices that you have noticed have issues?

Pierre
  • 15,865
  • 4
  • 36
  • 50
  • Currently i have only two devices right now, and i have submitted the issue as well. and i didn't enable splitting by screen density and language as you can see on my shared code. – MaKi Dec 03 '19 at 07:28
  • Splitting by screen density and language is enabled by default, you need to explicitly disable it if you don't want it. – Pierre Dec 03 '19 at 11:51
  • According to https://developer.android.com/studio/build/configure-apk-splits "Generating per-language APKs is supported only for Android Instant App projects" and for density splitting the enabled flag "the default value is false" (i.e. it is disabled by default) – straya Jan 07 '20 at 07:12
  • You are talking about a different product where you would generate the APKs yourself each with a different versionCode. The Android App Bundle works differently: you upload one App Bundle and Play will generate the APKs on your behalf. With the AAB, language, density and ABI splitting are all enabled by default. I hope that clarifies it. – Pierre Jan 07 '20 at 08:37
1

Write condition in manifest to download dynamic module on install time for sdk<=23

<dist:module
    dist:instant="false"
    dist:title="@string/chat">
    <dist:delivery>
        <dist:on-demand />
        <dist:install-time>
            <dist:conditions>
                <dist:min-sdk dist:value="21" />
                <dist:max-sdk dist:value="23" />
            </dist:conditions>
        </dist:install-time>
    </dist:delivery>
    <dist:fusing dist:include="true" />
</dist:module>
Harish Gyanani
  • 1,366
  • 2
  • 22
  • 43