Код-ревью Middle MVC, MVP, MVVM, MVI, VIPER обновлено

Код-ревью: view model экрана заказа (Kotlin)

Вопрос

Найдите архитектурные проблемы во view model экрана заказа.

class OrderViewModel(private val context: Context) : ViewModel() {
    var isLoading = false
    var error: String? = null
    var order: Order? = null
    lateinit var view: OrderFragment

    fun load(id: String) {
        isLoading = true
        GlobalScope.launch {
            val response = Retrofit.Builder().baseUrl(BASE).build()
                .create(Api::class.java).order(id)
            order = response
            isLoading = false
            view.render(order!!)
        }
    }
}

Коротко

Правильно: class OrderViewModel @Inject constructor(private val repo: OrderRepository) : ViewModel(), состояние MutableStateFlow<OrderUiState(Loading), загрузка в viewModelScope.launch с try/catch или runCatching, переход в Content или Error. Фрагмент собирает uiState с учётом жизненного цикла и рисует его.

Что должен найти ревьюер

  1. Ошибкастрока 1
    View model держит Context (скорее всего Activity): утечка после поворота. Нужен Application-контекст или, лучше, никаких Android-зависимостей.
  2. Стильстрока 2
    Три независимых поля состояния допускают невозможные комбинации. Нужно одно неизменяемое состояние (sealed class) в StateFlow.
  3. Ошибкастрока 5
    Ссылка на фрагмент: view model переживает view, будет утечка и обращение к уничтоженному экрану. View должна подписываться на состояние.
  4. Ошибкастрока 9
    GlobalScope не отменяется при уходе с экрана. Нужен viewModelScope.
  5. Стильстрока 10
    Создание Retrofit прямо во view model: нет репозитория, нельзя подменить в тестах. Зависимость должна приходить через конструктор (DI).
  6. Ошибкастрока 12
    Ошибки сети не обрабатываются: исключение уронит корутину, а isLoading останется true.

Разбор

Правильно: class OrderViewModel @Inject constructor(private val repo: OrderRepository) : ViewModel(), состояние MutableStateFlow<OrderUiState>(Loading), загрузка в viewModelScope.launch с try/catch или runCatching, переход в Content или Error. Фрагмент собирает uiState с учётом жизненного цикла и рисует его.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.