Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resources$NotFoundException for XML vector drawable on API 19

I'm trying to load an XML vector drawable with the following code:

int px = Application.get().getResources()
                .getDimensionPixelSize(R.dimen.bikeshare_small_marker_size);

Bitmap bitmap = Bitmap.createBitmap(px, px, Bitmap.Config.ARGB_8888);

Canvas c = new Canvas(bitmap);
Drawable shape = ContextCompat.getDrawable(Application.get(), R.drawable.bike_marker_small);
shape.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
shape.draw(c);

Here is the bike_marker_small.xml file:

<vector android:height="24dp" android:viewportHeight="210.0"
    android:viewportWidth="210.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#ffffff"
        android:pathData="M105.03,104.97m-53.84,0a53.84,53.84 117.34,1 1,107.67 0a53.84,53.84 117.34,1 1,-107.67 0"
        android:strokeAlpha="1" android:strokeColor="#3a4677" android:strokeWidth="1.46500003"/>
    <path android:fillAlpha="1" android:fillColor="#3a4677"
        android:pathData="M105.1,104.67m-47.53,0a47.53,47.53 118.07,1 1,95.06 0a47.53,47.53 118.07,1 1,-95.06 0"
        android:strokeAlpha="1" android:strokeColor="#000000" android:strokeWidth="0.26458332"/>
</vector>

On Android 6, 7, and 8 this works fine. However, when I try to run the app on an API 19 (Android 4.4) emulator or device, I get the following:

 android.content.res.Resources$NotFoundException: File res/drawable/bike_marker_small.xml from drawable resource ID #0x7f08005f. 
 If the resource you are trying to use is a vector resource, you may be referencing it in an unsupported way. 
 See AppCompatDelegate.setCompatVectorFromResourcesEnabled() for more info.
      at android.content.res.Resources.loadDrawable(Resources.java:2101)
      at android.content.res.Resources.getDrawable(Resources.java:700)
      at android.support.v4.content.ContextCompat.getDrawable(ContextCompat.java:353)
      at org.onebusaway.android.map.googlemapsv2.bike.BikeStationOverlay.createBitmapFromShape(BikeStationOverlay.java:195)
      at org.onebusaway.android.map.googlemapsv2.bike.BikeStationOverlay.(BikeStationOverlay.java:87)
      at org.onebusaway.android.map.googlemapsv2.BaseMapFragment.setupBikeStationOverlay(BaseMapFragment.java:474)

I have the following in my build.gradle:

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 21
        versionCode 86
        versionName "2.3.1"

        vectorDrawables.useSupportLibrary = true
    }
...

Why isn't this working?

like image 732
Sean Barbeau Avatar asked Jan 16 '18 16:01

Sean Barbeau


2 Answers

Trying creating the drawable shape using VectorDrawableCompat.create() instead of ContextCompat.getDrawable():

Drawable shape = VectorDrawableCompat.create(
    Application.get().getResources(),
    R.drawable.bike_marker_small, 
    Application.get().getTheme()
);
like image 100
Sean Barbeau Avatar answered Sep 21 '22 12:09

Sean Barbeau


Answer from Sean Barbeau is defintely great, although I'd like to add something that happened to me and that could cause troubles to other people trying to figure out what's going on with VectorDrawable on API 19 or API 20.

I created a method to obtain the VectorDrawable as Drawable for cases when I don't need/want to use them as Drawable, but as Bitmap, for example. This method would work for Android versions >= API 19.

This is the Kotlin method (look at the end for the improved version):

fun getDrawable(context:Context, drawableId: Int): Drawable {
    var drawable: Drawable?

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable = VectorDrawableCompat.create(context.resources, drawableId, context.theme)
    } else {
        drawable = ContextCompat.getDrawable(context, drawableId)
    }

    return drawable
}

Basically, I pass the context (since this method would be a helper method and will be in a class without a Context or an Activity) and the resource ID of the VectorDrawable.

It could happen, that you directly call this helper method with the resource ID using the R namespace, like this:

ImageUtils.Companion.getDrawable(
    getBaseContext(),
    R.drawable.your_vector_drawable
)

However, it could be as well that you generate the resource ID dynamically, similar to:

@DrawableRes val resourceID = context.resources.getIdentifier(
    "resource_prefix_" + type,
    "drawable",
    context.packageName
)

And then call to the helper method, in the same way as before, using the resourceID variable:

ImageUtils.Companion.getDrawable(
    getBaseContext(),
    resourceID
)

If you do it this way, while on API < 21, the dynamically generated resource ID would most likely be a PNG. And the helper method will fail, because it expected a XML (SVG). You can fix it by requesting a normal Drawable (and not a VectorDrawable), by catching the NotFoundException that will be raised and just getting a Drawable. This is the final helper method:

fun getDrawable(context:Context, drawableId: Int): Drawable {
    var drawable: Drawable?
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        try {
            drawable = VectorDrawableCompat.create(context.resources, drawableId, context.theme)
        } catch (ex: Resources.NotFoundException) {
            drawable = ContextCompat.getDrawable(context, drawableId)
        }
    } else {
        drawable = ContextCompat.getDrawable(context, drawableId)
    }

    return drawable
}
like image 24
xarlymg89 Avatar answered Sep 20 '22 12:09

xarlymg89