Site icon AppTractor

Использование строковых ресурсов в ViewModel

При создании приложений для Android одним из распространенных «подводных камней» является использование строковых ресурсов во ViewModel.

В этой статье мы выясним, почему включение строковых ресурсов непосредственно в ViewModel может вызвать проблемы, и рассмотрим альтернативные методы их решения.

Одним из способов использования строк во ViewModel является использование AndroidViewModel:

class MainViewModel(application: Application) : AndroidViewModel(application) {
    fun getString(): String? {
        return getApplication<Application>().resources.getString(R.string.sample_string)
    }
}

Такой подход имеет ряд недостатков

  1. Разделение задач: ViewModel в основном отвечает за управление состоянием и поведением компонентов пользовательского интерфейса в Android-приложении, в то время как строки и другие ресурсы обычно относятся к слою View. Смешивание строк с логикой ViewModel нарушает принцип разделения задач и может привести к созданию более тесно связанной и менее удобной в обслуживании кодовой базы.
  2. Тестируемость и модульное тестирование: ViewModel должны быть легко тестируемыми. Однако использование строковых ресурсов в ViewModel может затруднить написание подробных модульных тестов для этих компонентов, поскольку они требуют контекста.
  3. Viewmodel не будет пересоздаваться при изменении локали: Если строковый ресурс используется в конструкторе ViewModel, то он ресолвится только один раз. Это может вызвать проблему при изменении локали, так как ViewModel не будет пересоздана. В результате приложение может отображать устаревшие данные и не будет полностью локализовано.

Рекомендуемый подход заключается в том, чтобы решать задачу, связанную со строковыми ресурсами, из слоя пользовательского интерфейса. Представление (активити, фрагмент) будет пересоздано после изменения конфигурации, поскольку оно ориентировано на жизненный цикл. Это означает, что ресурс будет перезагружен корректно, а также мы можем не использовать контекст во ViewModel.

Для этого нам необходим sealed класс:

sealed class StringValue {

    data class DynamicString(val value: String) : StringValue()

    object Empty : StringValue()

    class StringResource(
        @StringRes val resId: Int,
        vararg val args: Any
    ) : StringValue()

    fun asString(context: Context?): String {
        return when (this) {
            is Empty -> ""
            is DynamicString -> value
            is StringResource -> context?.getString(resId, *args).orEmpty()
        }
    }
}

Во ViewModel:

private val _logMessage by lazy { MutableLiveData<StringValue>() }
val logMessage: LiveData<StringValue>
    get() = _logMessage

Обновление live data с указанием идентификатора ресурса:

_logMessage.postValue(StringResource(R.string.invalid_type))

В вашем фрагменте/активити, внутри live data observer  используйте его следующим образом:

 logMessage.observe(this@MainActivity) { 
            debug(TAG, it.asString(this@MainActivity))
        }

Это избавляет от необходимости использования контекста во ViewModel, и даже динамические строки, полученные из API, могут быть легко обработаны 🙌🏼👍🏼.

Надеюсь, кому-то это будет полезно и облегчит жизнь. Счастливого кодинга! 👨‍💻

Источник

Exit mobile version