For quite a while I was using the wrapper around t...
# android-architecture
g
For quite a while I was using the wrapper around the string resource. In order to delay context involvement. Ex: you have a string int, plus some params for the format, but you're not ready to pull the actual value, because it requires the context. So you delay it with a wrapper.
Copy code
class StringResource private constructor(
    val string: String?,
    @StringRes val stringRes: Int?,
    vararg val args: Any?
) {
    constructor(@StringRes stringRes: Int, vararg args: Any? = emptyArray()) : this(null, stringRes, args)

    constructor(string: String) : this(string, null, null)

    fun resolve(context: Context): String =
        if (stringRes != null) {
            context.getString(stringRes, args)
        } else requireNotNull(string)

  }
=============
Usage: 
ViewModel: StringResource(R.string.foo, arg1, arg2)
View: stringResource.resolve(context)
I'm wondering if other people do something similar and how their wrapper looks like.
👍 1
d
I would put this kind of thing inside a presenter. I always unit test my ViewModel and do not want anything platform specific inside or past the ViewModel. So a presenter seems like a fine place to do context necessary things.
most of the time though I think it's OK for the view to get the correct strings it needs to.
If I need the presenter for this kinda thing then have it observe a ViewState object on the ViewModel and do any string fetching or data mapping it needs to. The view could then observe the final data class and display it correctly.
My
ViewModel
usually gets it's
ViewState
objects from a
Channel
and posts them to a
LiveData<SpecificViewState>
. The
Presenter
uses a
MediatorLiveData<SpecificMappedViewState>
to map the ViewModels data to one containing the final result.
g
You're right, the purpose of this class — to not have context in your ViewModel/Presenter. I guess that's clear. My question was more like: how your version of this class looks like, if you have any?. But yeah, thanks for the remarks.
r
I’ve done a thing like this and liked it a lot. You can use a sealed class to clean it up a bit so you don’t need to have both
string
and
stringRes
as parameters. We also added
plurals
support on my current project. Not open-source unfortunately so I can’t share any code.
g
Awesome! My thoughts exactly, about plurals as well. Thank you.
a
@russhwolf is something like that
Copy code
sealed class StringResource(
    val string: String?,
    val stringRes: Int? = null,
    vararg val args: Any? = emptyArray())
{
    fun resolve(context: Context? = null): String =
        if (stringRes != null) {
            context?.getString(stringRes, args) ?: ""
        } else requireNotNull(string)
}

class StringRes(val i: Int, vararg val a: Any? = emptyArray()) : StringResource(null, i, a)
data class Pure(val s: String) : StringResource(s)


fun main() {
    println(Pure("Hello").resolve())
}
r
Something like that. I wouldn't put
string
and
stringRes
in the base class. You can make
resolve()
abstract and have the subclasses contain only the fields they need and the implementation relevant to them. But that's mostly a style thing.
💯 1
s
We have interface ResourceManager with methods like fun getString(res : Int) which we use in presenters, and concrete implementation AndroidResourceManager(val context : Context), which we inject into presenters.
✔️ 1
d
@sngrekov How does the users of the interface know which Ids to use?
s
We put android resource ids directly into getString method, like resources.getString(R.string.blabla)
d
That's not what I mean
g
yeah,
ResourseManager
is also good.. Similar situation with drawables. Sometimes you'd want to apply some color filter and there comes the context.
s
@dewildte Oh, sorry, must have misunderstood your question. Can you please clarify what do you mean?