Not at all.
Resource string manipulation belongs the View layer, not ViewModel layer.
ViewModel layer should be free from dependencies to both Context
and resources. Define a data type (a class or enum) that ViewModel will emit. DataBinding has access to both Context and resources and can resolve it there. All you need is a plain static method that takes the enum and Context
and returns String
:
fun someEnumToString(type: MyEnum?, context: Context): String? {
return when (type) {
null -> null
MyEnum.EENY -> context.getString(R.string.some_label_eeny)
MyEnum.MEENY -> context.getString(R.string.some_label_meeny)
MyEnum.MINY -> context.getString(R.string.some_label_miny)
MyEnum.MOE -> context.getString(R.string.some_label_moe)
}
}
(File is named MyStaticConverter.kt
, so in Java and XML inline Java it's referred as "MyStaticConverterKt").
Usage in XML - context
is a synthetic param, available in every binding expression:
<data>
<import type="com.example.MyStaticConverterKt" />
</data>
...
<TextView
android:text="@{MyStaticConverterKt.someEnumToString(viewModel.someEnum, context)}".
It may seem like "too much code in XML", but XML and bindings are the View layer. The only place for view logic - if you reject god-objects: Activities and Fragments.
In most cases, String.format
is enough to combine resource string format with other data emitted by ViewModel. For more complicated cases (like mixing resource labels with texts from API) instead of enum use sealed class that will carry the dynamic String
from ViewModel to the converter that will do the combining.
For the simplest cases, like the question, there is no need to invoke Context
explicitly at all. The built-in adapter already interprets binding int to text as string resource id. The tiny inconvenience is that when invoked with null
the converter still must return a valid ID, so you need to define some kind of placeholder like <string name="empty" translatable="false"/>
.
@StringRes
fun someEnumToString(type: MyEnum?): Int {
return when (type) {
MyEnum.EENY -> R.string.some_label_eeny
MyEnum.MEENY -> R.string.some_label_meeny
MyEnum.MINY -> R.string.some_label_miny
MyEnum.MOE -> R.string.some_label_moe
null -> R.string.empty
}
}
Yes, technically you could emit a @StringRes Int
directly from a ViewModel, but that would make your ViewModel dependent on resources, so I strongly advise against it.
"Converters" (a collection of unrelated, static and stateless functions) is a pattern that I use a lot. It allows to keep all the Android's View
-related types away from ViewModel and reuse of small, repetitive parts across entire app (like converting bool or various states to VISIBILITY or formatting numbers, dates, distances, percentages, etc). That removes the need of many overlapping @BindingAdapter
s and IMHO increases readability of the XML-inline Java.