Как стать автором
Обновить
19
0
Аркадий Иванов @arkivanov

Android разработчик

Отправить сообщение

Могу ошибаться, но мне кажется что в методе MainViewModel#bindView есть утечка - ссылка на MainView (т.е. на активити) будет сохраняться при переводе экрана. Скорее всего туда надо дополнительно передать жизненный цикл view.

Здравствуйте! Не могли бы Вы уточнить вопрос? Не очень понятно о каких "событиях по цепочке связей" идёт речь. И что такое "ненужные инит соятояния"?

Добрый день! На мой взгляд решение хорошее. Трудности могут возникнуть по следующим моментам:


  • Другая модель приложений и экранов и их жизненные циклы
  • Многопоточная среда
  • Отсутствие декларативного UI как стандарта — Jetpack Compose ещё в разработке, пока доминируют императивные подходы
  • Статическая типизация может доставлять неудобства первое время

Начать я бы посоветовал с вступления в следующие Телеграм-каналы, где всегда можно задать вопрос:



Далее я бы рекомендовал разобраться с Java в Android, как с языком, так и с виртуальной машиной. Потом Android SDK, Котлин, архитектурные шаблоны (MVI, MVVM, и т.д.).

Мы стараемся следовать SRP. По-этому да, если диалог сложный или может быть переиспользован, то его можно вынести в отдельную сущность (в статье это Component, или DialogFragment, или в нашем случае это был бы отдельный RIB). Там был бы свой Store. Тогда показ и скрытие лучше делать через News.

Показ диалогов можно делать также через состояние, а можно доработать "фреймворк" и добавить туда подобие News в MVICore. Если через состояние, то порядок примерно следующий:


  • По нажатию на котика производится Event.ItemClicked, который преобразуется в Intent.DoSomething
  • Intent.DoSomething поступает в Store
  • Store выставляет некий флаг State.isWaitingForData
  • State преобразуется во ViewModel, где проставляется флаг ViewModel.isRequestDataDialogVisible
  • При успешном закрытии диалога выдаётся Event.RequestDataDialogConfirmed(data)
  • При отмене диалога выдаётся Event.RequestDataDialogCancelled
  • Store получается соответствующие намерения, убирает флаг State.isWaitingForData и выполняет нужное действие

Если делать через News, то вместо фалага в состоянии Store будет выдавать одноразовый News.WaitingForData. Получив этот News представление показывает диалог.

Я с Вами полностью согласен, и в переведённом примере Rx (или это могли быть корутины+Flow) — во всех слоях, кроме прямо самого UI. И так получается, что UI можно сделать практически единственным, с чем придётся взаимодейстовать пользователям модуля. Это отлично ложится на имеющиеся ограничения Kotlin/Native. Да и просто, чем меньше пользователям модуля надо делать работы, тем лучше — на много проще вызвать метод onDestroy, чем заботится об утечках и отменах самому.

Спасибо за вопросы!


  1. Здесь несколько причин:
    • Прежде всего Ktor немного выходит за рамки статьи
    • Ktor знаком меньшему количеству разработчиков, нежели HttpUrlConnection
    • Хотелось лишний раз продемонстрировать возможности expect/actual
  2. Здесь речь именно про экспорт асинхронных фреймворков как API в Swift (или в Xcode). Из-за описанных ограничений их неудобно (и местами опасно) использовать. По-этому намного удобнее закрыть удобным и простым фасадом, а всю логику сделать деталями реализации общего модуля. Но использовать Reaktive для написания общего Kotlin кода — одно удовольствие. :-)

Спасибо, что прочитали! Большого примера в открытом виде, к сожалению, нет. Но общий принцип — делить на модули и не допускать их распухания. Компонент (презентер) — необязательно экран, это может быть и его часть. Общение между ними можно наладить при помощи входов/выходов, по такому же принципу, как Store соединяется со View внутри модуля.

Похоже, обсуждение переходит в обсуждение полезности шаблона MVI, что выходит за рамки данной статьи. Каждая команда выбирает свой подход к разработке. Мы последовательным путём пришли именно к MVI. Мы пробовали разные подходы и остановились именно на этом. У нас есть необходимость переиспользовать код в разных проектах. Мы иногда имеем более одной реализации вью. Иногда имеем более одно реализации бизнес логики. От инкрементального обновления вью мы ушли впользу stateless. От размызывания состояния по переменным мы ушли в пользу единого неизменяемого "хранилища" и обновления из одного места — редуктора. Кроме того, жизненный цикл вью может быть уже чем у модели. Мы можем отсоеденить вью от модели и потом присоеденить новый экземпляр, может быть даже другую реализацию.

