Скажите пожалуйста, как связано определение инкапсуляции из вики с содержимым статьи? Не находите противоречий? А что если вы не показали инкапсуляцию, а показали лишь реализацию приватных полей в dart-классах через геттеры и сеттеры? Ваш материал не про инкапсуляцию, а про что-то ещё.
Простите, но это уже третья статья-перевод одного и того же, с почти что уже 2-ух месячной просрочкой актуальности. Да и на две части разделённая не из благих намерений. Вопроса два: чем эти статьи хуже:
Привет, крутая история! Из стейт-менеджера рекомендую рассмотреть riverpod, поскольку он сильно освобождает от бойлерплейта и одновременно совмещает концепцию внедрения и реактивных BLoC-классов одновременно.
От getx вооще стоит отказываться, поскольку он отвратительно написан и для больших проектов не подходит.
Под рукой нет компухтера, чтобы детальней просмотреть, но чем getstorage отличается от SharedPreferences? Сомнительным бенчмарком?) Да, из доки я видел, что там есть прослушка к примеру. Что там ещё есть, чего не хватает в SP? Чисто из спортивного интереса... Я недавно выпустил cardoteka на основе SP, которая добавляет типизацию, работу с null, прослушку на основе коллбеков и разделение имён хранилищ. Может это покроет какие-то ваши кейсы использования... :)
Приношу извинения, что вы были вынуждены клацнуть и попасть сюда. Это ограничение формата в 1500 символов в который раз не даёт мне возможности полностью раскрыть свои мысли с помощью кода. Раздражает, ведь нет способа сделать это ещё ёмче и лаконичней (субъективный взгляд), а на полноценную статью этого материала недостаточно.
Собственно, код ниже:
class Wrapper {
Wrapper(this.i);
int i;
}
int increase(int i) => i + 1;
main() {
final w = Wrapper(0);
w.i = increase(w.i);
print(w.i); // 1
}
Непосредственное нахардкоживание late final во вьюмоделях может означать только одно - что нам делать, когда придёт время тестов. И честно, не увидел в readme пакета и не услышал в статье ни одного слова о тестировании: как и возможно ли?
Далее, примеры счётчиков настолько заезженные и банальные, что не отражают ровным счётом ничего и плохо пахнут. В противовес вашему примеру, пример на ValueNotifier(соблюдая именования и стиль):
Организация, к примеру, полноценной поисковой строки, с фильтрами и различными состояниями, и парой необычных возможностей была бы куда интересней и продуктивней.
Пункт "Почему не использовать уже существующее решение?" откровенно слаб и очень хочется его реального раскрытия. И вот почему:
Riverpod:
Не нравится подход со смешиванием DI и State Management'а.
Однако заметьте, что в реальном приложении придётся использовать и то и другое (под каким бы соусом не был подан DI). В данном случае, мы бы воспользовались vessel + beholder. А есть ли смысл импортировать два пакета вместо одного?
Засорение глобального скоупа
Тем, что мы имеем один ProviderScope, в котором содержится один ProviderContainer, который и содержит состояния наших провайдеров? Ну я вам скажу, что ещё можно поиграться с UncontrolledProviderScope и контейнеры создавать независимо. А ещё использовать ProviderScope.overrides и ProviderScope.parent для переопределения для конкретной ветки.
Тяжело масштабировать
Пожалуй это самое нелепое обвинение в сторону Riverpod. Начну с того, что StateNotifier уже устаревшая концепция. Используйте (Async)NotifierProvider. И комбинируйте состояния ровно также, как вы это делаете в случае с вашей библиотекой (ваш последний пример не ясен, возможно он содержит ошибку в именовании SearchUsersViewModel|UsersViewModel):
final selectedProjectId = StateProvider((_) => 32);
final users = Provider((_) => <User>[]);
final filteredUsers = Provider((ref) {
final projectId = ref.watch(selectedProjectId);
return ref
.watch(users)
.where((user) => user.projects.contains(projectId))
.toList();
});
Это классический стиль. При необходимости дополнительного namespase перенесите провайдеров в статические поля ваших ViewModel, либо используйте Notifier, если планируется управление над получившимся состоянием:
class FilteredUsersNotifier extends Notifier<List<User>> {
late List<User> _users;
late int _projectId;
@override
List<User> build() {
_users = ref.watch(users);
_projectId = ref.watch(selectedProjectId);
return _users.where((user) => user.projects.contains(_projectId)).toList();
}
User findById(id) {/*делайте что-то*/}
}
Опять же, такие примеры выглядят глупо из-за отсутствия реальной задачи.
Bloc:
Определение более-менее сложных состояний требует кодогенерации copyWith.
copyWith используется, когда модели являются иммутабельными и стейт-менеджер основан на сравнении hashcode для обновления состояния. Как в этом плане работает beholder? Если он основан на мутабельном состоянии, то как избежать лишних перестроек, когда данные на самом деле не изменились, но их присвоение произошло?
Субъективно, но в больших проектах именование Event'ов и State'ов начинает напоминать энтерпрайз Java: class RefreshPostsHomeScreenEvent
А как мы это избегаем здесь? ModelView превращаются в повелители всего и вся с сотнями методов и сотнями состояний?
AsyncState, AsyncValue, Result.guard - это всё мне что-то очень сильно напоминает на подход в R..?, ну ладно, окей.
---
Подводя черту, ваш стейт-менеджер может намного больше, под капотом там всё действительно интересно. Хабр хочет внутренностей и живых примеров приложений, основанных на данном пакете. Быть может, стоит показать конкретный пример, на котором сильно забуксуют имеющиеся менеджеры, а ваш решит проблему с лёгкостью. Пишите, пожалуйста, ещё. Независимо от всего, вы молодец, проделали большую работу, а полученный опыт может послужить хорошим фундаментом для будущих улучшений и новых пакетов. ?
Здравствуйте! Спасибо за такое прекрасное издание (и, конечно же, за ожидаемое переиздание) - читается на одном дыхании! Правда в старом издании мне больше симпатизировал Comic Sans нежели чем скучный и невероятно острый Arial Narrow для примеров кода.
Появились полноценные лабораторные работы, красивые скриншоты, как например в главе "Установка и настройка рабочего окружения", раздел с Record, раздел с "dynamic vs Object", некоторое количество лайфхаков по типу чтения с клавиатуры - очень круто! Это прекрасное издание для новичков было, а теперь ещё и с заряженным dart 3. Очень жду раздел про необъятный patterns :) При чём мне очень нравится, что это больше похоже на приятную и строгую мини-энциклопедию по языку, нежели чем на растянутые мануалы субъективщины. Продолжайте в том же духе :)
Отправил вам небольшой заряд мотивации, а-ля "немного на хлеб насущный". Хорошего дня ?
Да, миграция на dart 3 оказалась вполне приятной. Возможно, в силу специфичности моих проектов, но:
в приложении Weather Today это выглядело буквально вот так commit. При чём проект не обновлялся полгода и только поэтому я сделал такой непринуждённый обобщённый коммит, обновив сразу все доступные зависимости. Но для dart 3 этого не требовалось. Всё сразу заработало без танцев
в пакете weather_pack тоже всё прошло легко. Но проект совсем простой, это плохое сравнение с бизнес кейсами
прямо сейчас происходит миграция одного чуть бОльшего приложения чем погодка, с кучей устаревших зависимостей (даже форков для которых нет) и с флагом --no-sound-null-safety в командной строке при запуске/билде. В текущую минуту уже избавились от флага и на стадии "а не накатить ли dart 3", но:
если форков нет, нужно самому править пакеты, что занимает много времени
лучше потратить время на работу с кодовой базой, которая тоже не ахти (включая архитектуру)
в этом проекте нет нужды ни в pattern matching, ни в switch expressions, ни в модификаторах классов
Плюс в том, что некоторые популярные пакеты уже перешли на новую версию и мы этого можем даже не замечать, если автор указал минорное|патч повышение версии. И также жизнь облегчает то, что pub tool пока что позволяет ставить пакеты даже с ограничением а-ля sdk: '>=2.14.0 <3.0.0':
Dart’s pub tool allows resolution even when the upper bound is limited to versions below 3.0.0. For example, a package with the following constraint will be allowed to resolve with a Dart 3.x SDK, as pub will re-interpret the upper-constraint <3.0.0 as <4.0.0 when the lower constraint is 2.12 or higher
Опять же, когда я попробовал switch выражения, то забыл об идиотском обходе используя анонимные функции или "большом разглагольствовании". Когда я попробовал сопоставление (вместе с sealed) - пришлось привыкать к новому синтаксису, но оказалось вполне удобным. Records оказались также весьма кстати. Модификаторы - это для ультра проектов, либо для повышенного удобства использования пакетов (для авторов пакетов). А вот различные виды сопоставлений не могу освоить до сих пор (имеется ввиду, чтобы их восприятие стало для меня родным и удобным). Лично мне сейчас очень не хватает data-классов, метапрограммирования и нормального ide-рефакторинга.
Спасибо за статью, посылы несёте праведные! Хочу обратить внимание на некоторые вещи:
У вас слетели отступы (для dart обычно принят отступ в два пробела)
Чем продиктована необходимость использовать префикс I для именования интерфейсов? Былой разработкой на java?)) Есть мнение, что лучше именовать реализации интерфейсов, добавляя постфикс Impl (вот так: ClipboardServiceImpl -> ClipboardService), тогда ваша бизнес-логика не имеет лишней семантической сложности.
Стоит заметить, что для простых приложений лучше воспользоваться как раз таки if-else реализациями, нежели чем плодить абстракции абстракций интерфейсов :) В данном случае может показаться, что сделать интерфейс для сервиса достаточно просто. Но вспомните реальные кейсы с необходимой реализацией 10-20 методов. Мой вердикт - by design. Внедряем, если есть неотрицательная вероятность "бизнес захочет"
Используйте interface class или даже abstract interface class вместо abstract class, чего стесняться, если dart 3 разрешает
Спасибо за статью! Стоит отметить, что у go_router очень изменчивое api от версии к версии, которое пока что не стабилизировалось. Но пакет сильно значимый. Хочется верить, что получится учесть прошлый костыльный опыт по навигации и сделать favorite-пакет (я не про плашку в pub.dev)
Воу, прочёл с удовольствием, спасибо за материал. Как по мне, late накладывает свои ограничения, только если код изначально запутан и плох. Когда late переменная инициализируется где-то там и когда-то потом и эта самая инициализация находится на 100 строк ниже.
Во всех остальных случаях, в том числе в конструкторах и в State виджетах, это более чем уместно, нежели чем использовать nullable и терять final.
Излюбленным использованием является отложенное создание "дорогого" объекта, если он таки может и не понадобиться в итоге. Это особенно хорошо выглядит в локальных местах, где nullable семантически режет глаза, а очередная проверка на null сбивает с толку.
Скажите пожалуйста, как связано определение инкапсуляции из вики с содержимым статьи? Не находите противоречий? А что если вы не показали инкапсуляцию, а показали лишь реализацию приватных полей в dart-классах через геттеры и сеттеры? Ваш материал не про инкапсуляцию, а про что-то ещё.
Это было весьма содержательно и интересно! Спасибо за материал.
Простите, но это уже третья статья-перевод одного и того же, с почти что уже 2-ух месячной просрочкой актуальности. Да и на две части разделённая не из благих намерений. Вопроса два: чем эти статьи хуже:
Flutter 3.16: что нового / Хабр
Flutter 3.16: обзор обновления фреймворка с комментариями разработчиков Surf / Хабр
и надо ли оно?
Честно говоря, ваш Facade ни на секунду не фасад получился :)
Привет, крутая история! Из стейт-менеджера рекомендую рассмотреть riverpod, поскольку он сильно освобождает от бойлерплейта и одновременно совмещает концепцию внедрения и реактивных BLoC-классов одновременно.
От getx вооще стоит отказываться, поскольку он отвратительно написан и для больших проектов не подходит.
Под рукой нет компухтера, чтобы детальней просмотреть, но чем getstorage отличается от SharedPreferences? Сомнительным бенчмарком?) Да, из доки я видел, что там есть прослушка к примеру. Что там ещё есть, чего не хватает в SP? Чисто из спортивного интереса... Я недавно выпустил cardoteka на основе SP, которая добавляет типизацию, работу с null, прослушку на основе коллбеков и разделение имён хранилищ. Может это покроет какие-то ваши кейсы использования... :)
Пиши ещё, интересный путь ?
Хорошего настроения и интересных проектов! С Новым годом, друзья! ?
А есть ещё такая штука, как rfw. Это имеет какие-то общие грани с вашим проектом?
Спасибо, было интересно прочитать!
Спасибо большое за материал! Очень полезно и ёмко.
Кстати, когда добавят md preview без костылей вида Choose Boot Java Runtime...? И даже этот костыль перестал работать в Android Studio Iguana
Приношу извинения, что вы были вынуждены клацнуть и попасть сюда. Это ограничение формата в 1500 символов в который раз не даёт мне возможности полностью раскрыть свои мысли с помощью кода. Раздражает, ведь нет способа сделать это ещё ёмче и лаконичней (субъективный взгляд), а на полноценную статью этого материала недостаточно.
Собственно, код ниже:
Непосредственное нахардкоживание
late finalво вьюмоделях может означать только одно - что нам делать, когда придёт время тестов. И честно, не увидел в readme пакета и не услышал в статье ни одного слова о тестировании: как и возможно ли?Далее, примеры счётчиков настолько заезженные и банальные, что не отражают ровным счётом ничего и плохо пахнут. В противовес вашему примеру, пример на
ValueNotifier(соблюдая именования и стиль):Организация, к примеру, полноценной поисковой строки, с фильтрами и различными состояниями, и парой необычных возможностей была бы куда интересней и продуктивней.
Пункт "Почему не использовать уже существующее решение?" откровенно слаб и очень хочется его реального раскрытия. И вот почему:
Riverpod:
Однако заметьте, что в реальном приложении придётся использовать и то и другое (под каким бы соусом не был подан DI). В данном случае, мы бы воспользовались vessel + beholder. А есть ли смысл импортировать два пакета вместо одного?
Тем, что мы имеем один
ProviderScope, в котором содержится одинProviderContainer, который и содержит состояния наших провайдеров? Ну я вам скажу, что ещё можно поиграться с UncontrolledProviderScope и контейнеры создавать независимо. А ещё использоватьProviderScope.overridesиProviderScope.parentдля переопределения для конкретной ветки.Пожалуй это самое нелепое обвинение в сторону Riverpod. Начну с того, что
StateNotifierуже устаревшая концепция. Используйте (Async)NotifierProvider. И комбинируйте состояния ровно также, как вы это делаете в случае с вашей библиотекой (ваш последний пример не ясен, возможно он содержит ошибку в именованииSearchUsersViewModel|UsersViewModel):Это классический стиль. При необходимости дополнительного namespase перенесите провайдеров в статические поля ваших ViewModel, либо используйте
Notifier, если планируется управление над получившимся состоянием:Опять же, такие примеры выглядят глупо из-за отсутствия реальной задачи.
Bloc:
copyWithиспользуется, когда модели являются иммутабельными и стейт-менеджер основан на сравнении hashcode для обновления состояния. Как в этом плане работает beholder? Если он основан на мутабельном состоянии, то как избежать лишних перестроек, когда данные на самом деле не изменились, но их присвоение произошло?А как мы это избегаем здесь? ModelView превращаются в повелители всего и вся с сотнями методов и сотнями состояний?
AsyncState,AsyncValue,Result.guard- это всё мне что-то очень сильно напоминает на подход в R..?, ну ладно, окей.---
Подводя черту, ваш стейт-менеджер может намного больше, под капотом там всё действительно интересно. Хабр хочет внутренностей и живых примеров приложений, основанных на данном пакете. Быть может, стоит показать конкретный пример, на котором сильно забуксуют имеющиеся менеджеры, а ваш решит проблему с лёгкостью. Пишите, пожалуйста, ещё. Независимо от всего, вы молодец, проделали большую работу, а полученный опыт может послужить хорошим фундаментом для будущих улучшений и новых пакетов. ?
Здравствуйте! Спасибо за такое прекрасное издание (и, конечно же, за ожидаемое переиздание) - читается на одном дыхании! Правда в старом издании мне больше симпатизировал Comic Sans нежели чем скучный и невероятно острый Arial Narrow для примеров кода.
Появились полноценные лабораторные работы, красивые скриншоты, как например в главе "Установка и настройка рабочего окружения", раздел с Record, раздел с "dynamic vs Object", некоторое количество лайфхаков по типу чтения с клавиатуры - очень круто! Это прекрасное издание для новичков было, а теперь ещё и с заряженным dart 3. Очень жду раздел про необъятный patterns :) При чём мне очень нравится, что это больше похоже на приятную и строгую мини-энциклопедию по языку, нежели чем на растянутые мануалы субъективщины. Продолжайте в том же духе :)
Отправил вам небольшой заряд мотивации, а-ля "немного на хлеб насущный". Хорошего дня ?
Да, миграция на dart 3 оказалась вполне приятной. Возможно, в силу специфичности моих проектов, но:
в приложении Weather Today это выглядело буквально вот так commit. При чём проект не обновлялся полгода и только поэтому я сделал такой непринуждённый обобщённый коммит, обновив сразу все доступные зависимости. Но для dart 3 этого не требовалось. Всё сразу заработало без танцев
в пакете weather_pack тоже всё прошло легко. Но проект совсем простой, это плохое сравнение с бизнес кейсами
прямо сейчас происходит миграция одного чуть бОльшего приложения чем погодка, с кучей устаревших зависимостей (даже форков для которых нет) и с флагом
--no-sound-null-safetyв командной строке при запуске/билде. В текущую минуту уже избавились от флага и на стадии "а не накатить ли dart 3", но:если форков нет, нужно самому править пакеты, что занимает много времени
лучше потратить время на работу с кодовой базой, которая тоже не ахти (включая архитектуру)
в этом проекте нет нужды ни в pattern matching, ни в switch expressions, ни в модификаторах классов
Плюс в том, что некоторые популярные пакеты уже перешли на новую версию и мы этого можем даже не замечать, если автор указал минорное|патч повышение версии. И также жизнь облегчает то, что pub tool пока что позволяет ставить пакеты даже с ограничением а-ля
sdk: '>=2.14.0 <3.0.0':Опять же, когда я попробовал switch выражения, то забыл об идиотском обходе используя анонимные функции или "большом разглагольствовании". Когда я попробовал сопоставление (вместе с sealed) - пришлось привыкать к новому синтаксису, но оказалось вполне удобным. Records оказались также весьма кстати. Модификаторы - это для ультра проектов, либо для повышенного удобства использования пакетов (для авторов пакетов). А вот различные виды сопоставлений не могу освоить до сих пор (имеется ввиду, чтобы их восприятие стало для меня родным и удобным). Лично мне сейчас очень не хватает data-классов, метапрограммирования и нормального ide-рефакторинга.
Спасибо за статью, посылы несёте праведные! Хочу обратить внимание на некоторые вещи:
У вас слетели отступы (для dart обычно принят отступ в два пробела)
Чем продиктована необходимость использовать префикс
Iдля именования интерфейсов? Былой разработкой на java?)) Есть мнение, что лучше именовать реализации интерфейсов, добавляя постфиксImpl(вот так:ClipboardServiceImpl->ClipboardService), тогда ваша бизнес-логика не имеет лишней семантической сложности.Стоит заметить, что для простых приложений лучше воспользоваться как раз таки if-else реализациями, нежели чем плодить абстракции абстракций интерфейсов :) В данном случае может показаться, что сделать интерфейс для сервиса достаточно просто. Но вспомните реальные кейсы с необходимой реализацией 10-20 методов. Мой вердикт - by design. Внедряем, если есть неотрицательная вероятность "бизнес захочет"
Используйте
interface classили дажеabstract interface classвместоabstract class, чего стесняться, если dart 3 разрешаетЭто была ошибка. Правильный вариант вот такой:
Тип record, содержащий ровно одно позиционное поле, требует завершающей запятой. Для именованных полей это не имеет значение.
Получение позиционных полей идёт с $1 и далее.
Также стоит заметить, что для позиционных полей можно не указывать имя. Оно влияет только на документацию (и понимание кода).
Актуальную спецификацию можете посмотреть здесь - Records Feature Specification
Спасибо за материал! Имейте ввиду, что на текущий момент новый способ не работает под web и windows:
[AppLifecycleListener] Events not triggered #130566
Спасибо! Очень интересно было почитать про такое :)
Спасибо за статью! Стоит отметить, что у go_router очень изменчивое api от версии к версии, которое пока что не стабилизировалось. Но пакет сильно значимый. Хочется верить, что получится учесть прошлый костыльный опыт по навигации и сделать favorite-пакет (я не про плашку в pub.dev)
Воу, прочёл с удовольствием, спасибо за материал. Как по мне, late накладывает свои ограничения, только если код изначально запутан и плох. Когда late переменная инициализируется где-то там и когда-то потом и эта самая инициализация находится на 100 строк ниже.
Во всех остальных случаях, в том числе в конструкторах и в State виджетах, это более чем уместно, нежели чем использовать nullable и терять final.
Излюбленным использованием является отложенное создание "дорогого" объекта, если он таки может и не понадобиться в итоге. Это особенно хорошо выглядит в локальных местах, где nullable семантически режет глаза, а очередная проверка на null сбивает с толку.