Как стать автором
Обновить

Комментарии 12

Наймите дизайнера, пожалуйста :'(
Напишите пожалуйста статью с написанием небольшого приложения с использованием вашей библиотеки.
Спасибо за труды. Хотелось бы понять как вы решаете такие кейсы:
  1. Процесс убился и мы хотим что-то восстановить из Bundle'а при пересоздании Activity/Fragment. Как это может быть реализовано?
  2. Предположим у нас есть какой-нибудь большой список, как часть State'а. Выглядит так, что каждый раз, когда нам приходит новый State, придётся прогнать список через DiffUtil или что-то подобное. Получим значительный оверхед на любое изменение состояния. Или такие вещи не включаются в State вовсе?
  3. Обычно считается, что Clean и MV* — это ортогональные вещи (Clean — разделение бизнес-логики от фреймворков и системы, MV* — разделение presentation слоя). Мне показалось, что у вас часть бизнес-логики попадает в Feature и пр. классы фреймворка. Можете прокомментировать?
1. Мы можем инициализировать фичу с уже необходимым нам состоянием. При уничтожении фичи вы кладетё в Bundle её текущее состояние, а потом создаёте с уже готовым состоянием.
На данный момент мы используем TimeCapsule, однако есть некоторые концептуальные проблемы связанные с ним и возможно в будущем будет выработан другой подход.

2. Держать списки в State это нормальная практика. Ребята из чата встретились с этой проблемой когда переписывали его на MVICore. Для решения они дописали небольшое расширение, чтобы view получало одновременно пару из (viewModel, previousViewModel?). В таком случае список обновлялся только в том случае, если они отличались в моделях. Ну и собственно если список меняется, то его в любом случае придётся прогонять через DiffUtil, чтобы показать пользователю.
Если сформулировать кратко: мы хотим обновлять UI только тогда, когда он действительно меняется. Этот вопрос вынесен на обсуждения, чтобы найти подход, который покроет все необходимые кейсы, как только он будет найден — решение появится в библиотеке.

3. Вы верно поняли. В Feature и находится абсолютно вся бизнес-логика.
Правильно ли я понимаю, что при копировании списков, вы копируете и каждый его эллемент, иначе не получится пропустить его через логику DiffUtils? В этом случае не возникает ли проблем с большим списком на этапе копирования?

В догонку, списки могут обновится в 3 «направлениях», подгрузка «поздних»\«ранних» эллементов (как правило пачка) и реакция на уведомления от сервера, создавать ли копию State на каждый одиночный Event от сервера?
При копировании списков обычно используется обычная логика из стандартной либы Котлина: элементы старого списка просто перегоняются в новый, что не влечет с собой полного копирования каждого элемента. Если же список не поменялся, мы просто кладем весь старый инстанс в новый State (в Котлине это просто copy для data class). Эти списки и их элементы обычно immutable, что позволяет легко избежать возможных проблем из-за изменений данных снаружи Reducer.

Насчет второго вопроса: ваше предположение почти верно, мы создаем новую копию State на каждый ивент, который его меняет. Если ивент ничего не поменял (например он только триггерит News или PostProcessor), то обычно State остается прежним.
Если же State изменяется слишком часто, в теории можно аггрегировать эти ивенты (например через window для RxJava), и применять их изменения пачками.
элементы старого списка просто перегоняются в новый, что не влечет с собой полного копирования каждого элемента.

Переносятся ссылки, но при мапинге во ViewModel будут созданы новые объекты ViewModel для каждого элемента списка. Так что если хочется иметь ViewModel, то нужно придумывать обходные пути, либо на каждое обновление списка создавать порядка N объектов.

Если же список не поменялся, мы просто кладем весь старый инстанс в новый State (в Котлине это просто copy для data class)

Тогда нужно сохранять viewModel, чтобы каждый раз список не мапить в новые viewModel объекты.
В дополнение к комментарию ANublo насчет пункта про восстановление из Bundle. Если мы держим фичу в Android компоненте (Activity/Fragment), то восстановление State возможно при создании Feature (один из параметров — initialState). Вопрос в том, что часто имеет смысл держать фичу в более глобальном контексте, привязываясь к view и иным компонентам через Binder, который умеет в Lifecycle (об этом больше во второй статье).

В случае убийства всего процесса, можно просто открыть нужный экран, при этом пройдя через инициализацию фичи заново, так как процесс мог быть убит достаточно давно.
Как разработчик чата в Badoo и первопроходец MVICore могу добавить:
1. Есть возможность сохранять/восстанавливать стейты. Как отметили выше, на данный момент мы используем TimeCapsule и её Андроид реализацию AndroidTimeCapsule. Нужно ей передать в фичу и тогда фича регистрирует себя в капсуле как поставщик стейта, передавая туда Supplier текущего стейта. Далее из onSaveInstanceState надо сказать капсуле сохранить состояние и она опросит все зарегистрированные фичи, соберёт с них состояния и положит в Bundle. А позже фича сможет взять сохранённое состояние из капсулы. При этом перед выдачей стейта на сохранение, его можно изменить (во всё том же Supplier'е), например можно сбросить флаг загрузки, чтобы после пересоздания фича не оказалась в этом состоянии.
2. Списки храняться в тех же стейтах и они иммутабельные (хотя никто не мешает делать мутабельные списки для каких-либо хитрых случаев). Далее при маппинге стейтов во view-модели, можно постараться использовать исходные объекты: например просто перенести List как он есть из стейта во view-модель. Однако это не всегда возможно. В этом случае можно прибегнуть к хитростям. Например можно сделать LruCache из ChatMessage->ViewChatMessage и пробовать брать сначала оттуда, а если там нет, тогда мапить. Это позволит избежать лишних аллокаций.
3. В MVICore ничего особо строго не регламентировано, у разработчика довольно большая свобода. Лично я страюсь всю бизнес-логику складывать в фичи и писать на них хорошие и очень подробные юнит-тесты. А например источники данных (data source) и view делать совершенно без логики (например источники данных — это совершенно пассивные компоненты, выполняющие только дай-положи) и обязательно stateless.
Ребят очень классно.

Есть предложение немного переименовать News State Feature Wish (NSFW)
Зарегистрируйтесь на Хабре , чтобы оставить комментарий