Как стать автором
Обновить
18
0.1
Журат Максим @ChessMax

Пользователь

Отправить сообщение

Ссылка на проект, который можно запустить, конечно, не помешала бы. Количество метаданных в примере с конструктором больше чем кода самого конструктора))

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

Динамическая типизация, когда она применяется с умом — всегда приятно и хорошо.

В приведенном примере нет никакой динамической типизации. Рекорды в дарт строго типизированы. Если не указать тип, то компилятор выводит его автоматически. Это называется type inference.

Rx.combileLatest(...)

Видимо имелось ввиду Rx.combineLatest(...)

Однако и на этом синтаксический сахар не заканчивается! Нужно познакомиться еще с двумя понятиями: Guard clause и Exhaustiveness checking.

Exhaustiveness checking - это не синтаксический сахар. Синтаксический сахар - это когда, что-то можно сделать более удобно, чем раньше, при этом старый способ все так же работает. Exhaustiveness checking можно сделать только с помощью switch, никаких других вариантов нет.

Однако же, это работает и в обратном направлении: если убрать dynamic, то анализатор, совершенно справедливо, скажет Dead code. И это действительно так, ввиду динамической типизации и понимания контекста.

Если мы убрали dynamic, то у нас нет никакой динамической типизации, как уже выше рассмотрели.

— Алгебраические типы данных. Ну, то есть красивое название для sealed-классов.

Алгебраические типы данных это не только sealed классы.

Да, на первый взгляд конструкции почти идентичные. Разница в том, что Record - это тип (который мы присвоили переменной myCoolRecord), а Pattern - это выражение.

Переменной нельзя присвоить тип, только значение. Pattern - это не выражение, так как его нельзя сохранить в переменной.

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

```

LoginRepository repository; LoginBloc bloc; setUp(() { repository = MockLoginRepository(); bloc = LoginBloc(repository); });

```

Текст не соответствует коду? В коде и репозиторий и блок создаются для каждого теста. При этом этот код еще и не соответствует коду в гитхабе.

final String? email;

final String? password;

Эти поля повторяются в трех классах? Может быть стоило вынести в какой-то базовый класс? Или миксин? В таком случае код геттера emailStr можно было бы значительно сократить. Еще момент в том, что во всех состояниях эти поля зануляемые, что странно. Блок как раз и славится тем, что позволяет безболезненно моделировать точные состояния.

extension LoginStateX on LoginState { String? get emailStr { if (this is LoginInitialState || this is LoginSuccessState) { return null; } else if (this is LoginDataState) { return (this as LoginDataState).email; } else if (this is LoginLoadingState) { return (this as LoginLoadingState).email; } else if (this is LoginErrorState) { return (this as LoginErrorState).email; } return null; } }

Конечно вкусовщина, но возможно стоило не создавать расширение, а перенести этот геттер прямо в класс `LoginState`? Кстати, если сохранить this в локальную переменную, то не придется писать `as LoginDataState`.

act: (_bloc) => _bloc.add(EditedEmail('example@sample.com')),

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

blocTest( 'emits [LoginDataState] after adding email', build: () => bloc, act: (_bloc) => _bloc.add(EditedEmail('example@sample.com')), expect: () => [ isA<LoginDataState>(), ], );

Разве проверки одного только типа состояния достаточно? А вдруг в нем измененный эмейл не сохранится? Или сохранится не тот? Аналогично и в следующем тесте. Вдруг изменение пароля повлияло на ранее сохраненный эмейл?

Тут мы использовали еще одно свойство blocTest - seed, которое нужно для подстановки изначального состояния в BLoC.

Как то использование seed выглядит не очень уместным и даже опасным. По хорошему обычно изолируют зависимости, а в логику тестируемого класса извне не лезут. Другими словами лучше взаимодействовать с тестируемой сущностью только доступными публичными методами. Так как сделано ниже.

blocTest( 'emits [LoginErrorState] if email is null', build: () => bloc, act: (_bloc) { _bloc.add(EditedPassword('myPass123')); _bloc.add(LoginButtonPressed()); }, expect: () => [ isA<LoginDataState>(), isA<LoginLoadingState>(), isA<LoginErrorState>(), ], );

