1

I am using Android's Dynamic delivery for one of my feature. I have separated the code for the feature. I am also using the Navigation component in my project.

I can see dynamicfeature being downloaded from the progress bar and after downloading I am using Navigation component to navigate to Fragment2.

However, when I am trying to navigate from Fragment1 which is in my "app" to Fragment2 which is in my "dynamicfeature" I am getting below exception.

Fatal Exception: android.content.res.Resources$NotFoundException: com.sample.sample.debug.dynamicfeature:navigation/dynamic_feature_nav
   at androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.replaceWithIncludedNav(DynamicIncludeGraphNavigator.kt:95)
   at androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.navigate(DynamicIncludeGraphNavigator.kt:79)
   at androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.navigate(DynamicIncludeGraphNavigator.kt:40)
   at androidx.navigation.NavController.navigate(NavController.java:1049)
   at androidx.navigation.NavController.navigate(NavController.java:935)
   at androidx.navigation.NavController.navigate(NavController.java:868)
   at androidx.navigation.NavController.navigate(NavController.java:854)
   at androidx.navigation.NavController.navigate(NavController.java:1107)
   at com.compass.corelibrary.extensions.NavControllerExtensionsKt.navigateSafeSource(NavControllerExtensions.kt:18)

My app's build.gradle file is like this

apply plugin: 'com.android.application'
apply plugin: 'com.google.firebase.firebase-perf'
apply plugin: 'com.heapanalytics.android'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.compass.jacoco.jacoco-android'
apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
apply plugin: 'kotlinx-serialization'

ext.versionMajor = 2
ext.versionMinor = 35
ext.versionPatch = 0
ext.minimumSdkVersion = 21

android {
    compileSdkVersion 31

    defaultConfig {
        applicationId "com.sample.sample"
        minSdkVersion project.ext.minimumSdkVersion
        targetSdkVersion 31
        versionCode Integer.parseInt(project.VERSION_CODE)
        versionName project.VERSION_NAME
        testInstrumentationRunner "com.sample.SampleAndroidJUnitRunner"
        ext {
            heapEnabled = true
            heapAutoInit = true
            heapEnvId = HEAP_KEY_GAMMA
        }
    }
    signingConfigs {
        beta {
            keyAlias "circleci-beta-key-alias"
            keyPassword "password"
            storeFile file("circleci-beta-key.keystore")
            storePassword "password"
        }
    }
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/ASL2.0'
        exclude 'META-INF/kotlinx-coroutines-core.kotlin_module'
        exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
    }
    flavorDimensions "version"
    productFlavors {
        debugFlavor {
            getIsDefault().set(true)
            dimension "version"
            applicationIdSuffix ".debug"
            matchingFallbacks = ["release", "debug"]
            manifestPlaceholders = [
                    auth0Domain: "@string/com_auth0_domain_staging",
                    auth0Scheme: "sample",
                    facebookLoginProtocolScheme: "@string/fb_login_protocol_scheme_staging",
                    facebookAppId: "@string/facebook_app_id_staging",
                    facebookProvider: "@string/facebook_provider_staging"
            ]

        }
        alphaFlavor {
            dimension "version"
            applicationIdSuffix ".alpha"
            matchingFallbacks = ["release", "debug"]
            manifestPlaceholders = [
                    auth0Domain: "@string/com_auth0_domain_staging",
                    auth0Scheme: "sample",
                    facebookLoginProtocolScheme: "@string/fb_login_protocol_scheme_staging",
                    facebookAppId: "@string/facebook_app_id_staging",
                    facebookProvider: "@string/facebook_provider_staging"
            ]
        }
        betaFlavor {
            dimension "version"
            applicationIdSuffix ".beta"
            matchingFallbacks = ["release", "debug"]
            manifestPlaceholders = [
                    auth0Domain: "@string/com_auth0_domain_staging",
                    auth0Scheme: "sample",
                    facebookLoginProtocolScheme: "@string/fb_login_protocol_scheme_staging",
                    facebookAppId: "@string/facebook_app_id_staging",
                    facebookProvider: "@string/facebook_provider_staging"
            ]
        }
        rcFlavor {
            dimension "version"
            applicationIdSuffix ".rc"
            matchingFallbacks = ["release", "debug"]
            manifestPlaceholders = [
                    auth0Domain: "@string/com_auth0_domain_staging",
                    auth0Scheme: "sample",
                    facebookLoginProtocolScheme: "@string/fb_login_protocol_scheme_staging",
                    facebookAppId: "@string/facebook_app_id_staging",
                    facebookProvider: "@string/facebook_provider_staging"
            ]
        }
        playStoreFlavor {
            dimension "version"
            matchingFallbacks = ["release", "debug"]
            manifestPlaceholders = [
                    auth0Domain: "@string/com_auth0_domain_prod",
                    auth0Scheme: "compass",
                    facebookLoginProtocolScheme: "@string/fb_login_protocol_scheme_prod",
                    facebookAppId: "@string/facebook_app_id_prod",
                    facebookProvider: "@string/facebook_provider_prod"
            ]
            ext.heapEnvId = HEAP_KEY_PRODUCTION
        }
    }
    buildTypes {
        debug {
            getIsDefault().set(true)
            debuggable true
            multiDexEnabled true
            signingConfig signingConfigs.beta
            matchingFallbacks = ["release", "debug"]
            buildConfigField "boolean", "ENABLE_LEAK_CANARY", enableLeakCanary
            testCoverageEnabled false
            FirebasePerformance {
                instrumentationEnabled false
            }
        }
        release {
            minifyEnabled true
            shrinkResources true
            productFlavors.alphaFlavor.signingConfig signingConfigs.beta
            productFlavors.betaFlavor.signingConfig signingConfigs.beta
            productFlavors.rcFlavor.signingConfig signingConfigs.beta
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            matchingFallbacks = ["release", "debug"]
        }
    }
    dexOptions {
        javaMaxHeapSize "4g"
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
            // Added to ensure timezone is America/New_York for testing purposes
            all{
                jvmArgs '-Duser.timezone=America/New_York'
                systemProperty 'robolectric.dependency.repo.url', 'https://repo1.maven.org/maven2'

            }
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    sourceSets {
        androidTest { java.srcDirs = ['src/androidTest/kotlin'] }
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }

    buildFeatures {
        viewBinding true
        dataBinding true
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.0.5'
    }
    dynamicFeatures = [':dynamicfeature']

    preBuild.dependsOn ktlintFormat
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //All the dependencies are here.
}