Если у Представления есть метод render(StateChange) то по определению Представление зависит от от Состояния Модели, что делает Представление и Модель связанными. Введение отдельной Модели Представления избавляет от этой связности. Тоже самое в обратную сторону, судя по всему предполагается, что Представление будет обращаться к Модели на прямую (вызывать метод Reload). Это прямая зависимость Представления от Модели.


Далее, состояние в вашем случае представлено набором отдельных изменяемых переменных. Если их начать изменять из разных мест (не только из функции Reload), то придётся всё синхронизировать. А это потенациальная возможность получить гонки и неконсистентные состояния (например, флаг прогресса загрузки стоит, а загрузка не идёт).


MVI решает эти проблемы, представляя входы и выходы как потоки данных, которые можно преобразовывать. Имея класс Event можно преобразоывать его в Intent направить на вход Модели. Или же направить в некий AnalyticsTracker для аналитики. Можно преобразовать в Output Контроллера и выставить этот поток наружу.


В целом за счёт небольшого количества "boilerplate" кода достигается меньшая связность, поддерживаемость и безопасность.

На мой взгляд именно так. Могу дополнительно привести ссылку на твит одного известного в Андроид разработке человека. :-)

Спасибо за вопрос! В вашем примере Представление/контроллер — stateful, однако в MVI они должны быть stateless. Единым источником правды должна быть модель (Store). Таким образом Ваш вопрос сводится к "Зачем нужен MVI?".
Это уже особо не относится к теме этой статьи, но я попробую ответить.
Единый источник правды делает состояние всегда консистентным, что существенно упрощает поддержку и отладку когда. Также это даёт возможность делать крутые штуки вроде time travel отладки. Разделение на такие сущности, как Intent, State, Model и Event, позволяет делать представление и модель независимыми друг от друга. Опять же, компоненты, между которыми отсутствует связность, легче поддерживать и тестировать.


Спасибо, что прочитали статью!

Спасибо, что прочитали!

Спасибо за вопрос! Я позволю себе ответить вопросом на вопрос. Зачем нужен Flutter с его "другим" Dart, если теперь есть Kotlin Multiplatform? На мой взгляд у Flutter было два преимущества: возможность программировать сразу под несколько платформ и декларативный UI. Первый пункт больше не является преимуществом, и даже теперь имеет минусы в виде "другого" Dart. Второй пункт пока ещё актуален, но я думаю мультиплатформенный Compose будет и тогда в этом вопросе будет поставлена точка.


У KMP же есть следующие преимущества:


  • поддерживается больше платформ, среди которых есть нативные Linux x64, ARM и MIPS, watchOS, tvOS, Android Native, Windows и WASM. Полный список приведён здесь;
  • есть прямой доступ к API платформ с помощью expect/actual
  • можно легко управлять тем, какой код делать общим, а какой делегировать в платформы.
  • нативный UI (Android Views, Jetpack Compose, UIKit, SwiftUI и т.д.) позволяет поддерживать особенности платформ

В целом, KMP как инструмент мне кажется на много мощнее.

Не так давно, за неимением RxJava на Kotlin multiplatform

А как же github.com/badoo/Reaktive
Очень хорошая статья! Можно было ещё упомянуть Time Travel в MVI.
Привет! Опечатки и вёрстку исправили! Спасибо! Кстати, мы скоро добавим поддержку Linux и сразу после этого iOS!
Здравствуйте! Как уже было упомянуто выше в комментариях, мы думали о корутинах. Но не используем. Причина в том, что на момент начала работы над Reaktive не было до конца понятно, как с их помощью можно реализовать холодные стримы. Недавно появилось превью Flows, но и по ним тоже есть вопросы. Классический подход на данный момент выглядит более привлекательным и надёжным.
Здравствуйте! Project Reactor — это очень хороший проект. Но он нас не устраивает тем же, чем и RxJava — это Java-библиотека. У них было желание сделать её мультиплатформенной, но обсуждение недавно закрыли в пользу Kotlin Coroutines Flow.
Здравствуйте! Это очень хорошая идея и мы о ней, разумеется, думаем. Я не вижу каких-либо причин не перевести MVICore на Reaktive в будущем.
1

Информация

В рейтинге
Не участвует
Откуда
London, England - London, Великобритания
Дата рождения
Зарегистрирован
Активность