Русский
Русский
English
Статистика
Реклама

Из песочницы Так для чего же нам все таки нужен MVI в мобильной разработке

Много уже сказано про MVI, о том как его правильно прожарить и настроить. Однако не так много времени уделяется тому, насколько этот метод упрощает жизнь в определенных ситуациях, в сравнении с остальными подходами.

Цель этой статьи


Я не буду углубляться в то как технически реализуется MVI (способов больше одного и у каждого есть свои плюсы и минусы). Моя главная цель в короткой статье заинтересовать тебя изучать эту тему в дальнейшем и возможно побудить внедрить данный паттерн на своих боевых проектах или хотя бы проверить на домашних заготовках.

С какой проблемой можно столкнуться


Мой дорогой друг, давай представим такую ситуацию, у нас имеется интерфейс вью, с которым
предстоит работать:

interface ComplexView {    fun showLoading()      fun hideLoading()      fun showBanner()       fun hideBanner()       fun dataLoaded(names: List<String>)       fun showTakeCreditDialog()   fun hideTakeCreditDialog()}

На первый взгляд кажется, что ничего сложного. Ты просто выделяешь для работы с этой вьюшкой отдельную сущность, называешь ее презентером (вуаля вот и MVP готов), а это большие проблемы небольшие сложности и сейчас я постараюсь объяснить почему.

А вот и сам презентер:

interface Presenter {     fun onLoadData(dataKey: String)       fun onLoadCredit()}

Все просто, вьюшка дергает методы презентера, когда надо загрузить данные, презентер в свою очередь имеет право дергать вьюшку, для того, чтобы отобразить загруженную информацию, а также отобразить прогресс. Но тут и появляется проблема сложность это абсолютное отсутствие контроля консистентности твоего UI мой товарищ.

К примеру мы хотим вывести диалог предлагающий кредит выгодное предложение пользователю и делаем этот вызов из презентера, имея на руках ссылку на интерфейс вью:

view.hideTakeCreditDialog()


Но при этом тебе не следует забывать, что при отображении диалога, нужно скрывать лоадинг и не показывать его пока у тебя имеется диалог на экране. Плюс к этому есть метод, показывающий баннер, который мы не должны вызывать пока у нас отображается диалог (либо закрывать диалог и уже после этого отображать баннер, все зависит от требований). Картина у тебя складывается такая.

Тебе ни в коем случае нельзя вызывать:

view.showBanner()


view.showLoading()


Пока идет показ диалога. Иначе будут плакать котики тестировщики и пользователи, от боли в глазах от одного взгляда на баннер поверх важного диалога c кредитом выгодным предложением.

А сейчас давай еще подумаем с тобой и предположим, что все таки захотелось показать баннер (такое уж требование от бизнеса). О чем же надо помнить?
Дело в том, что при вызове сего метода:

view.showBanner()


Обязательно надо вызывать:

view.hideLoading()


view.hideTakeCreditDialog()


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

Вот и всплывает вопрос, кто ударит тебя по рукам, если сделаешь что-то не так? Ответ прост НИКТО. В такой реализации у тебя нет абсолютно никакого контроля.

Возможно в будущем понадобится добавить еще какую-либо функциональность во вью, которая также будет связано с тем, что уже есть. Какие минусы мы из этого получаем?

  1. Лапша из зависимостей состояний юайных элементов
  2. Логика переходов из одного состояния отображения в другое будет размазана по
    презентеру
  3. Довольно тяжело добавлять новое состояние экрана, так как велик риск того, что
    забудешь что-то скрыть перед тем как отобразить новый баннер или диалог

И это мы с тобой разбирали кейс когда во вью всего 7 методов. И даже тут получилось встрять на проблемы.

А ведь бывают вот такие вью:

interface ChatView : IView<ChatPresenter> {    fun setMessage(message: String)        fun showFullScreenProgressBar()        fun updateExistingMessage(model: ChatMessageModel)        fun hideFullScreenProgressBar()        fun addNewMessage(localMessage: ChatMessageModel)        fun showErrorFromLoading(message: String)           fun moveChatToStart()        fun containsMessage(message: ChatMessageModel): Boolean        fun getChatMessagesSize(): Int    fun getLastMessage(): ChatMessageModel?        fun updateMessageStatus(messageId: String, status: ChatMessageStatus)        fun setAutoLoading(autoLoadingEnabled: Boolean)    fun initImageInChat(needImageInChat: Boolean)        fun enableNavigationButton()       fun hideKeyboard()       fun scrollToFirstMessage()        fun setTitle(@StringRes titleRes: Int)        fun setVisibleSendingError(isVisible: Boolean)        fun removeMessage(localId: String)        fun setBottomPadding(hasPadding: Boolean)        fun initMessagesList(pageSize: Int)        fun showToast(@StringRes textRes: Int)       fun openMessageDialog(message: String)       fun showSuccessRating()        fun setRatingAvailability(isEnabled: Boolean)        fun showSuccessRatingWithResult(ratingValue: String)}