My Dynamic Feature build.gradle looks like

apply plugin: 'com.android.dynamic-feature'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin'

android {
    compileSdk 31

    defaultConfig {
        minSdk 21
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        debug {
            getIsDefault().set(true)
            debuggable true
            matchingFallbacks = ["release", "debug"]
        }
        release {
            matchingFallbacks = ["release", "debug"]
        }
    }
    flavorDimensions "version"
    productFlavors {
        debugFlavor {
            getIsDefault().set(true)
            dimension "version"
            matchingFallbacks = ["release", "debug"]
        }
        alphaFlavor {
            dimension "version"
            matchingFallbacks = ["release", "debug"]
        }
        betaFlavor {
            dimension "version"
            matchingFallbacks = ["release", "debug"]
        }
        rcFlavor {
            dimension "version"
            matchingFallbacks = ["release", "debug"]
        }
        playStoreFlavor {
            dimension "version"
            matchingFallbacks = ["release", "debug"]
        }
    }
    buildFeatures {
        viewBinding true
        dataBinding true
    }
}

dependencies {
    implementation project(':app')
}

I am navigating to Fragment2 which is in "dynamicfeature" from Fragment1 which is in "app". Fragment1 is hosted by MainActivity which is in "app".

My app's navigation graph has entry as

<include-dynamic
        android:id="@+id/me_dynamic_feature"
        app:moduleName="dynamicfeature"
        app:graphResName="dynamic_feature_nav"
        app:graphPackage="${applicationId}.dynamicfeature" />
Code-Warrior
  • 911
  • 9
  • 18
  • So which part of the `com.sample.sample.debug.dynamicfeature:navigation/dynamic_feature_nav` that it is looking for is wrong? Is it the package name or do you not have an XML file at `res/navigation/dynamic_feature_nav.xml` in your feature module? – ianhanniballake Feb 14 '22 at 05:37
  • @ianhanniballake - I can see that dynamicfeature is being downloaded. After downloading I am navigating to Fragment2 using Navigation component. navigation file is present in dynamicfeature navigation folder. In my app's build.gradle I have applicationIdSuffix for each product flavor. Application id is com.sample.sample Package name for main app is com.sample.sample Package name for dynamicfeature module is com.sample.sample.dynamicfeature One thing which I observed is when I host my Fragment2 in an Activity and try to open the activity from Fragment1, then everything works fine. – Code-Warrior Feb 14 '22 at 06:35
  • It seems like `${applicationId}.dynamicfeature` isn't the right thing to put in there if that isn't actually the package name of your dynamic feature module. Have you tried putting in the correct package name there? – ianhanniballake Feb 14 '22 at 06:40
  • @ianhanniballake - I tried putting app:graphPackage="com.sample.sample.dynamicfeature" but it is not working. Now it is not even downloading the dynamic feature module. I got the idea of putting applicationId from this answer - https://stackoverflow.com/questions/64122383/resourcesnotfoundexception-with-include-dynamic-navigation-graph – Code-Warrior Feb 14 '22 at 07:08

1 Answers1

2

I was able to resolve this issue. Apparently, if you use <include-dynamic> tag for navigating into the dynamic feature module. You have to specify ProgressFragment which extends AbstractProgressFragment and specify it as app:progressDestination.

Also since Fragment1 is in base app, which is hosted by activity in base app. We need to override attachBaseContext method in this activity too like below. This will fix the case where same exception was occurring at the subsequent launch.

override fun attachBaseContext(newBase: Context) {
    super.attachBaseContext(newBase)
    SplitCompat.installActivity(this)
}
Code-Warrior
  • 911
  • 9
  • 18
  • As documented here: https://developer.android.com/guide/playcore/feature-delivery/on-demand#enable_splitcompat – linimin Nov 09 '22 at 09:25