СОДЕРЖАНИЕ: 00:00:00 - введение 00:01:34 - Архитектура MVVM с Clean Architecture, как основа для MVI 00:02:56 - MVI на диаграмме 00:05:09 - Model-View-Intent на практике в коде 00:05:42 - Меняем ViewModel по архитектуре MVI 00:08:56 - Адаптирем Android Activity под MVI 00:09:25 - Реализуем State в MVI 00:16:14 - Запускаем Android приложение 00:17:32 - подводим итоги
Ещё не добрался до MVI, но пишу комментарий здесь чтобы возможно ты увидел его. Огромное спасибо за твою подачу столь сложных вещей, я уверен что благодаря тебе очень много джуниоров прошли свои собеседования и работают во благо мобильной разработки! Для тех, кто недавно влился в мобилку скажу. Я посмотрел видео про MVVM месяца так два назад, и честно сказать просто переписал код, но не понял как и куда его использовать, пересмотрел видео сегодня, всё понял и всё впитал. Я это к чему, не парьтесь, что не понимаете вещей прямо сейчас, через время вам эти вещи будут казаться просто пустяками легчайшими, набивайте руку и делайте свои проекты(вначале говнокодьте, куда ж без этого). Спасибо Тимофею за его видосы, смотрю теорию в метро, решаю практику дома
Здравствуйте, Тимофей, какая радость видеть ваши новые видео ,большое вам спасибо за работу. Кстати если вдруг вы принимаете заявки на видео, снимите пожалуйста про корутины. Огромный затык с ними. Но если нет, то и ладно, просто продолжайте, еще раз большое спасибо за ваши труды.
Как идея заменить interface MainEvent на sealed class MainEvent. Чтобы не смогли передать state, который не описан в MainViewModel. Так мы защитим свой от наследования от MainEvent уже после того как у нас будет написан код, чтобы не оказалось, что кто-то сможет передать необрабатываемое состояние. Плюс при использовании sealed сlass компилятор автоматиченски проверит все when и не даст собрать, если мы забудем обработать какое-то состояние или другой разработчик добавит новое состояние и не добавит обработку. Но можно и не делать, конечно)
Пока что лучшее, из того, что нашел ) спасибо. Без воды коротко и ясно. Думаю стоило использовать enum или sealed, но подозреваю, что это было сделано для простоты понимания. Еще раз спасибо.
Это не MVI. MVI подразумевает, что у нас есть Reducer, который на вход принимает текущее состояние и изменения состояния, а на выходе отдает новое состояние целиком. Этот подход актуален больше для Jetpack Compose, потому что при такой ситуации он с помощью подкапотным diff на вьюшки будет переотрисовывать только изменения, но с xml вьюшками будет переотрисовываться все целеком и придется реализовывать свои diff. Пример реализации MVI - MVIKotlin, MVI Core Badoo.
У нас в примере Reducer это просто функция. Отсутствие названия Reducer не показатель того, что это не MVI:). А MVIKotlin, MVI Core Badoo - это уже специфичные реализации MVI подхода с кучей всего.
Добрый день Тимофей, спасибо огромное за ваше красноречивое объяснение отдельных тем связанные с андроид разработкой, хотелось бы от вас видео урок про микросервисы(отправка кода удалённо и его выполнение в приложении).
Вопросы такие: - чем плохо 2 публичных функции типа load / save ? чем этот подход плох в MVI ? Ведь если как и раньше к ним обращаться, то мы избавляемся от Event моделей. - как быть c singleEvents (сделай переход, покажи тоаст, показать диалог) которые нужно показать один раз. (где то у гугла читал, что они предлагают это добавить в стэйт, с той разницей что после прочтения этого события, его переводить в null или какой то нетральный стэй, который не будет вызывать повторное событие, при повторной подписке)
2 публичных метода не плохо, мне лично MVVM больше нравится и вполне устраивает. Тут просто другой подход, предлагающий единый обработчик для всего, что происходит в приложении. И да, с MVI много кода выходит, особенно, если событий прилично. C singleEvents все записываем в state и потом кидаем событие, что-бы обнулить это значение 🙂 То есть на каждое действие создаем событие.
Утверждение о том, что MVI это как MVVM, только со стейтами/экшенами не очень корректное. MVI не обязательно подразумевает использование того же механизма что и в MVVM для общения с View. MVI можно реализовать в связке с MVVM, а можно в связке с MVP, ибо MVVM/MVP отвечают за механизм взаимодействия вьюшки с логикой, а MVI позволяет определить как выглядят данные, которыми они обмениваются. В случае с MVP вы можете определить метод render(state: State) во View, и метод onAction(action: Action) в презентере, и это будет вполне себе MVI
Да, все верно, основу не обязательно брать в виде MVVM. Но в Android это наиболее популярный вариант, так как VM идет из коробки. MVP все же устаревшее решение, без официальной поддержки.
@@TimofeyKovalenko согласен, сегодня трудно встретить mvp в новых проектах. Мой комментарий скорее про то, что применение MVI не обязательно связано строго с MVVM
Все понятно , круто , спасиб . Непонятно только то , что в mvvi для всего были свои "модельки" , чуть-ли не модельки для моделек. А в этом примере везде тупо STRING . Или это только для примера ?
Тимофей, спасибо Вам огромное за вашу проделанную работу! Благодаря вашим видео много понял и открыл для себя! Хочу вас попросить, если у вас будет желание, сделать материал по тому, как правильно маппить модели из разных слоев, а также модели, полученные через ретрофит и локальные базы данных. Спасибо вам!
Просто храните их внутри стейта в той же модельке, которую получили из domain. Либо можно создать UI модель, если там требуется дополнительная логика для отображения полученных даных.
sealed class конечно по интереснее смотрится чем interface и просто class ниже в файле. так же хотелось услышать про .copy при изменении состояния + почему value, а не postValue
нуу можно конечно, но я думаю это не такая уж дорогая операция, да и корутину вы же запускаете в UI потоке, а уже дальше ее переключаете на нужный поток.
Привет, спасибо огромное за уроки, хотел спросить: В стейт мы получается должны записать все необходимые переменные, а допустим их будет 10-15, в каждой определенной функции типа save или load надо будет вручную присваивать нужные значения и сохранять старые ?
у data class есть метод copy(), который замечательно позволяет копировать старые значения не указывая их значения в параметрах, а только заменять значения у нужных, используя именованные параметры
Здравствуйте, Тимофей! У меня такой вопрос, немного уточняющий. Если мы хотим иметь какую-то обработку ошибок, то в state нужно хранить условный screenState, который из себя представляет Sealed Class с различными стейтами экрана (загрузки, ошибки, дата и т.д.), верно?
Хм, обычно одна страничка в приложении отображает сразу очень много данных. Кажется не резонным каждый раз перерисовать состояние полностью.. Либо я что-то не понял
Как только в РБ можно будет преподавать без необходимости платить конский налог с этого вида деятельности ;). В этом году к сожалению не преподаю, возможно со следующего года возобновится набор.
@@TimofeyKovalenko это дружеские беседы в скайпе на общие интересующие темы, какой бизнес, какой налог? Обсудить детали «донатов» за ваш труд и вперёд!! Мы ждём!! :)
Вместо простых интерфейсов лучше использовать sealed interfaces - так надёжней, если забыл написать обработчик - компилятор подскажет. И очень плохо каждый раз создавать новый стейт - легко потерять данные. Лучше создать дефолтный стейт, а при изменении использовать метод copy, это же data class
Я не так давно перечитывал developer документации и там был такой пример, что мы во view model создаём модель для хранения view state и все events обарачиваются в callback поля: val primaryActionClick: () -> Unit У меня вопрос немного не по теме, но я хочу узнать как нам в таком случае мапить модели которые мы получаем с backend в screen state model? К примеру, если на нужно доставать string resources через контекст (мы жже не можем делать это во view model)
создай interface ResourcesInteractor, который дублирует методы для получения ресурсов. Создай его реализацию ResourcesInteractorImpl, которая и выдает ресурсы. Передай ResourcesInteractor туда, куда надо, через DI (dagger/koin)
Хотелось бы больше информации про навигацию между фрагментами с помощью cicerone, на просторах интернета очень мало на эту тематику, да и на самом деле, хотелось бы лично от вас услышать. Прошел первые 7 уроков из плейлиста по чистой архитектуре. Это невероятно, хоть и достаточно сложно к пониманию, но видео содержат 100% полезности)
у data class есть метод copy(), в который можно передать только изменяемые параметры, намного красивее будет чем каждый раз перезаписывать все поля. В видео нет приписки для чайников, поэтому можно было и это вставить)
СОДЕРЖАНИЕ:
00:00:00 - введение
00:01:34 - Архитектура MVVM с Clean Architecture, как основа для MVI
00:02:56 - MVI на диаграмме
00:05:09 - Model-View-Intent на практике в коде
00:05:42 - Меняем ViewModel по архитектуре MVI
00:08:56 - Адаптирем Android Activity под MVI
00:09:25 - Реализуем State в MVI
00:16:14 - Запускаем Android приложение
00:17:32 - подводим итоги
Ещё не добрался до MVI, но пишу комментарий здесь чтобы возможно ты увидел его. Огромное спасибо за твою подачу столь сложных вещей, я уверен что благодаря тебе очень много джуниоров прошли свои собеседования и работают во благо мобильной разработки! Для тех, кто недавно влился в мобилку скажу. Я посмотрел видео про MVVM месяца так два назад, и честно сказать просто переписал код, но не понял как и куда его использовать, пересмотрел видео сегодня, всё понял и всё впитал. Я это к чему, не парьтесь, что не понимаете вещей прямо сейчас, через время вам эти вещи будут казаться просто пустяками легчайшими, набивайте руку и делайте свои проекты(вначале говнокодьте, куда ж без этого). Спасибо Тимофею за его видосы, смотрю теорию в метро, решаю практику дома
Здравствуйте, Тимофей, какая радость видеть ваши новые видео ,большое вам спасибо за работу. Кстати если вдруг вы принимаете заявки на видео, снимите пожалуйста про корутины. Огромный затык с ними. Но если нет, то и ладно, просто продолжайте, еще раз большое спасибо за ваши труды.
Про корутины есть мысли, но не в ближайшее время пока.
@@TimofeyKovalenko буду ждать, у вас талант от бога объяснять сложные вещи.
Поддержу.
Как идея заменить interface MainEvent на sealed class MainEvent. Чтобы не смогли передать state, который не описан в MainViewModel. Так мы защитим свой от наследования от MainEvent уже после того как у нас будет написан код, чтобы не оказалось, что кто-то сможет передать необрабатываемое состояние. Плюс при использовании sealed сlass компилятор автоматиченски проверит все when и не даст собрать, если мы забудем обработать какое-то состояние или другой разработчик добавит новое состояние и не добавит обработку.
Но можно и не делать, конечно)
Да, это хороший вариант, я бы так и рекомендовал делать.
А файл не разбухнет описать все ивенты в одном классе?
@@ДенисСаранин-м1и можно и не в одном файле. Sealed можно раскидать в рамках одного пакета в модуле.
при просмотре видео тоже подумал о sealed class)))
Ура! Уроки вернулись, спасибо большое!
Самое понятное описание паттерна MVI. Благодарю!
Огромное спасибо! Пытался уже читать статьи по MVI и даже намека на понимание не было. А тут всё очень доступно объяснено.
Отличное объяснение, впервые хоть немного понял mvi. На работе редьбкс, там ещё хуже 😂
сразу лайк, Тимофей!) По твоим видео учил как архитектуру строить!)
Кратко и доступно) Большое спасибо за урок!
Большое спасибо за урок !!!
Пока что лучшее, из того, что нашел ) спасибо. Без воды коротко и ясно.
Думаю стоило использовать enum или sealed, но подозреваю, что это было сделано для простоты понимания. Еще раз спасибо.
Спасибо! Как раз хотел понять суть MVI!
Спасибо большое 😍
Недавно вас нашёл, очень понравилось про MVVM, но огорчился, что уже пол года не выпускали ролики (
Перерывчик был ;)
Спасибо за урок! Все как обычно, ясно и понятно
Это не MVI. MVI подразумевает, что у нас есть Reducer, который на вход принимает текущее состояние и изменения состояния, а на выходе отдает новое состояние целиком. Этот подход актуален больше для Jetpack Compose, потому что при такой ситуации он с помощью подкапотным diff на вьюшки будет переотрисовывать только изменения, но с xml вьюшками будет переотрисовываться все целеком и придется реализовывать свои diff.
Пример реализации MVI - MVIKotlin, MVI Core Badoo.
У нас в примере Reducer это просто функция. Отсутствие названия Reducer не показатель того, что это не MVI:). А MVIKotlin, MVI Core Badoo - это уже специфичные реализации MVI подхода с кучей всего.
Огромное вам спасибо, если бы все так учили =)
Добрый день Тимофей, спасибо огромное за ваше красноречивое объяснение отдельных тем связанные с андроид разработкой, хотелось бы от вас видео урок про микросервисы(отправка кода удалённо и его выполнение в приложении).
Про такое точно не будет видео, не специалист в этом вопросе.
Вопросы такие:
- чем плохо 2 публичных функции типа load / save ? чем этот подход плох в MVI ? Ведь если как и раньше к ним обращаться, то мы избавляемся от Event моделей.
- как быть c singleEvents (сделай переход, покажи тоаст, показать диалог) которые нужно показать один раз. (где то у гугла читал, что они предлагают это добавить в стэйт, с той разницей что после прочтения этого события, его переводить в null или какой то нетральный стэй, который не будет вызывать повторное событие, при повторной подписке)
2 публичных метода не плохо, мне лично MVVM больше нравится и вполне устраивает. Тут просто другой подход, предлагающий единый обработчик для всего, что происходит в приложении. И да, с MVI много кода выходит, особенно, если событий прилично.
C singleEvents все записываем в state и потом кидаем событие, что-бы обнулить это значение 🙂
То есть на каждое действие создаем событие.
На публичных методах возможны параллельные срабатывания, а на сингл ивент нет надежного метода доставки
Утверждение о том, что MVI это как MVVM, только со стейтами/экшенами не очень корректное. MVI не обязательно подразумевает использование того же механизма что и в MVVM для общения с View. MVI можно реализовать в связке с MVVM, а можно в связке с MVP, ибо MVVM/MVP отвечают за механизм взаимодействия вьюшки с логикой, а MVI позволяет определить как выглядят данные, которыми они обмениваются.
В случае с MVP вы можете определить метод render(state: State) во View, и метод onAction(action: Action) в презентере, и это будет вполне себе MVI
Да, все верно, основу не обязательно брать в виде MVVM. Но в Android это наиболее популярный вариант, так как VM идет из коробки. MVP все же устаревшее решение, без официальной поддержки.
@@TimofeyKovalenko согласен, сегодня трудно встретить mvp в новых проектах. Мой комментарий скорее про то, что применение MVI не обязательно связано строго с MVVM
Не плохо, разобрался поболее что и как. Единственное - MainEvent и его наследников мне кажется лучше выполнять в качестве sealed class
Да, все верно, в видео не хотелось усложнять.
@@TimofeyKovalenko это скорее не усложнение, а правильная практика)
Все понятно , круто , спасиб . Непонятно только то , что в mvvi для всего были свои "модельки" , чуть-ли не модельки для моделек. А в этом примере везде тупо STRING . Или это только для примера ?
Да, это для примера. Плюс можно раскладывать сразу на примитивы, что-бы вьюшке осталось только положить их в нужное место
Тимофей, спасибо Вам огромное за вашу проделанную работу! Благодаря вашим видео много понял и открыл для себя!
Хочу вас попросить, если у вас будет желание, сделать материал по тому, как правильно маппить модели из разных слоев, а также модели, полученные через ретрофит и локальные базы данных.
Спасибо вам!
Обязательно сделаю такое!
Думаю лучше ли использовать sealed class в месте интерфейса чтобы видеть все возможные варианты а не запомнить каждый класс ✅
Конечно лучше). В видео просто упрощено, что-бы не вводить лишнее
а почему не sealed для Event`ов?
Спасибо за видео. Как должен выглядить стейт, когда нужно так же хранить данные которые получены с domain?
Просто храните их внутри стейта в той же модельке, которую получили из domain. Либо можно создать UI модель, если там требуется дополнительная логика для отображения полученных даных.
sealed class конечно по интереснее смотрится чем interface и просто class ниже в файле.
так же хотелось услышать про .copy при изменении состояния + почему value, а не postValue
Про copy просто не стал усложнять. А зачем вам postValue тут? Не никакого смысла в нем.
Чтобы не нагружать дополнительно поток в котором происходит отрисовка экрана. Запустить туже корутину и там прописать postValue@@TimofeyKovalenko
нуу можно конечно, но я думаю это не такая уж дорогая операция, да и корутину вы же запускаете в UI потоке, а уже дальше ее переключаете на нужный поток.
Привет, спасибо огромное за уроки, хотел спросить: В стейт мы получается должны записать все необходимые переменные, а допустим их будет 10-15, в каждой определенной функции типа save или load надо будет вручную присваивать нужные значения и сохранять старые ?
Да, но можно сделать функцию copy, что-бы не делать это каждый раз. Так же, есть смысл разбить UI на более мелкие со своими VM.
у data class есть метод copy(), который замечательно позволяет копировать старые значения не указывая их значения в параметрах, а только заменять значения у нужных, используя именованные параметры
Как обрабатывать side эффекты в MVI?
Здравствуйте, Тимофей! У меня такой вопрос, немного уточняющий. Если мы хотим иметь какую-то обработку ошибок, то в state нужно хранить условный screenState, который из себя представляет Sealed Class с различными стейтами экрана (загрузки, ошибки, дата и т.д.), верно?
Не обязательно, это может быть простая модель
Здравствуйте, Тимофей
Возможно ли ещё записаться к Вам на курсы?
Да, посмотри на главной странице вверху есть ссылка на набор в группу.
Хм, обычно одна страничка в приложении отображает сразу очень много данных. Кажется не резонным каждый раз перерисовать состояние полностью.. Либо я что-то не понял
Да, поэтому нужно менять только те части, данные для которых изменились. С композ это все легко решается.
Тимофей, скажите пожалуйста, когда у вас будет старт набора на обучающий курс?
Как только в РБ можно будет преподавать без необходимости платить конский налог с этого вида деятельности ;). В этом году к сожалению не преподаю, возможно со следующего года возобновится набор.
@@TimofeyKovalenko это дружеские беседы в скайпе на общие интересующие темы, какой бизнес, какой налог? Обсудить детали «донатов» за ваш труд и вперёд!! Мы ждём!! :)
Спасибо, теперь-то я понял, в чем их разница. А почему так тихо?
С микрофоном бывает не угадаешь, а переснимать потом лень ;)
Вместо простых интерфейсов лучше использовать sealed interfaces - так надёжней, если забыл написать обработчик - компилятор подскажет. И очень плохо каждый раз создавать новый стейт - легко потерять данные. Лучше создать дефолтный стейт, а при изменении использовать метод copy, это же data class
Простые интерфейсы используются что-бы понимание тоже простым было ;), тоже и с copyWith(). А так конечно же, это лучше.
@@TimofeyKovalenko Это скорее необходимо. Лучше потратить пару минут на объяснение sealed interface и data class - без них в MVI лучше не суваться
@@VoroninSergey подушню немного - всё-таки код котлина, а в котлине нет sealed interface, там sealed class))
@@РоманМатохин Интерфейсы тоже есть
Не легче было создавать новую версию MainState через copy? прописывая только изменившиеся свойства) не зря же data class используется
Да, но не хотелось усложнять еще какими-то конструкциями.
Those explanations seems really nice, I wish I understood russian :/
Я не так давно перечитывал developer документации и там был такой пример, что мы во view model создаём модель для хранения view state и все events обарачиваются в callback поля:
val primaryActionClick: () -> Unit
У меня вопрос немного не по теме, но я хочу узнать как нам в таком случае мапить модели которые мы получаем с backend в screen state model? К примеру, если на нужно доставать string resources через контекст (мы жже не можем делать это во view model)
создай interface ResourcesInteractor, который дублирует методы для получения ресурсов. Создай его реализацию ResourcesInteractorImpl, которая и выдает ресурсы. Передай ResourcesInteractor туда, куда надо, через DI (dagger/koin)
а этот подход случайно не под state flow сделан?Недавно смотрел видео про него
Нет, но они хорошо сочетаются.
Думаю здесь нужны sealed классы
🔥🔥🔥🔥🔥🔥
Хотелось бы больше информации про навигацию между фрагментами с помощью cicerone, на просторах интернета очень мало на эту тематику, да и на самом деле, хотелось бы лично от вас услышать. Прошел первые 7 уроков из плейлиста по чистой архитектуре. Это невероятно, хоть и достаточно сложно к пониманию, но видео содержат 100% полезности)
Почему мало? если на habr есть целая статья об этом? К тому же в README тоже понятно написано.
cicerone прошлый век, юзайте Android Navigation Component
у data class есть метод copy(), в который можно передать только изменяемые параметры, намного красивее будет чем каждый раз перезаписывать все поля. В видео нет приписки для чайников, поэтому можно было и это вставить)
Приватные методы во вьюмодели нетестируемы
Тестируется вся логика, просто приватные методы не вызываются напрямую, они часть общей логики других методов, которые как раз таки и тестируем.
Лайвдата? Мы вроде не на джабе пишем
А LiveData только на джаве может использоваться?). Используем самый простой инструмент для демонстрации. Если вы про корутины, то это отдельная тема.
@@TimofeyKovalenko зачем лишняя библиотека