Pull to refresh
18
0
Валерий @rookie_cruekie

Программист

Send message

Это старая история, да, не буду холиварить. Я ж говорю, статья для тех, кто использует GetX именно в плоскости их офигенного State Management. Каждый выбирает для себя. Полтора года использования этой либы на пяти-шести проектах - полет нормальный. Немного пилим напильником, но не принципиально. Если что, мы не понаслышке знаем про Provider, Redux, setState (хехе), так что наш выбор управления состоянием осознанный.

Зачем в представлении делать counter++ и дальше говорить о каком-то UDF? 

Потому что под капотом декоратора находится переопределение оператора ++ (полная мимикрия под стандартный Rx<int>, а как же иначе - это же паттерн "декоратор"), и инкремент значения не изменится напрямую, а только лишь после взаимодействия с логикой модели, посмотрите код в гисте, там кстати полно юнит-тестов и есть пример для понимания работы.
Вкратце поток логики будет такой:

  1. View скомандовала, что хочет инкремента

  2. Сеттер (часть модели) провел операции над этой командой, приняв решение

  3. Значение реактивного поля изменилось

  4. View реактивно перерисовалась

Никак иначе чем через модель, изменение и команда на перерисовку не произойдет. Ровный такой UDF.

Ведь в реальности во вью будет какой-нибудь...

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

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

/// Модель для счетчика лайков.
class LikesController extends GetxController {
  /// Чисто обыгрыш логики.
  static const _isAdmin = true;

  /// Сама переменная, которая не может быть приватной, следовательно
  /// доступна для нарушения инкапсуляции и логики.
  var likeCounter = 0.obs;

  /// Метод изменения переменной.
  void like() {
    if (_isAdmin) {
      likeCounter.value = likeCounter() + 2;
    } else {
      likeCounter.value = likeCounter() + 1;
    }
  }
}

/// Где-то на View
ObxValue(
  (data) => ElevatedButton(
    onPressed: c.like,
    child: Text('Std Likes: $data'),
  ),
  c.likeCounter,
),

Ну вроде все норм, только есть нюанс. Переменная likeCounter публична (а как иначе, если только не накладывать ограничения на размещение контроллера в пакедже View, ну или другие фокусы). Соответственно, никто не мешает постучаться в нее напрямую:

ObxValue(
  (data) => ElevatedButton(
    // Уппс, ай дид ит эген
    onPressed: () => c.likeCounter.value = c.likeCounter.value + 1,
    child: Text('Std Likes: $data'),
  ),
  c.likeCounter,
),

и вся логика превратится в тыкву.

Давайте теперь применим GetRxDecorator.

/// 4-в-1: Rx-переменная, геттер, сеттер + стрим на всякий случай.
/// Строгая инкапсуляция _значения_ переменной.
final likeCounterDeco = 0.obsDeco(setter: (oldValue, _, __) {
  if (_isAdmin) {
    return oldValue + 2;
  } else {
    return oldValue + 1;
  }
});

А в клиенте будет как-то так

Obx(
  () => ElevatedButton(
    onPressed: c.likeCounterDeco,
    child: Text('Decorator Likes: ${c.likeCounterDeco}'),
  ),
),

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

P.S. И, может быть, не надо вставлять код картинками?

Да, зря я так. Надо будет поправить, согласен. Но с другой стороны это же просто сниппеты, а в gist и в pub.dev есть полный код, примеры и тесты.

Ох, пахнуло снобизмом-то как! Наша команда умеет в BuildContext достаточно, чтобы связно объяснить на JobInterview про все три дерева Flutter, про их задачи и  не ляпать антипаттерны типа Widget _someFunc(). Более того, наша команда понимает, что бездумное использование как Get, так и любого другого подхода - это путь в никуда. Наша команда знает, как под капотом устроен, к примеру, Obx, или уже проще - Get.width. Именно поэтому мы никогда не напишем MediaQuery.of(context).size.width, вместо Get.width, потому что чтобы достичь одного и того же результата первый соберет с десяток вызовов, а второй - всего один, чтобы достичь одного и того же результата:
size = window.physicalSize / window.devicePixelRatio.
В нашей команде вполне себе соседствуют BLoC на уровне Model/Logic и GetxControllers/GetxService на уровне View/ViewModel, потому что они вполне себе дружат и каждый выполняет свою роль наилучшим образом, а писать многословные конструкты из flutter_bloc - увольте! Более того, Getx отлично сочетается с rxdart, как ни странно. В нашем инструментарии, как у хорошего строителя (позволю себе аллегорию), есть и дрель, и шуруповерт и перфоратор и даже отверточек набор. Каждый мастер умеет в инструменты, подходящие под контекст (контекст задачи, если будет угодно).
Бездумное следование за авторитетами ничуть не лучше бездумного делания чего-либо. Вы подумайте об этом как-нибудь на ночь.

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

Возможно. Мы используем из Getx только GetxService, GetxController и их отличный локатор. Я не понимаю, почему из локаторов сделали жупел, думаю из-за неправильного перевода ) Он такой же DI-инструмент, да вот хоть Фоулера почитать. Навигация у него тоже кстати ничего, правда после выхода Navigator 2.0 я как-то больше к последней склоняюсь. С падением контроллеров не сталкивались, уже пяток больших проектов на Getx - полет нормальный. А по поводу вгрызться - ничего такого. Вполне себе соседствуют eazy_localization вместо Getx.localization, Dio вместо GetConnect, Getx очень дружелюбная либа. Ну и попробуйте отказаться от FlutterBloc, если вы начали его использовать - тоже хапнете работенки. Так с любой более-менее серьезной либой будет. Getx очень изящно и легковесно сделан под капотом с точки зрения реактивности, Obs/Obx и протчая... Одно удовольствие работать.

