Приношу извинения, что вы были вынуждены клацнуть и попасть сюда. Это ограничение формата в 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 format` к коду и выбрать язык dart для подсветки синтаксиса, проследить полноту информации ваших скриншотов. Сейчас читать это близко к невозможному.
Если же мы возвращаем void, а не значение вместо value в then ставим прочерк(_).
Это не так. Я перечитал несколько раз данное предложение и это всё ещё не так. Мы ставим прочерк в том случае, когда аргумент нам не нужен. Мы не собираемся его использовать, вот и всё. Это лучше выражает наши намерения и делает код семантически ясней.
Хорошего настроения и интересных проектов! С Новым годом, друзья! ?
А есть ещё такая штука, как 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 сбивает с толку.
Что же касается имеющейся чисто технической информации, то вам нужно поработать над подачей и оформлением. Минимально – перечитать ещё раз и обратить внимание на семантические и синтаксические ошибки, применить `dart format` к коду и выбрать язык dart для подсветки синтаксиса, проследить полноту информации ваших скриншотов. Сейчас читать это близко к невозможному.
Это не так. Я перечитал несколько раз данное предложение и это всё ещё не так. Мы ставим прочерк в том случае, когда аргумент нам не нужен. Мы не собираемся его использовать, вот и всё. Это лучше выражает наши намерения и делает код семантически ясней.
Brave Награды?)
Спасибо, это было интересное, хотя и короткое путешествие в мир оптимизаций. Как говорится, пишите ещО :)
Если сервис дёргает метод репозитория, значит он связан. На диаграмме этого не показано, вот в чём вопрос :)