Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android dynamic feature module, resource not found

I'm having a problem starting an activity in a downloaded feature module when it's published to the play store. It always crashes on setContentView() in the downloaded modules activity.

java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx/xxxActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7e080000
    at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:227)
    at android.content.res.Resources.loadXmlResourceParser(Resources.java:2149)
    at android.content.res.Resources.getLayout(Resources.java:1158)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:421)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
    at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:469)
    at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)

The really strange part is that if I publish a new version of the app (only change is versionCode) to play store and update the app everything works perfectly.

When I uninstall the app and install it again the crash returns.

my Application is inheriting SplitCompatApplication() and just to be sure I've since tried to add:

override fun attachBaseContext(newBase: Context?) {
        super.attachBaseContext(newBase)
        SplitCompat.install(this)
    }

to the activty in the feature module and disabled proguard to make sure nothing is removed during minify

My SplitInstallStateUpdatedListener

private val listener = SplitInstallStateUpdatedListener { state ->
        val multiInstall = state.moduleNames().size > 1
        state.moduleNames().forEach { name ->
            // Handle changes in state.
            when (state.status()) {
                SplitInstallSessionStatus.DOWNLOADING -> {
                    //  In order to see this, the application has to be uploaded to the Play Store.
                    displayLoadingState(state, "Laddar ner $name")
                }
                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.
                     */
                    startIntentSender(state.resolutionIntent()?.intentSender, null, 0, 0, 0)
                }
                SplitInstallSessionStatus.INSTALLED -> {
                    if(toInstall.isNotEmpty() && toInstall.contains(name)) {
                        toInstall.remove(name)
                    }
                    if(toInstall.isEmpty()) {
                        // Updates the app’s context with the code and resources of the
                        // installed module. (should only be for instant apps but tried it anyway, no change)
                        SplitInstallHelper.updateAppInfo(applicationContext) 
                        Handler().post {
                            viewModel.goToOverview()
                        }
                    }
                }

                SplitInstallSessionStatus.INSTALLING -> displayLoadingState(state, "Installerar $name")
                SplitInstallSessionStatus.FAILED -> {
                    toastAndLog("Error: ${state.errorCode()} for module ${state.moduleNames()}")
                }
            }
        }
    }

This code downloads modules depending on user claims and starts an activity in the base app

The downloaded modules activity is then started from a BottomSheetDialogFragment like this:

xxx.setOnClickListener(view -> {
                    Intent intent = new Intent();
                    String packageName = Constants.MODULE_BASEPACKAGE + "." + Constants.MODULE_XXXXX;
                    intent.setClassName(getActivity().getPackageName(),packageName + ".XxxxxActivity" );
                    ParcelUuid parcelUuid = new ParcelUuid(UUID.randomUUID());
                    intent.putExtra("uuid", parcelUuid);
                    startActivity(intent);
                    dismiss();
                });

I'm all out of ideas about what to try next. It seems like it's something that doesn't update the resource list until an update is installed and a restart of the app is not enough, or am I just missing something simple?

like image 466
Mattias Avatar asked Feb 05 '19 15:02

Mattias


3 Answers

I had an exactly same problem; fresh install crashes with Resources$NotFoundException, but subsequent upgrade works OK (the dynamic module is not downloaded again). But my case was slightly different, because instead of starting an Activity in the dynamic module, I wanted to load a Fragment through Navigation. In that case, I should have just navigated and let Navigation do its thing without manually checking the module was loaded or not (refer to https://developer.android.com/guide/navigation/navigation-dynamic for more info).

// Just navigate without calling splitInstallManager.installedModules.contains()
findNavController().navigate(DynamicDeliveryDirections.actionDynamicFragment())

If you want to start an Activity, you do need to check whether the module is loaded or not, as you are already doing. I suggest you take a look at Google's example, which does exactly what you are trying to do.

https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/index.html?index=..%2F..index#1

As for my case, I had to make sure the package names were correct. For example, if the main module's package name is com.example.foo and dynamic module is com.example.foo.dynamic_activity, then starting the Activity in the dynamic module would look like the following.

Intent().setClassName(
    "com.example.foo",
    "com.example.foo.dynamic_activity.DynamicActivity"
).also {
    startActivity(it)
}
like image 196
solamour Avatar answered Oct 02 '22 16:10

solamour


You can always access the resources from the main project inside the dynamic module, so you could just put your resources for the dynamic module in the main app, and then use the R.java from the main App.

However, the proper way to open these resources is to use SplitCompat.install(this) inside the dynamic delivered activity

like image 20
Adam Boyle Avatar answered Oct 02 '22 17:10

Adam Boyle


This seems to have been a bug in com.android.tools.build:gradle:3.2.1 When I upgraded to 3.3.0 the problem resolved itself.

Hopefully it might help someone else who has this problem...

like image 33
Mattias Avatar answered Oct 02 '22 17:10

Mattias