A context aware string abstraction for Android
Ask yourself a fairly standard question for any interview to fill an Android position: How would you provide a String from a ViewModel to display it with the View? This is not only a theoretical problem, but rather an applied one which surfaces in every project. Most solutions are fairly simple as well and result in either sending the String directly or providing a @StringRes int string resource reference to resolve with the View, where both have their specific downsides and shortcomings. If you need the String to be locale aware, the ViewModel requires access to the Context (or rather the application context), while string resources only provide static values predefined and localized with the application.
When the requirements start to exceed these simple use-cases, you’ll find yourself writing abstractions for and around these solutions. You might need to format a string with a string resource, you might need to provide plurals depending on quantities provided by your API or you might even need to mix both, strings provided as an error from a backend and string resources for predefined or unknown error states. Eventually your ViewModel will know about the Context or your View needs to learn how to handle these abstractions, which you’d like to avoid (or rather I’d like to avoid in my projects).
The Java String type
The simple and fairly static solution is to provide a simple CharSequence like a String or Spannable and set it as text.
textView.setText("string")
There’s no big magic going on here. Depending on the kind of text view, though the CharSequence might get wrapped into a more specific type like Editable.
The Android String resource
Text views and similar widgets also provide overloads with @StringRes int parameters instead of the standard CharSequence.
textView.setText(R.string.resource)
The magic involved here is that these methods just wrap the context.getString(stringRes) method to create the CharSequence to provide to the original method.
Android String resource abstraction
When it comes to providing a mix of String and string resources a common pattern is to provide both with a default value within the same entity. Depending on the availability of the provided property, your View would just use one or the other then.
data class ErrorMessage(
@StringRes val messageId: Int = 0,
val message: String? = null,
)
fun ErrorMessage.getMessage(context: Context) =
message ?: context.getString(messageId)
To support formatting argument or quantities, the entity could be extended further. Alternatively it would be possible to provide a sealed class to handle all the different use-cases here depending on the actual implementation.
Job Offers
The Android String type
To generalize the approach Android took for string resources, I created the context aware parcelable string type abstraction AString with the single function invoke(Context): CharSequence? – written in Java for Kotlin.
The core library itself implements types to support null-values, simple Java CharSequence types (including String and Spannable), as well as string and text resources, with and without formatting arguments or quantities.
Furthermore I defined Kotlin extension functions as overloads for all methods usually taking a CharSequence directly. So it’s not necessary to provide a CharSequence or string resource directly with a view model to be used within the view implementation.
textView.setText("value".asAString())
textView.setText(StringResource(R.string.value))
The AString interface is defined to be extensible, so that it’s also possible to encapsulate formatting logic and small conversions or access information directly from the Context provided to the function.
@Parcelize
data class LocalDateFormatAString(
private val date: LocalDate,
private val pattern: String,
) : AString {
override fun invoke(context: Context) =
date.format(DateTimeFormatter.ofPattern(pattern))
}
@Parcelize
object LocaleAString : AString {
override fun invoke(context: Context) =
context.resources.configuration.locales[0].toString()
}
So there’s only a single type now to provide to the View and use as if it was a simple string.