По идее `_bloc.add(EditedPassword('myPass123'));` должно быть в build, так как это относится к части arrange.

Если мы внимательно посмотрим на код, то увидим, что нужно протестировать следующие кейсы:

Наверное так же стоило проверить случаи когда пароль и/или логин неправильные?

Запустил тесты из репозитория, но один не проходит:

package:matcher                              expect
package:mocktail/src/mocktail.dart 595:5     VerificationResult.called
test\auth_bloc\login_bloc_test.dart 93:18    main.<fn>.<fn>
package:bloc_test/src/bloc_test.dart 230:21  testBloc.<fn>
===== asynchronous gap ===========================
dart:async                                   _Completer.completeError
package:bloc_test/src/bloc_test.dart 257:43  _runZonedGuarded.<fn>
===== asynchronous gap ===========================
dart:async                                   _CustomZone.registerBinaryCallback
package:bloc_test/src/bloc_test.dart 254:5   _runZonedGuarded.<fn>
dart:async                                   runZonedGuarded
package:bloc_test/src/bloc_test.dart 253:3   _runZonedGuarded
package:bloc_test/src/bloc_test.dart 200:11  testBloc
package:bloc_test/src/bloc_test.dart 156:13  blocTest.<fn>

Expected: <1>
  Actual: <3>
Unexpected number of calls

Печалька...

if (state.emailStr?.isNotEmpty == false || state.emailStr?.isNotEmpty == false) { emit(LoginErrorState( email: state.emailStr, password: state.passwordStr, errorToShow: 'Email or password is empty', )); return; }

Дважды на пустоту проверяется email, а пароль нет? Странно, что ваши тесты это не отловили. Возможно вам стоит посмотреть в сторону TDD?

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

Функция compute не кэширует результаты.

Спасибо, интересные нюансы. Не знал о них.

Было бы интересно узнать про отличия VCL от FireMonkey. А также плюсы и минусы последнего.

Ну главное, чтобы ребенку нравилось. Смысл идти, если не твое? Ну и если что, вернуться в где можно же?

но не весь спектр задач должен закрываться ими

Согласен.

Автор riverpod уже не однократно говорил, что с релизом макросов, riverpod будет их использовать. Интересно, останется ли возможность использовать riverpod без них? Подозреваю, что нет.

В Dart нет специального синтаксиса для объявления интерфейсов. Любой класс может действовать как интерфейс, и другой класс может его реализовать с implements:

Строго говоря это не совсем так.

Спасибо за статью.

Вы переизобрели MobX) Читая статью думал, будет ли computed, но не увидел (жаль). На самом деле с пользовательской точки зрения это намного удобнее (хотя свои минусы тоже есть). Вместо кучи combineN и RebuilderN был бы один ComputedValue и Rebuilder. Потому, что постоянно указывать и менять цифры, прописывать для всего типы и все это вручную, ну такое... "Продать" относительно Stream и rxdart конечно получилось, но сравнивать, на мой взгляд, нужно не с ними, а с MobX и его аналогами, и возможно даже с Riverpod.

  1. StreamController, через который предполагается посылать обновления в Stream, требует вызова dispose(), а также имеет переусложнённый синтаксис: Stream и StreamController это два разных объекта.

Строго говоря у StreamController -а нет метода dispose.

В подписчик передаётся значение на момент обновления Value через сеттер, а не на момент вызова подписчика, в ряде случаев это важно.

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

такое поведение можно отключить, задав параметр distinctMode = false:

Наверное лучше было бы использовать предикат? В таком случае возможности были бы шире.

Пример с combine2 не компилируется. Зачем делать в CombinedValueSubscription метод отмены с типом Future, если ассинхронность не используется?

При этом теряется возможность обработки ошибки в Stream (в Value эта концепция отсутствует), но можно через параметр errorBuilder задать преобразование ошибки в значение Value (например в null, если underlying тип nullable).

Если использовать тип Either, то можно не терять возможность обработки ошибок.

final startSw = Stopwatch()..start();

Создавать новый экземпляр Stopwatch на каждый вызов notify несколько накладно.

