Pull to refresh

Comments 16

Красиво.
Качественная работа!
Отдельное спасибо за код.
Как ограничить время жизни State при данном подходе? В большей части случаев хранить состояние одной фичи все время жизни приложения нет смысла. Или задача отдельной библиотеки?
Зависит от того, что именно Вы хотите получить. Вряд-ли Вам нужно ограничивать время жизни всего State, скорее какой-нибудь его части, так ведь? Приведу пример, если я правильно понял вопрос. В ApplicationState можно положить BottomSheetState, который будет отвечать за отображение вот такого UI элемента. У него могут быть различные вариации для отображения различных состояний, но, если у Вас в настоящий момент времени скрыт BottomSheet — просто ставите state = null и всё, на стороне UI соответственно подписка на эту часть состояния и отображение / скрытие UI элемента в зависимости от значения.
image

Интересный подход сам по себе.
Главный вопрос — на сколько сильно раздувается AppState в большом приложении?

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

Я приводил пример подхода чуть ниже в ответ на другой комментарий. Вкратце — зависит от того, как Вы построите состояние. Моё мнение в чем: При другом подходе данных меньше у Вас не станет, они просто будут разбросаны по всему приложению, часть во фрагменте, часть в интеракторе, часть ещё где-то и т.д.
В случае с Redux они просто лежат в одном месте, а всё остальное эти данные только использует, но не хранит.

В AbstractStore передаются списки middleware и reducer'ов, и судя по коду, обработка происходит в том же порядке, в котором они и находятся в этих списках. Не создает ли это проблем, когда нужно добавить изменения в существующую фичу?

Да, последовательность действительно имеет значение в данном случае и я бы даже сказал, что надо быть внимательным, но, это редко создаёт проблему, если правильно к этому подойти. Как правило у Вас будет мало случаев, когда несколько разных Middleware следят за одним и тем же событием и пытаются его заменить на что-то другое. Шанс выстрелить себе в ногу, конечно, всегда есть, но больших проблем я особо не заметил, да и логика эта хорошо покрывается тестами, что даёт большую уверенность в отсутствии регрессий. Отличный вопрос, кстати.

Спасибо! Но как быть, в случае если отправляем событие A и это событие в результате хождения по миддлварам подменяется на событие B, но в редюсере нужно менять стейт при событиях A и B? Создавать новое событие AB, которое будет подразумевать события A и B не очень хочется
Сложно дать однозначный ответ, потому что эта логика очень зависит от конкретной ситуации. Т.е. если Вам на событие А нужно что-то загрузить из сети и потом отправить событие В, то в middleware можно просто запустить запрос поймав событие А и при этом вернуть событие А дальше, таким образом, оно дойдет до reducer'a. А когда запрос выполнится — сделать dispatch события B. Либо же можно делать dispatch из самого middleware, но это нужно делать осторожно, потому что редукс синхронный сам по себе. Вообще, очень хороший вопрос и это своего рода краеугольный камень, с событиями надо быть осторожным, особенно когда речь идёт о замене одного на другое.
Уже давно пользуюсь похожим решением, но со стейтом на каждом фрагменте / активности, а не одним стейтом на все приложение.
Возникает вопрос. А не происходит мощного усложнения читабельности единого стейта в крупных приложениях?
В принципе можно выносить в подстейты целые флоу, которые в свою очередь тоже дробить на стейты отдельных экранов, но как-то больно много вложенности, читать же неудобно… Какое решение Вы используете для таких случаев?

Ну и второй вопрос — пробовали дробить на отдельные стейты? Проблемы переживания поворота и прочего вполне легко решаются, если сделать store на базе обычной ViewModel от гугла, а каждая фича становится полностью независимой, в любой момент по feature модулям можно приложение бить.
Если пробовали, какие минусы и плюсы по ощущениям от сравнения Вашего варианта и отдельных стейтов?

В более сложной конструкции части ApplicationState описываются иначе.
Например как-нибудь вот так:


data class ApplicationState {
    ... some other data here
    val bottomSheetState: BottomSheetState,
    val currentScreenState: CurrentScreenState
}

При этом BottomSheetState и CurrentScreenState могут являться sealed классами с явно ограниченным количеством возможных вариантов


BottomSheetState.kt


sealed class BottomSheetState {

    data class SimpleMessageBottomSheetState(...) : BottomSheetState()

    data class MessageWithButtonBottomSheetState(...) : BottomSheetState()
}

CurrentScreenState.kt


sealed class CurrentScreenState {
    data class Home(...) : CurrentScreenState()

    data class Profile(...) : CurrentScreenState()

    data class Settings(...) : CurrentScreenState()
}

Т.е. получается что у нас есть стейт какого-то BottomSheet'a или конкретного экрана и в момент времени активным может быть только один из. Но в тоже время в ApplicationState рядом могут лежать и другие данные, которые каждый может дергать на любом экране. Мне кажется, что в целом это баланс, между работоспособностью и достаточно простым на восприятие состоянием. Создатели Redux на JS в одной из частей документации даже описывают подход, как лучше структурировать состояние. Вкратце — более плоское состояние — лучше, сильно много вложенностей усложняют работу с такой конструкцтей.
Если под дроблением вы имели ввиду что-то другое и я не до конца ответил на Ваш вопрос — дайте пожалуйста знать, подискутируем.

А как это все дружится с разбиением приложения на модули?

Зависит от ситуации. По принципу вынести что-то в общий commons модуль, а остальное в другие — нормально. Если речь про feature модули, тут, к сожалению, подсказать не могу, ввиду отсутствия опыта в реализации подобного подхода.

Интересно. Как по мне — не хватает некой сущности event для того чтобы вывести простые события из store наружу, например показ диалога с ошибкой / snackbar / ну или чего-то подобного. Выносить подобные вещи (single event) в state как-то трудоемко.
Еще позволю сделать наблюдение что не все action должны быть видны снаружи store, например при некоем абстрактном search должен быть action = SearchChanged который посылает view в store, он обрабатывается middleware, который загружает данные и собирает некий внутренний action = DataUpdated, который в свою очередь обрабатывается reducer и изменяет state. Так вот по моему мнению action=DataUpdated не должен торчать наружу, т.к. нарушается инкапсуляция и у разработчика появляется дополнительная возможность чего-нибудь сделать не так.

Для показа Snackbar'a в примере со вторым приложением я тоже использовал middleware. Он следил за единственным интересующим его событием и показывал сообщения, когда что-то прилетало. Я примерно понимаю что Вас смущает в случае с событием DataUpdated, то, что оно пролетит через все Middleware и другие Reducer'ы, потенциально давая возможность прострелить колено, но, в этом весь Redux, это фундаментальный его механизм, сложно будет сделать иначе не нарушив изначальную идею.

Sign up to leave a comment.