Не знаю, мб Вы знаете?
Я отнюдь не призываю к использованию Get тех, кто по тем или иным причинам не хочет этого делать. Я раскрываю тему дополнительного удобства для тех, кто им пользуется.

А Вы попробуйте с малого, например, с obs/Obx - реактивных состояний.
Как говорится в оригинальной доке:

Если это ваша переменная:

var name = 'Jonatas Borges';

то, чтобы сделать ее реактивной, достаточно добавить obs

var name = 'Jonatas Borges'.obs;

и в UI для реактивного поведения, нужно всего лишь обернуть ее в Obx:

Obx(() => Text("${controller.name}"));

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

И самое главное, вам не надо идти на компромисс между чистотой и принципами ООП. К примеру, чтобы добиться инкапсуляции переменной, не требуется никаких ухищрений (спасибо @AndrewPiterov за решение):

// Делаем переменную приватной
final _name = 'Jonatas Borges'.obs;
// Навешиваем геттер
get name$ => _name.value;
// И сеттер
set name(String value){
  if(value.isNotEmpty){
    _name(value);
  }
}

и все. Реактивность UI не сломается, ничего вообще не надо менять. Obx(() => Text("${controller.name}")) по-прежнему останется реактивным, хотя в других местах кода можно использовать controller.name, как обычную переменную, ведь она таковой и является. Это решение очень изящно реализовано в коде библиотеки, там вообще стоит покопаться для развития.

Да, Вы правы. Время идет, все меняется.

а подскажите, пожалуйста, как правильно приготовить на своем GitLab CI/CD? На родном-то я как-то не заморачивался, и подключил CodeMagic, он из коробки парсит проекты gitlab/github, а тут?

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

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

Спасибо. Это перевод, к сожалению, автор прекратил публиковать серию.
По поводу же lambda, вот выжимка из официального Language Tour:


Most functions are named, such as main() or printElement(). You can also create a nameless function called an anonymous function, or sometimes a lambda or closure. You might assign an anonymous function to a variable so that, for example, you can add or remove it from a collection.

С этим термином изначально в программировании путаница. Кондовые математики конечно порвут рубаху за чистоту терминов, но в реальности lambda function, anonymous function, как-то стали означать одно и то же.

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

Спасибо, обязательно, вот только поизучаю новый плагин.

Впору "Клуб Пострадавших" открывать. Реально вздрогнул, когда представил, сколько бы за месяц набежало.

Ну, может быть. Я пока в это не вляпывался.

Удачи в работе. Надо быть ищущим, и любознательным.

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


Но раз уж Вы за чистоту рядов, то что скажете про реализацию toMap/fromMap в дарте "из коробки"? Отсутствие как минимум признака типа в карте делает ее худо-бедно применяемой только в узком локальном контексте, не позволяя передавать никуда дальше, ибо иначе может приводить к интересным коллизиям типа


test('Когда все пошло не по плану', () async {

    // в точке А.
    final manBefore = Straight(name: 'Serg', age: 25);
    final map = manBefore.toMap();
    print('man before is: ${manBefore.runtimeType} as $map');

    // в точке Б.
    final manAfter = Gay.fromMap(map);
    print('man after is: ${manAfter.runtimeType} as $map');

  });

LOG:
man before is: Straight as {name: Serg, age: 25}
man after is: Gay as {name: Serg, age: 25}

для схожих типов


class Straight {
  final String name;
  final int age;
   ...
class Gay {
  final String name;
  final int age;
   ...

и персонаж даже не узнает об этом, пока не нагнется за мылом....


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

Давайте так, спасибо конечно, но мне тут не надо ничего расписывать, и так уже простыня-простынища.
Предлагаю Вам написать статью, в которой Вы представите свой взгляд. свои подходы, и свое видение.
Я достаточно знаю и про DI, и про BLoC, статья не об этом. Статья о том, что можно эффективно и просто достигать целей, используя очень простые решения. Я писал и с использованием Provider, и с использованием MVU, и в конечном итоге остановился на том, что безумное количество дополнительного кода не может служить оправданием того, что я использую хайповые решения. Видите ли, я сам себе режиссер, и мне не надо ходить на собеседование и ожидать тупых вопросов типа "а знаете ли вы вот эти стопицот модных технологий?", а в реальной жизни дауншифтинг на rxdart уменьшил мой код вдвое(!), не затронув производительность.


Вот о чем статья. Для сравнения технологий, практик, паттернов. Так что пишите свою, (и наполучаете от хейтеров в карму :) ).


PS. Да, вдогон. Про DI, Provider я вообще не упоминал, а на КДПВ указан концепт провайдера для BLoC, по аналогии с тем, что в этой статье. Пожалуйста, читайте статьи, которые комментируете, а не додумывайте свое за автора. Спасибо.

1

Information

Rating
Does not participate
Location
Екатеринбург, Свердловская обл., Россия
Date of birth
Registered
Activity