- Почему так происходит
- Statex* — автоинжектирование без ограничений
- Statexview.find — теперь и с инверсией зависимостей
- Гаджет конструктор | funny gifts
- Гаджет конструктор 6 1 — купите гаджет конструктор 6 1 с бесплатной доставкой на алиэкспресс version
- Как с этим бороться
- Как собрать все это воедино
- Подготовка контроллера
- Пример 1. управлять контроллерами в страницах pageview
Почему так происходит
Если в двух словах. то виджеты и их контроллеры, участвующие в GetPageRoute, синхронизируют свои жизненные циклы, и все работает из коробки — при смене роута в навигаторе нужные контроллеры заново инициализируются, ненужные удаляются. Для операций вне роутинга это не предусмотрено. А наши кейсы — как раз вне роутинга.
Statex* — автоинжектирование без ограничений
На стороне виджетов введены 2 класса:
1. StatexWidget — базовый абстрактный класс
Его задача в том, чтобы позади основного дерева виджетов внедрить служебный микровиджет _StatexWidgetInjector (по сути, пустой контейнер). Вся работа производится в этом виджете.
abstract class StatexWidget<T extends StatexController>
extends StatelessWidget {
/// Базовый конструктор для передачи билдера и свойств.
/// Под капотом вызывает Get.put(...), передавая туда билдер, тег
/// и флаг permanent.
/// Удобен для передачи параметров прямо по месту вызова, но не поддерживает
/// принцип Dependency Inversion, так как точный тип контроллера нужно знать
/// в точке вызова.
/// Если требуется что-то типа Get.lazyPut<Base>(()=>Inherited()),
/// то следует воспользоваться конструктором [StatexWidget.find]
///
/// ```
/// class BusinessPage extends StatexWidget<_BusinessPageControllerImpl> {
/// BusinessPage() : super(() => _BusinessPageControllerImpl(),
/// tag: 'TAG', permanent: true, args: {'key': value} );
///
/// ```
const StatexWidget(
this.builder, {
this.tag,
this.permanent = false,
this.args = const <String, dynamic>{},
Key? key,
}) : super(key: key);
/// Конструктор для работы в паре с [Get.lazyPut<Some>(()=>SomeImpl())].
/// Другими словами, для поддержания концепции Dependency Inversion.
///
/// [markAsPermanent] используется в случаях,
//// когда конструктор `StatexWidget.find` будет вызываться
/// в паре с ранее зарегистрированной ленивой фабрикой Get.lazyPut.
/// Для Get.lazyPut нельзя сделать контроллер перманентным,
/// только возобновляемым при помощи свойства `fenix`.
/// Но fenix заново создает контроллер, убивая его состояние.
/// В случае создания контроллера [markAsPermanent] передаст
/// свое значение в инжектор Get.put(..., permanent = markAsPermanent),
/// тем самым создав перманентный контроллер.
/// И соответственно, при [dispose] не будет удаления Get.delete<T>
/// для этого типа.
///
/// ```
/// // Где-то в инжекторе определяем фабрику и параметры,
/// // подставляя имплементацию
/// Get.lazyPut<HomePageController>(
/// () => HomePageControllerImpl(),
/// fenix: true,
/// );
///
/// // Используем [StatexWidget.find], передавая дополнительные параметры.
/// // Если инстанс не существует, он будет создан с нужными параметрами.
/// // Иначе будет найден и выдан текущий инстанс.
/// class HomePage extends StatexWidget<HomePageController> {
/// HomePage({Key? key}) : super.find(
/// markAsPermanent: true,
/// key: key,
/// );
///
/// ```
const StatexWidget.find({
String? tag,
bool markAsPermanent = false,
Map<String, dynamic> args = const <String, dynamic>{},
Key? key,
}) : this(null, tag: tag, permanent: markAsPermanent, args: args, key: key);
/// [builder] обязателен для базового конструктора, но не используется
/// для [StatexWidget.find]
final InstanceBuilderCallback<T>? builder;
///
final String? tag;
final bool permanent;
final Map<String, dynamic> args;
T get controller => GetInstance().find<T>(tag: tag);
Widget buildWidget(BuildContext context);
/// Идея в том, чтобы внедрить виджет-менеджер времени
/// жизни контроллера в стек позади основного дерева клиента.
@override
Widget build(BuildContext context) {
// Необходимый стек для внедрения [_StatexWidget]
return Stack(
fit: StackFit.passthrough,
children: [
// Wrapping уменьшает геометрию виджета до минимально возможной
Wrap(
children: [
_StatexWidgetInjector<T>(
builder,
tag: tag,
permanent: permanent,
args: args,
),
],
),
buildWidget(context),
],
);
}
}
2. _StatexWidget State: Виджет управления состояниями контроллера.
/// Контрольный [StatefulWidget]
class _StatexWidgetInjector<T extends StatexController> extends StatefulWidget {
_StatexWidgetInjector(
InstanceBuilderCallback<T>? builder, {
this.tag,
this.permanent = false,
Map<String, dynamic> args = const <String, dynamic>{},
Key? key,
}) : super(key: key) {
// Инжектирование контроллера прямо в конструкторе виджета.
final inst = GetInstance();
if (builder != null && !inst.isRegistered<T>(tag: tag)) {
inst.put(builder(), tag: tag, permanent: permanent);
}
final c = inst.find<T>(tag: tag);
c.args = args;
}
final String? tag;
final bool permanent;
@override
_StatexWidgetInjectorState<T> createState() =>
_StatexWidgetInjectorState<T>();
}
/// Состояние для [_StatexWidgetInjector]
/// Управляет вызовами [onWidgetInitState], [onWidgetDisposed]
/// и в случае необходимости, удаляет инстанс контроллера
/// из памяти
class _StatexWidgetInjectorState<T extends StatexController>
extends State<_StatexWidgetInjector<T>> {
@override
initState() {
super.initState();
final wc = GetInstance().find<T>(tag: widget.tag);
wc.onWidgetInitState();
}
@override
void dispose() {
final inst = GetInstance();
if (inst.isRegistered<T>(tag: widget.tag)) {
final wc = inst.find<T>(tag: widget.tag);
wc.onWidgetDisposed();
if (!widget.permanent) {
Get.delete<T>(tag: widget.tag);
}
}
super.dispose();
}
@override
Widget build(BuildContext context) => Container();
}
Это StatefulWidget и поэтому он поддерживает полный жизненный цикл при перестройке дерева, в котором находится, включая initState и dispose. Этим и воспользуемся.
Вот как это работает:
Переданный в конструкторе билдер (если есть), создаст нам инстанс контроллера с требуемыми параметрами, или вернет существующий
В ином случае мы попытаемся найти инстанс через
GetInstance.find
Напоследок передадим в инстанс аргументы
Далее, в
_StatexWidgetState.initState
, вызываетсяonInitWidgetState
давая возможность произвести нужные действия в момент инициализации дереваА в
_StatexWidgetState.dispose
мы производим вызовonWidgetDisposed
, и при необходимости удаляем контроллер
Statexview.find — теперь и с инверсией зависимостей
Отдельного разговора заслуживает именованный конструктор StatexView.find.
Передача билдера конкретного типа в конструктор не вписывается в Dependency Inversion. Если необходимо управлять имплементациями, подойдет связка Get.lazyPut<Some>(()=>SomeImpl) StatexView.find().
Это работает так
// [1.]
// Где-то в инжекторе определяется имплементация интерфейса,
// например, вот так
Get.lazyPut<HomePageController>(
() => HomePageControllerImpl(),
fenix: true,
);
// или так
Get.lazyPut<HomeSubPageController>(
() => HomeSubPageControllerImpl(),
tag: HomeSubPageController.someTagForFindStrategy,
fenix: true,
);
// [2.]
// В конструкторе виджета идет обращение к StatexView.find
// вот так
HomePage({Key? key})
: super.find(
markAsPermanent: true,
key: key,
);
// или так
HomeSubPage({Key? key})
: super.find(
key: key,
tag: HomeSubPageController.someTagForFindStrategy,
);
Этого достаточно, чтобы произошел поиск инстанса, создание нужной имплементации в случае неоходимости, и все заверте всего остального цикла работы.
Гаджет конструктор | funny gifts
Сложно отнести данное изобретение к какому либо из привычных образов но в любом случае в качестве подарка это будет идеальным решением, как для ребенка так и для взрослого. Этот гаджет конструктор одновременно является и игрушкой, и роботом, и пазлом, и ребусом. При этом что именно из него собрать решать именно вам, но несколько фигур производитель таки заложил в интеллектуальные элементы.
Гаджет конструктор
Где купить: magic-present.su
Цена: 390 рублей
Элементы:
– солнечная батарея;
– мини-электродвигатель;
– 22 пластиковые детали, не требующие склейки и делающие сборку и разборку конструктора совершенно не скучной.
Шесть фигур
Конструктор дает возможность любому попробовать свои силы в смекалке и в легкой конструкторской сноровке и собрать шесть моделей. Чуть-чуть усилий и на столе поочередно появляется мельница, робот, мини-вентилятор, самолет и даже вертолет. Но самое удивительное заключается в том, что благодаря солнечной батарее и мини-мотору, каждая из фигур закрутиться, поедет, задвигается. И все это от любого источника питания будь то солнце, или лампочка.
Игрушка дает возможность окунуться в мир фантазии не только ребенку, но и взрослому человеку, скоротать время на работе или попросту удивить друзей и знакомых. Будьте оригинальными при выборе подарка и ответная радостная реакция виновника торжеств, а вернется сторицей. Ну а если вы вообще масштабно мыслите и уже увидели перспективу, то купить гаджет конструктор во множественном числе для большой компании и у вас появится шанс создать нечто большее, чем шесть фигур. Дерзайте и сотворите чудо!
Гаджет конструктор 6 1 — купите гаджет конструктор 6 1 с бесплатной доставкой на алиэкспресс version
Перед покупкой сравните цены на гаджет конструктор 6 1, прочитайте реальные отзывы покупателей, ознакомьтесь с техническими характеристиками.
Закажите гаджет конструктор 6 1 онлайн с доставкой по России: товары с пометкой Plus доступны с ускоренной доставкой и улучшенными условиями возврата.
На Алиэкспресс гаджет конструктор 6 1 всегда в наличии в большом ассортименте: на площадке представлены как надежные мировые бренды, так и перспективные молодые.
Как с этим бороться
Для решения этих задач я использую небольшую надстройку в виде двух классов на стороне виджета и одного на стороне контроллера.
Как собрать все это воедино
Унаследовать контроллер от
StatexController
. Если есть необходимость, переопределить нужные методыУнаследовать
Widget
отStatexWidget
В конструкторе вызвать конструктор суперкласса
либо основной — для простого инжектирования
либо
super.find
для связки с ленивой инициализацией. Имеет смысл только для инверсии зависимостей
Вместо
build
реализоватьbuildWidget
Профит
Подготовка контроллера
Чтобы все окончательно заработало, на стороне контролллера введен тип StatexController
abstract class StatexController extends GetxController {
final _args = <String, dynamic>{};
Map<String, dynamic> get args => _args;
set args(Map<String, dynamic> value) => _args.assignAll(value);
/// Вызывается в момент [_StatexWidgetState.initState].
/// Таким образом можно отлавливать момент перехода на страницы
/// в [PageView], например
void onWidgetInitState() {}
/// Вызывается в момент [_StatexWidgetState.dispose].
void onWidgetDisposed() {}
}
Конкретно инжектирования там касается только два метода-события, которые вызываются из StatexWidget в нужное время.
Пример 1. управлять контроллерами в страницах pageview
Архитектура PageView такова, что субстраницы строятся одномоментно в процессе build и при переходах не пересоздаются. Но предположим, что по бизнес-логике необходимо рассматривать эти субстраницы, как отдельные элементы, например инициализировать и отключать таймеры, обновлять поля, открывать и закрывать какие-то ресурсы при переходе между ними. Явно просится прикрутить по контроллеру к каждой из них.
body: PageView(
controller: controller.pageController,
children: [
HomePage(), // HomePageController
BusinessPage(), // BusinessPageController
],
),
Вообще, следует начать с того, что непонятно, куда прикручивать инжектирование. Биндить к странице-владельцу PageView? Но теряется контекстность применения, ведь контроллеры управляют данными конкретных субстраниц, а onInit/onReady/onClose контроллеров никак не будут соответствовать моментам переходов.
Из лога видно, что никакой привязки контроллеров не получается.
А вот как это должно быть:
Очевидно, что теперь жизненные циклы контроллеров страниц отслеживаются. Это позволяет управлять ресурсами в контексте бизнес-логики.