android / android-ktx

A set of Kotlin extensions for Android app development.
https://android.github.io/android-ktx/core-ktx/
7.47k stars 563 forks source link

Properties to access resources #258

Open andrey-shikhov opened 6 years ago

andrey-shikhov commented 6 years ago

In my project, I've written a lot of extensions for accessing resources in property alike manner, for example: 1) Getting color: Origin: val color = ContextCompat.getColor(context, R.color.color_sample) Result: val color = context.colors[R.color.color_sample]

Implementation:

   class Colors(val context: Context) {
        operator fun get(@ColorRes id: Int): Int = ContextCompat.getColor(context, id)
    } 

    var Context.colors: Colors
           get() = Colors(this)
           private set(value) {}

Same way I did with dimensions, integers, strings, animations, layouts etc. So far, so good. Also for drawables I use unchecked cast to eliminate explicit drawable cast(not safe, but at least no boilerplate which also is not safe anyway) Origin: val shapeDrawable = ContextCompat.getDrawable(context, R.drawable.drawable_sample) as ShapeDrawable Result: val shapeDrawable:ShapeDrawable = context.drawables[R.drawable.drawable_sample] Note: explicit drawable type required only for local variable, if drawable declared as property of view it also can be omitted. Implementation:

class Drawables(val context: Context) {
    @Suppress("UNCHECKED_CAST")
    operator fun <T : Drawable> get(@DrawableRes id: Int): T =
            ContextCompat.getDrawable(context, id) as T
}

var Context.drawables: Drawables
    get() = Drawables(this)
    private set(value) {}
JakeWharton commented 6 years ago

This seems like a feature that should wait for inline classes in Kotlin 1.3 such that we can make the types of these properties zero-overhead wrappers around Resources such that the resulting code doesn't allocate.

jitinsharma commented 6 years ago

How about this

fun Context.getColorCompat(@ColorRes colorInt: Int) : Int =
        ContextCompat.getColor(this, colorInt)

Edit: Never mind. ContextCompat is from support library.

andrey-shikhov commented 6 years ago

To minimize allocation, 1) we can reuse object per block(for example it is onCreateView method of Fragment) 2) we can make singleton object with application context to avoid activity context leak. For first case we can do something like that: client code:

 fun onCreateView(...) {
     with(context.properties) {
            val color = colors[R.color.color_sample]
            ...
      }
}

Implementation:

class Properties(val context: Context) {

    inner class Colors {
        operator fun get(@ColorRes id: Int) = ContextCompat.getColor(context, id)
    }

   val colors = Colors()
 }

var Context.properties: Properties
    get() = Properties(this)
    private set(value) {}
JakeWharton commented 6 years ago

That assumes people will use it in that manner. And in the case where they only want a single resource the allocations have jumped to 2 and the calling semantics are worse:

val color = context.properties.colors[R.color.color_sample]

I'd rather wait for inline classes before trying this out.