Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active February 13, 2024 12:26
Show Gist options
  • Star 35 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save PlugFox/7ee89778d0145f3bba704dbc4e4002da to your computer and use it in GitHub Desktop.
Save PlugFox/7ee89778d0145f3bba704dbc4e4002da to your computer and use it in GitHub Desktop.
БИНГО ошибок при создании BLoC'а

БИНГО ошибок при создании BLoC'а

❗❗❗ОБНОВЛЕННАЯ ВЕРСИЯ СТАТЬИ НАХОДИТСЯ ТУТ ❗❗❗

ОШИБКИ:

  1. Начать писать логику непосредственно в mapEventToState,
    он у вас быстренько превратится в нечитаемую портянку и придете жаловаться на бойлерплейт.
    Если правильно готовить блок, то бойлерплейтом там и не пахнет,
    эвенты + стейты + блок умещаются все вместе на 1-2 экранах.
    Все запредельно воздушно, даже не надо создавать отдельные файлы под эвенты и стейты.
    Все ультра емко получается.

  2. Мутабельные стейты - нет и еще раз нет, все они должны быть помечены @immutable.
    Пэйлоада/нагрузки/данных стейтов это также касается.
    Желательно не проморгать и списки также завернуть в UnmodifiableListView.

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

  4. Попробывать создать "свой блок", ведь "блок, это паттерн, а не пакет".
    Конечно, если вы не опытнейший архитектор с кучей ресурсов, временем на тесты/документацию/поддержку.
    А также у вас огромное комьюнити контрибьютеров готовое помогать вам в этой задаче.
    Эммм... Ну тогда что вы тут делаете?

  5. У БЛоК'а не должно быть дополнительных публичных методов, геттеров, сеттеров, переменных.
    Если у вас не выходит сделать что-то через pub/sub (add/listen), значит вы однозначно делаете это не правильно.

  6. Не соблюдаете уникальность стейтов:
    a) Вместо создания нового объекта стейта вы прокидываете существующий инстанс по ссылке
    b) Переносите объект из предидущего стейта в новый по ссылке
    c) Забываете про переопределенное равенство у стейта и его пейлоада.
    Во всех этих случаях вы рискуете хлопая ресницами удивляться, что заэмиченные стейты не доходят до UI.

  7. Cubit. Это не стейт менеджер и не архитектура.
    На проекте "это" применять нельзя.
    Исключение #1 - вы недавно начали знакомиться с реактивщиной и БЛоК'ом,
    в таком случае вы можете попробывать начать с него на небольшом демо проекте.
    Исключение #2 - у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. Это может быть полезно для ловли сайд эффектов не зависящих напрямую от действий пользователя с интерфейсов. (watch к СУРБД, отслеживание геопозиции, отслеживание состояния интернета). То есть он выступает "прокладкой" между потоком репозитория и интерфейсом.

  8. Вы забываете про очередность эвентов и asyncExpand в transformEvents.
    Вы должны знать, что по умолчанию все эвенты обрабатываются строго поочередно.

  9. Вы не используете Bloc.observer для перехвата ошибок и логирования.
    Или делаете try { ... } on dynamic catch (e) { ... } без rethrow в своих блоках.

  10. Начиная с блока (включая) не должно быть импортов флатера.
    Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
    Не должно быть виджетов, не должно быть контекста.

ЗАБЛУЖДЕНИЯ:

  1. Если я создам БЛоК в initState/didChangeDependencies, добавлю в него эвенты, то они могут быстро обработаться и результирующие стейты не попадут в первый build StreamBuilder/BlocBuilder. Нет. До первого build'а эвенты даже не начнут обрабатываться, не то что эмит стейтов на их основании. А в случае StreamBuilder - первым снэпшотом ВСЕГДА будет то, что вы установите ему в initialData.

СОВЕТЫ:

  1. По возможности используйте freezed пакет под эвенты и стейты,
    а особенно его фичу с Union и методами when, maybeWhen.
    Сниппет для Android Studion / IDEA можете посмотреть здесь: https://github.com/dart-side/live-templates/blob/main/bloc.md
@misterfourtytwo
Copy link

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

@PlugFox
Copy link
Author

PlugFox commented Dec 2, 2020

так получается подписывать блок на обновление стейтов другого блока это хреново?

Почему хреново?
Я такого не говорил.

@misterfourtytwo
Copy link

Начиная с блока (включая) не должно быть импортов флатера.
Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
Не должно быть виджетов, не должно быть контекста.

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

@PlugFox
Copy link
Author

