3

From the Kotlin Fundamentals course, we have this code:

@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight?) {
    item?.let {
        setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
}

In other languages I would simplify this by using the sleepQuality integer to look up the matching element, in Typescript for example:

setImageResource(R.drawable[`ic_sleep_${item.sleepQuality}`] ?? R.drawable.ic_sleep_active)

To start trying this out even my first step doesn't compile:

0 -> R.drawable["ic_sleep_0"] // doesn't compile

Is this kind of operation possible in Kotlin?

Edit/Update

There's a few good responses here.

It looks like for this specific use case, I can look up resources by string, similar to what I'm trying:

val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)

However, this is not a general solution. The following does not work:

val x = item['sleepQuality']

As noted in some responses, this may be possible using reflection. How would this be done?

Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129
  • 2
    Hi, interesting, perhaps use [reflection](https://stackoverflow.com/questions/35525122/kotlin-data-class-how-to-read-the-value-of-property-if-i-dont-know-its-name-at) – IronMan Jul 31 '20 at 17:50
  • I don't know the objects you're using here, but would there be a way of using an array or list — types _designed_ for indexed access — instead of a series of separate properties? – gidds Jul 31 '20 at 18:48
  • @gidds yes this example, being numbered, would be better as an array (can you have a resource array?) but my question, generally, remains. – Jonathan Tuzman Jul 31 '20 at 19:29
  • I'm not sure where that code comes from or if it was written like that on purpose to showcase a switch, but I'd just point out it'd be cleaner to just use a fixed `LevelListDrawable` and modify its level. Solutions using reflection or `getIdentifier` by string do not provide compile time safety so they should be generally avoided. – Pawel Jul 31 '20 at 19:36
  • `resources.getIdentifier` is using reflection under the hood. Note that using reflection should be avoided, as it throws away the benefits of using a strongly typed language (the compiler catches your errors before you even run your code). You wouldn't need this `when` statement if you stored your drawables in an array in the first place. – Tenfour04 Jul 31 '20 at 20:45

2 Answers2

3
val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)
setImageResource(if (resId != 0) resId else R.drawable.ic_sleep_active)

Through reflection (based on Getting value of public static final field/property of a class in Java via reflection) :

val resId = try {
    R.string::class.java.getField("ic_sleep_${item.sleepQuality}").getInt(null)
} catch (e: Exception) {
    R.string.ic_sleep_active
}
setImageResource(resId)
IR42
  • 8,587
  • 2
  • 23
  • 34
2

Only using reflection. Kotlin statically typed programming language and does not support "Variable variables"

Sergey Bulavkin
  • 2,325
  • 2
  • 17
  • 26
  • 3
    Also worth pointing out that, while it might seem attractive, reflection is rarely a good solution for general-purpose programming in a statically-typed language like Kotlin.  Of course, it can be necessary and very useful when writing frameworks, plugins, tests, compile-time tools, dependency injection, and suchlike.  But it bypasses the type system and all the usual compile-time checks and optimisations, and performs badly.  If you find yourself reaching for it, it's often a hint that there's a better way of structuring your code. – gidds Jul 31 '20 at 18:46