'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:
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:
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);
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
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 |