Swapping resource values at runtime

from the CommonsWare Community archives

At December 28, 2021, 8:36pm, Raptor asked:

Hello,

I have a situation where in my app you can select a particular type of “path” and the string resources, later on, should respond by containing different things (while keeping the same string resource names). Is there a possibility to change the string resources’ (and other resources’) values at runtime? Like say you have string R.string.hello be both “hi” and “hello” at the same time and it will be “hi” if you choose path A and “hello” if you choose path B through the app.

Something I’m currently reading on about this is: Runtime Resource Overlays (RROs)  |  Android Open Source Project

Thanks!


At December 28, 2021, 11:14pm, mmurphy replied:

Not really.

I don’t think that an ordinary app can cause an RRO change – AFAIK, historically, that required root. That is because it is a system-level change, not just something for your app. And, it feels like that’s swatting a fly with a low-yield nuclear weapon.

If the string resources are being applied via Java/Kotlin code, this feels like you have some sort of StringSupplier that knows about the “path” and can switch between different sets of string resources. If the string resources are also being applied via layout resources… you might be able to pull something off by using a custom ContextWrapper with the LayoutInflater, so you can override getString() to run it through the StringSupplier.


At December 29, 2021, 8:29am, Raptor replied:

So you’re telling me there’s a chance?!

Do you have any short example of that available? Would this work for other resources such as icons as well?


At December 29, 2021, 12:45pm, mmurphy replied:

I do not know what “that” is. Here is a short StringSupplier in Kotlin:

private val NORMAL_STRINGS = mapOf(R.string.foo to R.string.foo, R.string.bar to R.string.bar)
private val ALT_STRINGS = mapOf(R.string.foo to R.string.alt_foo, R.string.bar to R.string.alt_bar)
private val STRINGS_MAP_BY_PATH = mapOf("normal" to NORMAL_STRINGS, "alt" to ALT_STRINGS)

class StringSupplier {
  var path = "normal"

  fun getMappedStringId(id: Int): Int? = STRINGS_MAP_BY_PATH[path][id]
}

If getMappedStringId() returns null, either your path or your id is invalid. Otherwise, would then pass the result of getMappedStringId() to whatever function you are using to resolve your string resources now (e.g., setText(Int) on TextView). You switch paths by updating the path var.


At December 29, 2021, 1:12pm, Raptor replied:

This looks really useful, thank you.


At December 30, 2021, 7:07pm, Raptor replied:

It works, but in my situation I have this scenario:

How do you see it approached? It seems this is the more complicated situation as the getString() method is all the way down to the Context class and that’s quite a way down from the higher level Activity and Fragment classes. You mentioned something about a ContextWrapper and a LayoutInflater but…

EDIT: I found something promising, here: Taming Android Resources and LayoutInflater for string manipulation - Jitin Sharma


At December 30, 2021, 7:57pm, mmurphy replied:

I would wait until my project adopted Compose UI and layout resources went away. :grin:

Yes, that is the sort of thing that I had in mind, though it is more complicated than I was thinking that it would be. :disappointed:


At December 30, 2021, 8:17pm, Raptor replied:

I tried that approach and I got stuck in here:

override fun cloneInContext(newContext: Context?): LayoutInflater {
        return LayoutInflater.from(newContext)
    }

It asks me to implement the cloneInContext method, which in turn throws an infinite runtime error which originates in my CustomLayoutInflater:

2021-12-30 21:06:13.302 7352-7352/? E/AndroidRuntime:     at androidx.appcompat.view.ContextThemeWrapper.getSystemService(ContextThemeWrapper.java:163)
        at android.view.LayoutInflater.from(LayoutInflater.java:282)

This is so damn complicated.