Using Vector Drawable Safely on pre-lollipop version

Vector Drawable - offically support trên android version 5.0 (API level 21)vector graphic định nghĩa trong một file XML bởi tập các points, lines, curves cùng với thông tin về màu sắc tương ứng. Điểm lợi thế của nó là khả năng scalability. Nó có thể scale mà không bị mất chất lượng, điều đó có nghĩa là chỉ cần 1 file Vector Drawable, bạn có thể resize cho các màn hình với density khác nhau mà không bị mất chất lượng hình ảnh. Rõ ràng điều này giúp cho kích thước của file APK giảm đáng kể.

Bạn có thể tìm hiểu thêm nhiều thông tin về loại drawable này tại đây .

Vì lợi ích đặc biệt của nó nên dù ra đời từ android 5.0, nhưng google vẫn cho phép các version android trước đó sử dụng thông qua các gói support. Tuy nhiên, trong lúc sử dụng cho các version trước đó, có một vài điểm cần lưu ý. Trong bài viêt này, mình xin tóm tắt vài tips để tránh những issues lúc sử dụng Vector Drawable cho android 4.4 (pre-lollipop).

build.gradle

Để hỗ trợ cho android pre-lollipop bạn cần enable vectorDrawable config trong build.grale của module app. Lưu ý, đây là điều bắt buộc.

android {
   defaultConfig {
     vectorDrawables.useSupportLibrary = true
    }
 }

Setting in Application class

override fun onCreate() {
    super.onCreate()
    // App crashes when selector with using vector-drawable
    // So, keep using this line + AppCompatImageView for android 4.4
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
    // Your other logic code ...
}

Đây là option không bắt buộc, mặc định sẽ là false, bởi vì enable nó có thể gây những issue về sử dụng memory, và khi update đối tượng Configuration. Nếu bạn thay đổi configuration bằng code, thì tốt nhất là không nên enable feature này.

Còn lý do để thêm dòng này là bạn có thể bị crash trên 4.4 nếu vô tình tạo 1 selector có chứa reference đến một Vector Drawable

VD:

<selector xmlns:android="...">
     <item android:state_checked="true"
           android:drawable="@drawable/vector_checked_icon" />
     <item android:drawable="@drawable/vector_icon" />
 </selector>

=> Vì thế, hãy cân nhắc trường hợp của ứng dụng trước khi sử dụng option này.

Way to getDrawable programmatically

Có nhiều cách để get và tạo Vector Drawable thông qua gói support.

// using ContextCompat
ContextCompat.getDrawable(@NonNull Context context, @DrawableRes int resId)
// using ResourcesCompat
ResourcesCompat.getDrawable(@NonNull Resources res, @DrawableRes int id, @Nullable Theme theme)
// using AppCompatResources
AppCompatResources.getDrawable(@NonNull Context context, @DrawableRes int resId)
// using VectorDrawableCompat
VectorDrawableCompat.create(@NonNull Resources res, @DrawableRes int resId, @Nullable Theme theme)

Tuy nhiên, với mỗi cách khác nhau, trong các điều kiện khác nhau có thể gây ra những lỗi không mong muốn. Đặc biệt trong trường hợp làm custom view nếu bạn cho phép các attribute reference đến resource là một drawable (normal drawable hoặc vector drawable). Hãy xem kết quả:

Normal image resource (non-vector)

Kind Set setCompatVectorFromResourcesEnabled Not Set
ContextCompat Good Good
ResourcesCompat Good Good
AppCompatResources Good Good
VectorDrawableCompat Crashed Crashed

Vector drawable resource

Kind Set setCompatVectorFromResourcesEnabled Not Set
ContextCompat Good Crash API < 21
ResourcesCompat Good Crash API < 21
AppCompatResources. Good Good
VectorDrawableCompat Good Good

Với cả Normal hay Vector Drawable resource, pre-lollipop hay không, chỉ có AppCompatResources.getDrawable(...) là an toàn nhất để getDrawable().

Dưới đây là ví dụ về cách custom view có thể dùng cho bất kì drawable nào:

const val INVALID_RES_ID = -1
private fun initAttrs(attrs: AttributeSet?) {
    if (isInEditMode || attrs == null) return
    var drawableStart: Drawable? = null
    var ta: TypedArray? = null
    try {
        ta = context.obtainStyledAttributes(attrs, R.styleable.YourCustomView)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // Directly call getDrawable is safely
            drawableStart = typedArray.getDrawable(...)
        } else {
            val drawableStartId = typedArray.getResourceId(..., INVALID_RES_ID)
            if (drawableStartId != INVALID_RES_ID)
                drawableStart = AppCompatResources.getDrawable(context, drawableStartId)
        }
        // other logic
    } finally {
        ta?.recycle()
    }
}

Ngoài ra, lúc tạo custom view, mình khuyên các bạn nên extends các class từ gói support như AppCompatImageView, AppCompatTextView,... Vì bản thân các component trong gói support có hỗ trợ tốt hơn trên những device pre-lollipop version.

Happy Coding !