'Access graphics from drawable folder of a dynamic feature module from main module

I'm getting my feet wet with dynamic module split API delivery in order to break up my game app into Instant and Installable versions. I've been following the Codelabs tutorial here https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/index.html#0. Unfortunately it uses Kotlin for the MainActivity code, which is less specific than Java, but still fairly followable if you've done a Kotlin tutorial. The example includes accessing a text tile in an 'assets' folder in an 'assets' feature module with the following:

private const val packageName = "com.google.android.samples.dynamicfeatures.ondemand"

val assetManager = createPackageContext(packageName, 0).assets
// Now treat it like any other asset file.
val assets = assetManager.open("assets.txt")
val assetContent = assets.bufferedReader()
           .use {
               it.readText()
           }

For now I just want to access graphic files in a drawable folder of my dynamic feature module. I'll only be using my dynamic feature module to store large graphics that would take me over the 10 MG limit for an Instant app download. What would be the cleanest way to do this?

Main 'app' module:

enter image description here

Java code in 'app':

loadTexture(R.drawable.aaa_image);

Bitmap bitmap;
public void loadTexture(final int resourceId){
    bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
    ***

Dynamically delivered 'installationassets' module:

enter image description here

Still java code in 'app', won't reach:

 loadTexture(R.drawable.testgraphic);
 cannot resolve symbol 'testgraphic'


Solution 1:[1]

I found a working solution to load drawables from a feature module resources. The trick is to use the correct package name for the module.

Example:

  • base module package name is "com.project"
  • feature module name defined in build.gradle "FeatureModule"
  • feature module package name "com.project.FeatureModule"

build.gradle

dynamicFeatures = [":FeatureModule"]

Java

int drawableResId = context.getResources().getIdentifier("your_drawable_name", "drawable", "com.project.FeatureModule");
context.getDrawable(drawableResId);

Reference article

Solution 2:[2]

Traditionally you cannot access resources from another module, just assets (which are stored raw). Now apparently you can, but it's messy. The whole point of the split API arrangement, however, is that you could indeed access code and resources of all parts (modules) like they where one. I found this is true for the assets folder, as you don't need to dynamically create a new context for the dynamic module reference. I found if you happen to have the same titled asset in your main and dynamic instant module assets folders, it is pulled from the dynamic module.

However, I've still not been able to pull from the dynamic module resources (R), but I'll offer a workaround in answer to my own question until I find an example or get a better answer. You can put your image files in the assets folder of your dynamic module instead, and then pull and convert them as follows:

loadTextureResource("testimage.png");//include file type (.png)

Bitmap bitmap;
public void loadTextureResource(String imagename){

    ImageView mImage=new ImageView(context);
    InputStream ims;

    try {
        // get input stream
        ims = context.getAssets().open(imagename);
        // load image as Drawable
        Drawable d = Drawable.createFromStream(ims, null);
        // set image to ImageView
        mImage.setImageDrawable(d);

        bitmap = ((BitmapDrawable)mImage.getDrawable()).getBitmap();
        ***

Solution 3:[3]

A refinement of the answer that @pumnao posted, which I still managed to find a bit confusing. Maybe this will help the next weary traveler.

If your app / base build.gradle includes this:

dynamicFeatures = [':dynamicfeature']

Then you can load resources from dynamicfeature like this:

val splitManager = SplitInstallManagerFactory.create(context)
val featureName = "dynamicfeature"
val installed = splitManager.installedModules.contains(featureName)

if (installed) {
  val refreshedContext = context.createPackageContext(context.packageName, 0)
  val packageName = "${context.applicationContext.packageName}.$featureName"

  val drawableId = refreshedContext.resources.getIdentifier(
    "your_drawable_name", "drawable", packageName
  )
  val drawable = refreshedContext.getDrawable(drawableId)
}

Where the following is the interesting line. Note that we use the packageName of the applicationContext for the prefix.

val packageName = "${context.applicationContext.packageName}.$featureName"

Solution 4:[4]

A com.android.application module is not meant to access com.android.dynamic-feature module's resources as the dynamic-feature module's resource table is not accessible to the application.

Solution 5:[5]

It must be an Vector path. for me it works like this

ic_effect_oval.xml

 fun Context.moduleRes(drawable_name: String):Int{
    val refreshedContext = createPackageContext(packageName, 0)
    val packageName = "${applicationContext.packageName}.$module_name"
    return refreshedContext.resources.getIdentifier(
        drawable_name, "drawable", packageName
    )
}

imageView

 val intRes = context.moduleRes("ic_effect_oval")
 your_ImageView.setImageResource(intRes)
 your_ImageView.setColorFilter(R.color.black)     

or in composables

 val context = LocalContext.current
 val intRes = context.moduleRes("ic_effect_oval")
   Icon(
       modifier = Modifier
       .size(100.dp),
       painter = painterResource(id = intRes),
       contentDescription = animDescription,
       tint = Color.Blue
   )

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pumnao
Solution 2
Solution 3
Solution 4 keyboardsurfer
Solution 5 AllanRibas