Хватит использовать ResourceProvider во ViewModel
Alexey Bykov, Google Developer Expert for Android
Каждый по разному обрабатывает показ того или иного текста.
В большинстве своём, картина простая: создаётся ResourcesProvider/ResourceManager/etc с applicationContext внутри и ижектится в Presentation-слой, будь то Presenter или ViewModel.
Проблема
Смена конфигурации
Основная проблема с этим подходом — не 100 процентов кейсов смены конфигурации будут работать хорошо. Например, если вы устанавливаете текст в конструкторе ViewModel, при смене языка, пользователь увидит старый язык, т.к. ViewModel при правильной реализации переживает смену конфигурации.
class BojackHorsemanViewModel(provider: ResourceProvider): ViewModel(){
private val liveData: LiveData<String> = MutableLiveData()
init {
liveData.value = provider.getString(R.string.dont_do_this_please)
}
...
Лишняя сущность
У ResourcesProvider/ResourceManager/etc несколько задач — скрыть Android реализацию, запретить использование других методов у context и сделать Presentation слой тестируемым.
Эту проблему можно довольно просто решить и без её использования в конструкторе ViewModel, что упростит код.
Дизайн система
В больших проектах, для ускорения и стандартизации разработки, принято использовать дизайн систему. Иными словами, набор утверждённых, и главное, переиспользуемых компонентов.

Как мы видим на примере, каждый из компонентов может показывать текст. Однако, это может быть текст из ресурсов, текст, который пришёл с сервера или даже комбинация, где мы подставляем значения с сервера в уже существующую строку.
Без наличия общего решения, которое отвечает за обработку текста, сконфигурировать входящие данные и обработать результат для RowItem будет довольно-таки тяжело.
Решение
Мы можем сделать описание всех необходимых нам кейсов, связанных с текстом:
sealed class Text {
data class Resource(@StringRes val resId: Int) : Text()
data class ResourceParams(
@StringRes val value: Int,
val args:List<Any>
) : Text()
data class Simple(val text: String) : Text()
}
За обработку будет отвечать extension, который отображает текст в TextView. Крайне желательно протестировать его с помощью скриншот-тестов (например, с помощью Paparazzi):
fun TextView.setText(clause: Text) {
when (clause) {
is Text.Resource -> {
setText(clause.resId)
}
is Text.ResourceParams -> {
text = context.getString(clause.value, *clause.args.toTypedArray())
}
is Text.Simple -> {
text = clause.text
}
}
}
Для JetpackCompose, решение так же легко адаптируется, однако придётся переименовать сам sealed класс во что-то другое, например, Clause.
Результат
- ViewModel ничего не знает о ResourceProvider/ResourceManager/etc, смена конфигурации работает как надо
class BojackHorsemanViewModel(): ViewModel(){
private val liveData: LiveData<Text> = MutableLiveData()
init {
liveData.value = Text.Resource(R.string.do_this)
}
...
- ViewModel легко тестируется
- Компоненты дизайн-системы прозрачны и независимы
data class RowItem(val title: Text, ...)
- Решение масштабируемо. Обработка тех же Spannable решается вводом дополнительной абстракции с описанием
Кстати, таким же образом можно обрабатывать и картинки, но об этом, как-нибудь в другой раз.
___________________________________
@invalidate_cache
09-07-2022 Немного про Manifest & Android 12
25-06-2022 Gradle Enterprise & AAB Tests