Добавлять сюда что-либо новое или править старое будет довольно тяжело, придется посмотреть, что и как связано между собой, а потом начать плакать работать и молиться, чтобы тестировщик ничего не пропустил. И в момент твоего отчаяния появляется он.

MVI



image

Вся суть


Суть в том, что мы имеем сущность, которая называется стейт. На основе этого стейта вьюшка будет рендерить свое отображение. Не буду углубляться, так моя задача пробудить в тебе интерес, так что сразу перейду к примерам. А в конце статьи будет список очень полезных источников, если тебя все же появится интерес.

Давай вспомним наше положение в начале статьи, у нас есть вью, на которой мы показываем диалоги, баннеры и волшебство. Опишем ка мы с тобой стейт вьюшки

data class UIState(       val loading: Boolean = false,       val names: List<String>? = null,           val isBannerShowing: Boolean = false,           val isCreditDialogShowing: Boolean = false)

Установим правило, мы с тобой можем менять вью только с помощью этого стейта, будет такой интерфейс:

interface ComplexView {    fun renderState(state: UIState)}

А сейчас установим еще одно правило. Мы можем обращаться к владельцу стейта (в нашем случае это будет презентер) только через одну точку входа. Путем отправления ему событий. Хорошая идея назавать эти события экшенами.

sealed class UIAction {           class LoadNamesAction(dataKey: String) : UIAction()           object LoadBannerAction : UIAction()           object LoadCreditDialogInfo : UIAction()}

Только не кидай в меня помидоры за силд классы, они упрощают жизнь в текущей ситуации, избавляя от дополнительных кастов при обрабокте экшенов в презентере, пример будет ниже. Интерфейс презентера будет выглядеть так:

interface Presenter {    fun processAction(action: UIAction)}

А теперь давай подумаем как связать все это дело:

fun processAction(action: UiAction): UIState {    return when (action) {            is UiAction.LoadNamesAction -> state.copy(                loading = true,                 isBannerShowing = false,                         isCreditDialogShowing = false    )           is UiAction.LoadBannerAction -> state.copy(                            loading = false,                           isBannerShowing = true,                            isCreditDialogShowing = false    )            is UiAction.LoadCreditDialogInfo -> state.copy(                    loading = false,                            isBannerShowing = false,                            isCreditDialogShowing = true    )      }}

Если ты обратил внимание, то перетекание из одного состояния отображения в другое сейчас происходит в одном месте и уже проще сложить в голове картину как все работает.

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

Однако не стоит рано радоваться, у всего в этом мире есть как плюсы так и минусы, а вот и они


  1. Нас ломает об ногу обычный показ toast
  2. При обновлении одного флажка весь стейт будет скопирован заново и отправлен во
    вью, то есть произойдет ненужная перерисовка, если ничего с этим не сделать

Предположим, что мы хотим вывести обычный андроидный toast, по текущей логике мы заведем в нашем стейте флаг для вывода нашего тостика.

data class UIState(       val showToast: Boolean = false,)

Первое


Берем и меняем стейт в презентере, ставим showToast = true и самое простое, что может произойти это поворот экрана. Все уничтожается взрывы и разрушения активити пересоздается, но так как ты крутой разработчик твой стейт все это дело переживает. А в стейте у нас волшебство флаг, который говорит отобразить toast. Результат toast показывается дважды. Для решения данной проблемы есть несколько способов и все выглядят как костыли. Опять же об этом будет написано в источниках, приложенных к этой статье.

Ну, а второе


Это уже проблема ненужных отрисовок во вью, которые будут происходить каждый раз даже когда в стейте меняется всего одно из полей. И эта проблема решается несколькими иногда не самыми красивыми способами (порой тупой проверкой перед тем как сетать во вью новое значение, на то, что оно отличается от предыдущего). Но с выходом compose в stable версию эта проблема будет решена, вот тогда мой друг заживем с тобой в преображенном и счастливом мире!

Время для плюсов:


  1. Одна точка входа во вью
  2. Мы всегда под рукой имеем текущее состояние экрана
  3. Еще на стадии реализации приходится продумывать как один стейт будет перетекать
    в другой и какая между ними связь
  4. Unidirectional Data Flow

Любите андроид и никогда не теряйте свою мотивацию!

Список моих вдохновителей



Источник: habr.com
К списку статей
Опубликовано: 05.09.2020 22:11:28
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка под android

Android development

Mvi

Architecture

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru