Хватит использовать 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