PlugFox commented Dec 2, 2020

Начиная с блока (включая) не должно быть импортов флатера.
Исключение, пожалуй, несколько сущностей из flutter/foundation, которые могут быть заменены сторонними универсальными пакетами, не зависящими от flutter SDK.
Не должно быть виджетов, не должно быть контекста.

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

Давай представим две ситуации, когда тебе говорят:

  1. Слушай, а давай теперь сделаем все тоже самое, но на ангуляр дарте.
    При хорошей архитектуре логика портируется практически 1 к 1 без изменений. Портировать свое поделие сможешь?
  2. Слушай, что то у нас много ошибок, давай юнит тесты писать! Ты сможешь покрыть юнит тестом такой блок или придется рожать контекст откудато?

Идея БЛоК'а и в целом архитектуры - снизить зацепление (coupling), отделив слой интерфейса от слоя бизнес логики.
А ты, получается, все равно упорно перемешиваешь их, таща интерфейс в логику.

Что нужно сделать?
Достать БЛоК еще в интерфейсе и передать в конструктор другого блока.

@misterfourtytwo
Copy link

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

@PlugFox
Copy link
Author

PlugFox commented Dec 2, 2020

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

Ну это ошибка и не является оправданием)
А так - смотри сам.

@karabanovbs
Copy link

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

@PlugFox
Copy link
Author

PlugFox commented Jan 20, 2021

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.

Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

@karabanovbs
Copy link

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.

Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

А в чем проблема виджет теста? Как раз с гет_ит появится возможность управлять зависимостями без доступа к конструктору ( когда мы тестируем виджет в котором уже есть провайдер).

Как оно вылезет? перед каждым тестом формируется новое "чистое" окружение get_it.

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

@PlugFox
Copy link
Author

PlugFox commented Jan 21, 2021

@PlugFox не совсем понял чем плохо доставлять репозитории в блок через get_it? в подкасте говорится что это сделает невозможном написание тестов, а чем непонятно. Разве что в хуке подготовки теста будет заполняться инстанс get_it моковыми репозиториями. Или имелось ввиду что мы не будем знать какие репозитории нам нужны по сигнатуре и придется смотреть код?

Вот ты будешь писать не только юнит тест под блок, а скажем виджет тесты. Как ты доставишь зависимости тогда?
И естественно тестов должно быть больше чем 1, а гет_ит это тупо синглтон и то что ты туда загонишь в одном тесте - вылезет и в другом.
Да это все разруливается +- костылями, но зачем, если сразу можно сделать хорошо?
И более того, хорошо сделать не сложно и тебе в этом никто не мешает...

А в чем проблема виджет теста? Как раз с гет_ит появится возможность управлять зависимостями без доступа к конструктору ( когда мы тестируем виджет в котором уже есть провайдер).

Как оно вылезет? перед каждым тестом формируется новое "чистое" окружение get_it.

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

Так вам и не надо каждый раз писать вызов конструктора блока. Зачем вам это?
В приложении? У вас блок в контексте.
В тестах? setUp и setUpAll.

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

В том то и дело, что нет.
Использование контекста позволяет задать жизненный цикл при котором объект создается и уничтожается.
Позволяет задать динамично меняющиеся зависимости.
Определяет область видимости (вряд ли вы scope'ами пользуетесь в get_it).
Делает так, что вы чисто технически не можете получить что то из синглтона/сервайс локатора до того как оно туда было помещено или обновлено.

Я прекрасно понимаю почему люди используют get_it: ну чо, тут при старте приложения суну, а потом где угодно высуну и думать, чего то планировать, не надо.
Но это даже звучит как [3.14]здец и лапша вместо кода.

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

@karabanovbs
Copy link

Так вам и не надо каждый раз писать вызов конструктора блока. Зачем вам это?
В приложении? У вас блок в контексте.
В тестах? setUp и setUpAll.

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

Я прекрасно понимаю почему люди используют get_it: ну чо, тут при старте приложения суну, а потом где угодно высуну и думать, чего то планировать, не надо.
Но это даже звучит как [3.14]здец и лапша вместо кода.

Используют для отделения описания зависимостей от описания логики и описания UI. Тем самым уменьшая связанность кода. Собственно как и DI В любой другой технологии.
Не понимаю где Вы тут увидели лапшу.
Вообще у меня бекграунд из ангуляра, поэтому возможно я пытаюсь работать с зависимостями как там. Где мы в модуле описываем зависимости, а компоненты уже сами из подтягивают.

Я понимаю что Flutter путь это передавать зависимости через провайдеры, но я стараюсь все что можно вынести за пределы флаттера выносить, что бы в моих виждетах (кроме виджетов которые должны жить сами по себе, аля контролы) было минимум не UI кода, и написание огромного конструктора в провайдере блока пока вызывает у меня кровь из глаз :D.

@PlugFox
Copy link
Author

PlugFox commented Jan 21, 2021

@karabanovbs

Для начала просто нарисуйте схему вашего приложения.
MaterialApp, Navigator'ы, экраны, виджеты, кнопочки.

Затем нарисуйте отдельно сбоку кружком GetIt.

А затем нарисуйте красными линиями что от чего зависит и что откуда что берет.

Как нарисуете - вставляйте сюда картинку и посмотрим где у вас там лапша)

