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" />
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)
}