scheduleMicrotask(() async { // unawaited

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

//! There is a potential problem in multiple entering paused state

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

@override R get value => transformer(origin.value);

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

Примечателен тот факт, что Value сам по себе не требует вызова dispose(), он штатно утилизируется сборщиком мусора.

Вы в нескольких местах что-то подобное пишете, но по факту это не совсем правильно. По-хорошему ваш Value так же должен предоставлять метод dispose или его аналог, т.к. во-первых, чтобы можно было отписаться от все подписок, с текущим апи это сделать нельзя. Во вторых наследники этого класса используют, например Timer, который тоже никак не остановить, насколько я понял и он будет тикать, хотя уже может быть давно не нужен. Просто если тот же StreamController использовать по аналогии с вашими классами, то мы так же не обязаны вызывать dispose и будет плюс-минус такое же поведение, что и вас.

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

То что есть порядок вычислений и не все считается, не говорит, о том, что таблица истинности не используется. Вот, например, есть выражение a && b && c. Мы вычислили значение a и оно равно false. Воспользовавшись таблицей истинности и тем свойством, что считать все остальное не нужно вернули результат сразу. Оперировать таблицей в общем случае, это не значит, что нужно посчитать сначала все операнды. И вот здесь не соответствие между не оперирует, хотя на самом деле вполне оперирует. Мне кажется без таблицы истинности (явно или не явно) нельзя реализовать эти операторы, т.к. таблица истинности это и есть определение операции, просто в табличной форме, разве нет?

Да это понятно. Просто фраза "не оперируют таблицей истинности" звучит странно (но может быть я не прав), как будто бы при вычислении она не используется, хотя она используется. И то что первый или второй операнды могут не вычисляется, это (опять, как мне кажется) не говорит, о том, что таблица истинности не используется. Это лишь говорит, что при вычислении выражения используются свойства логических операций, позволяющие не выполнять ненужных операций.

Они не оперируют таблицей истинности, хотя их результат совпадает с соответствующими функциями, когда те применимы

Можно вот этот момент разъяснить? Если результат совпадает, то это значит, что они оперируют таблицей истинности, разве нет? Или что здесь имеется ввиду?

Может быть подойдут сервисы по типу клавагонок ? Небольшой спортивный азарт, кастомизация машинок, все лучше "бессмысленного" (с точки зрения ребенка) перепечатывания текста с экрана? Кроме того можно тренироваться на своих текстах. Согласен, не идеально, но хоть что-то?

Спасибо за совет. Смотрю нескольких, но ощущение, что толку от этого нет. Своего рода развлекательный контент. Запомнить что-то малореально.

Но играть сильнее они почему то не стали, а вот видеть мат – стали точно лучше. Парадокс!

Вот это очень интересный момент. Тоже за собой такое замечаю. Задачи решаются относительно неплохо. А вот качество игры никакущее. И как это исправить не особо понятно. А что на ваш взгляд наиболее сильно влияет на улучшение именно качества игры в целом?

Более того я потеряю премию

и

денег мне и так хватает)

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

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

− чтобы текст начал обрезаться с многоточием, элементу текста надо дать возможность занимать меньше отведённого под полный текст места без ошибки RenderFlex overflowing , то есть применить констрейнт

Чтобы текст начал обрезаться нужно тексту задать ограничение по ширине (это легко проверить, засунув текст в SizedBox). Так же нужно понимать как работает лейаут у Row/ Column. Оверфлоу возникает не у текста, а у row. Row спускает unbound (от нуля до бесконечности) ограничения своим детям. Т.е. говорит тексту будь любых размеров, вне зависимости от ограничений row (от ширины row). И если по итогу ширина текста превышает ограничения row, то и происходит оверфлоу. Таким образом текст не обрезается из-за того, что от row пришли unbound ограничения. Вот и все. Крайне полезно разобраться с тем как работают лейаут и ограничения во флаттер (можно сделать по вот этой статье и своим экспериментам или чтением исходников). И тогда жизнь станет значительно проще)) Соответственно, если знаешь как эта система работает, то сделать так, чтобы она работала как тебе нужно обычно не сложно...

Информация

В рейтинге
3 436-й
Откуда
Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Mobile Application Developer
Senior
Flutter
Flutter Bloc
MobX
Mobile
Development of mobile applications