Просто сейчас это выглядит так, что вы просто оправдываете то, что уже написали.
Могу показать как у нас было под тысячу строк зависимостей в injector'е (это аналог GetIt от гугла) как и у вас.
И естественно я в курсе за последствия таких допущений и сделок с совестью, больше так не делаем)))

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

@karabanovbs
Copy link

Могу показать как у нас было под тысячу строк зависимостей в injector'е (это аналог GetIt от гугла) как и у вас.

Если есть возможность покажите.

Для начала просто нарисуйте схему вашего приложения.

Посмотрю, как со временем будет.

@PlugFox
Copy link
Author

PlugFox commented Jan 22, 2021

Если есть возможность покажите.

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

Даже нарушение SRP и проблемы с тестированием должны вам прям кричать о том, что вы явно делаете что то не так)

Можете ознакомиться с этим, к примеру: https://habr.com/ru/post/270005/

image

@karabanovbs
Copy link

@PlugFox в статье говорится о случае когда мы скрываем зависимости внутри класса, это ошибка, я согласен (уже говорил об этом ранее). Но когда эти зависимости можно передать через конструктор, и в дополнение они аннотированы для работы с DI и следовательно экземпляр можно получать из DI не переживая о зависимостях.

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

У меня складывается впечатление что вы вообще против любого DI, или только во flutter? В angular, .net, java вроде используется и ничего страшного.

@Nomoke
Copy link

Nomoke commented Oct 21, 2021

_ у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. _

почему?

@PlugFox
Copy link
Author

PlugFox commented Oct 22, 2021

@karabanovbs

используется в <язык/фреймвок>

Это супер не валидный поинт. Много где и чего используется.
Во флатере используется GetX, более того, это топ 1 пакет по популярности.
При том, что большего говна я в жизни не видел.

it-юмор-geek-5955906

против любого DI

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

Если мы говорим про условный get_it и типичное его использование в виде синглтона с возможными не только пулл, но и пуш операциями - этот подход будет даже хуже чем сохранение в late final глобальную переменную, по очевидным причинам.

Если вы у себя каким то чудом смогли запретить команде использовать push операции в ваш "DI", все зависимости идут по ОБЛАСТЯМ ВИДИМОСТИ, все содержимое у него ИММУТАБЕЛЬНО, а если даже изменяются - то обязательно САЙД ЭФФЕКТОМ УВЕДОМЛЯЮТ подписчиков это уже другое дело.

Если вы делаете push операции ИММУТАБЕЛЬНЫХ объектов ТОЛЬКО НА ЭТАПЕ ИНИЦИАЛИЗАЦИИ, не храните в нем текущего пользователя и все такое - это нормально, хотя по сути бессмысленно и он по большому счету ничего и не делает.

Но если для вас "DI": ну тут я в него сунул, а тут высунул, а там снова суну, а вон там подобновлю объект - это ЧРЕЗВЫЧАЙНО плохой подход, чреватый неприятными последствиями, плавающими багами и лапшой из инверсии зависимостей, от которого на перспективу надо избавляться.

Флатер предоставляет гораздо более интересное решение для управления зависимостями.

@PlugFox
Copy link
Author

PlugFox commented Oct 22, 2021

@August79

_ у вашего Cubit'а не будет собственных публичных методов, только стандартный listen. _

почему?

Потому что в противном случае это нисколько не будет отличаться от того, что в флатере называют "simple state managment" через ChangeNotifier. У вас не будет очередности событий и как следствие поимете странные race condition когда за состоянием "разлогиниваемся" может запросто идти состояние "залогинен".

Ну и также этот подход ничуть не будет отличаться от "вызывать методы репозитория сразу из слоя виджетов", чем, собственно и будет являться.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment