https://kotlinlang.org logo
Title
w

Will Nixon

12/18/2019, 10:45 PM
Hi all, I need to access drawables via template literals inside a RecycleView.ViewHolder like:
"@drawable/${formatString(entry)}"
Any thoughts on the best way to do this?
b

Ben Abramovitch

12/18/2019, 10:51 PM
What are you trying to do that you want to reference the drawable that way and not via the resource ID? You aren't writing xml in the view holder? E.g. Why not imageView.setImageResource(entry) Where entry is R.drawable.whatever
w

Will Nixon

12/18/2019, 10:57 PM
So basically entry might be “blue sky” and I want to assign the drawable “blue_sky” to that specific cell in the recyclerView. I have a list of entries and need to assign the related drawable image to each imageview, if that makese sense?
b

Ben Abramovitch

12/18/2019, 10:57 PM
Why is it blue sky and not R.id.blue_sky though?
w

Will Nixon

12/18/2019, 10:58 PM
I have an array of strings and am effectively wanting to create images for each item in that string without needing to hardcode R.id.XXXX and also right the string itself beneath the image
b

Ben Abramovitch

12/18/2019, 10:59 PM
What method are you applying this "@drawable/${formatString(entry)}" to? I'm under the impression that there is no programatic way to do this. That's only doable with XML
I'm pretty sure you'll have to pass around the ID and the name you want for it, or the name and do a lookup for the ID seperately.
w

Will Nixon

12/18/2019, 11:23 PM
I had found this recommendation on stackoverflow, but my guess is it’s super deprecated haha…
val uri = "@drawable/${formatString(entry)}"
            val imageResource : Int = context.getResources().getIdentifier(uri, null, context.getPackageName())
            val res : Drawable = context.getResources().getDrawable(imageResource)
I guess a dictionary look-up could work. ie. my_entry: “My entry”, and using the key (my_entry) as the R.id ?
b

Ben Abramovitch

12/18/2019, 11:24 PM
Wow, I didn't know that was a thing, but using a dictionary lookup would be the better way IMO. That looks risky and unreliable vs a hard coded lookup
w

Will Nixon

12/18/2019, 11:26 PM
It’s not really working so I plan to avoid it haha. I’ll go down a dictionary-lookup route. Is “R.id.XXX” the only way to grab a drawable outside of an XML or can you do something like getDrawable(XXX) somehow? Just trying to figure out what the dictionary would need to look like
And thanks for your help @Ben Abramovitch - first time playing with Kotlin so trying to translate things I’ve done them in Swift!
b

Ben Abramovitch

12/18/2019, 11:29 PM
No worries, glad to help. You can use ContextCompat.getDrawable(context, R.drawable.id) to get a drawable, and then imageView.setDrawable() this might be useful if you want to do something to it before showing it. Or you can use the setImageResource(R.id.drawable) Where do the names come from? I'd want to avoid a situation where the name doesn't always correspond to a id
you could use a sealed class to type it all?
sealed class ResourceImages(@StringRes val name: Id, @DrawableRes val id: Int) {
     object BlueSky : ResourceImages(R.string.blue_sky, R.drawable.image)
 object RedSky : ResourceImages(R.string.red_sky, R.drawable.image)
}
Then you could just call the update method and go textView.setText(resourceImage.name) imageView.setImage(resourceImage.id)
and if the names come from the server a companion object with a getResourceImage(name: String) that returns the correct one based off the server name, with an unknown when no strings match
return when(name) { "blue sky" -> BlueSky "red sky" -> RedSky } This way your strings are also localized if you support multiple languages
w

Will Nixon

12/18/2019, 11:37 PM
At the moment, all the names are stored in string-arrays
That’s all really helpful, thank you. Don’t fully understand it but you’ve given me plenty to go away and wrap my head around
b

Ben Abramovitch

12/18/2019, 11:44 PM
If you haven't used sealed classes before, they're kinda like swifts fancier enums. You can do exhaustive switch (when) statements on them and they force you do things in a very specific way. So when you call a function updateImage(image: ImageResource) you know it's the correct type
w

Will Nixon

12/18/2019, 11:45 PM
Oh nice!
b

Ben Abramovitch

12/18/2019, 11:45 PM
highly recommend you check them out even if it doesn't work out here, very very very useful, and helps clean up the code
w

Will Nixon

12/18/2019, 11:46 PM
Yeah I for sure will, thank you!
p

pt

12/18/2019, 11:59 PM
A sealed class seems like overkill, you can also probably use an enum like
enum class Image(name: String, id: Int) {
  BLUE_SKY("blue_sky", R.drawable.something)
}
b

Ben Abramovitch

12/19/2019, 12:01 AM
I tend to use sealed classes as they can be parcelized, and they restore easier than enums. It lets you pass them around, and gives the future flexability of one of them being slightly different. What if there's a generic sky class that can take in a color which gets used to tint it?
👍 1
Errr. I meant they restore easier from onSavedInstanceState and bundles if you needed to persist it
w

Will Nixon

12/19/2019, 12:29 AM
Love this, it’s super helpful! I was trying to do everything programmatically as I have hundreds of these entry instances that I’m trying to keep the hard-coding of to a minimum, but it sounds like I may not be able to do it quite as easily as I could in Swift
b

Ben Abramovitch

12/19/2019, 12:33 AM
Oi hundreds is a lot. Assuming you were able to get the original way working, you'd still have a problem with the way you had your string arrays, since the name had to match the image label. You wouldn't have been able to localize the strings that way, but hundreds is a lot to configure manually ....
p

pt

12/19/2019, 12:36 AM
All good points! Kotlin enums giveth and kotlin enums taketh away
b

Ben Abramovitch

12/19/2019, 12:38 AM
if you go the enum/sealed class route, I'd probably use a spreadsheet to write out all the code around the name id and image id... save you some typing
stringId, imageId, className -> "object $ClassName : Type($stringId, $imageId) kinda thing, then copy the result into the IDE
nevermind, that might be a bad idea, not sure if it's clean and easy enough to get the data to do that
p

pt

12/19/2019, 12:44 AM
MyFirstCodeGenerator.xls
😂 2
b

Ben Abramovitch

12/19/2019, 12:45 AM
I've used the technique before when I had to transpose content, enter 3-4 fields, get a full line of code, but this isn't really transposing so there isn't the same manual work to begin with
Like i dunno could you write a script to do the following... Take all the files in the drawable folder. Remove all the underscores and that's your english string, and the ID is the same as the file name. Create a strings file record for that. The sealed class/enum name is the drawable name with _ removed. Use the file name with R.string and R.drawable in front of it for the ids?
remove all the records that you don't actually want.
w

Will Nixon

12/19/2019, 1:55 PM
Love all this, thank you so so much for taking the time to think through a solution. At the moment, I think it might well be a case of hardcoding everything (and using something like Excel, or creating a simple script, to spit out the written values so I can just copy / paste into an enum). To some extent, any updates thereafter will just be simple adding/removing entries. Ultimately that data has to live somewhere (it’s not being fetched from anywhere else) so if there’s no way to dynamically create ‘R.id.whatever’ dynamically with template-literals, then this sounds about right