Флаттер GetX выглядит

Android iOS Flutter

предисловие

get | Flutter Package (flutter-io.cn)всегдаFlutterВ нем есть противоречивая трехсторонняя библиотека. Именно из-за полемики, поэтому мы должны иметь собственное суждение, не стоя в очереди.

с одной стороны

Это самая популярная библиотека в pub.dev.

截屏2021-12-10 下午5.05.40.png

Более 5500 звезд Github

截屏2021-12-10 下午5.06.22.png

Имеет более 140 участников

截屏2021-12-10 下午5.06.50.png

1400+ выпусков

截屏2021-12-10 下午5.12.39.png

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

с другой стороны

Это также является объектом жалоб разработчиков.

截屏2021-12-10 下午5.14.23.png

A36E6E28-D475-4387-90C0-B28FBF384365.png

getx.jpg

B7326A01E7479C435A7AB79F703227CF.jpg

Видно что слоты еще заполнены.Нажимать на стол пока не будем.Давайте сначала разберемся что естьGetX.

Основная тема

GetX — легкое и мощное решение на Flutter: высокопроизводительное управление состоянием, интеллектуальное внедрение зависимостей и удобное управление маршрутизацией — из официального описания.

То же самое относится и к трем основным функциям, представленным в официальной документации.

880D7581-9132-472C-8C53-FD9EF929DD27.png

давайте загрузимGetXпроект, откройте его, чтобы увидеть, как выглядит структура.

  • Из папки выше можно примерно увидеть функции, за которые отвечает каждая часть.

get_connect: Связанный с сетью

get_instance: связанные с инъекцией

get_navigation: связанные с маршрутизацией

get_rx: Магия (собачья голова)

get_state_manager: связанный со статусом

截屏2021-12-11 上午9.57.59.png

  • Надо сказать, что здорово поддерживать документы из разных стран. Конечно, это для тех, кто будет читать документацию.

截屏2021-12-11 上午9.40.23.png

Далее я проанализирую его с точки зрения исходного кода.GetXтри функции.

управление зависимостями

Пучок依赖管理Упоминается в начале, потому что другие 2 функции более или менее основаны на нем.

определить класс

В большинстве случаев этот класс необходимо наследоватьGetxController, чтобы вся система делала это автоматическиdisposeоперации (эта часть будет обсуждаться в управлении маршрутизацией).

class FFController extends GetxController {}

регистр

// 普通方式
Get.put<FFController>(FFController());
// 如果你想这个实例永远存在,不被删除,可以把 permanent 设置为 true
Get.put<FFController>(FFController(), permanent: true);
// 如果你的场景中,会存在多个相同的 FFController 实例,你可以用 tag 来进行区分
Get.put<FFController>(FFController(), tag: 'unique key');
// 使用的时候才创建类
Get.lazyPut<FFController>(() => FFController());
// 注册一个异步实例
Get.putAsync<FFController>(() async => FFController());

Получать

// 普通方式
FFController controller = Get.find<FFController>();
// 如果你的场景中,会存在多个相同的 FFController 实例,你可以用 tag 来进行区分
FFController controller = Get.find<FFController>(tag: 'unique key');

принцип

По сути, вы вводите кодGet.putилиGet.find, что в конечном итоге указывает наGetInstance.

GetInstanceНа самом деле единичный случай(DartОдин поток действительно ароматный? ), который использует_singlКарта хранит ваши зарегистрированные объекты/фабричные методы, конкретный процесс не показан.

class GetInstance {
  factory GetInstance() => _getInstance ??= GetInstance._();

  const GetInstance._();

  static GetInstance? _getInstance;

  T call<T>() => find<T>();

  /// Holds references to every registered Instance when using
  /// `Get.put()`
  static final Map<String, _InstanceBuilderFactory> _singl = {};

  /// Holds a reference to every registered callback when using
  /// `Get.lazyPut()`
  // static final Map<String, _Lazy> _factory = {};
}

государственное управление

E0655AB2-864E-415F-8C6D-24B3ECF241C7.png

Прежде чем говорить об этой части, позвольте мне повторить, что независимо от того, как работает фреймворк, в конечном итоге он вернется кsetState(() {});.

Obx

ЭтоGetXОдна из самых больших магий, давайте посмотрим, как она работает.

obs

Мы первыеFFControllerдобавить один<int>[]переменная массива,obsэто метод расширения, который вернетRxList<int>, а что такоеRxList, мы пока не будем вдаваться в подробности, давайте сначала посмотрим, как он используется.

class FFController extends GetxController {
  RxList<int> list = <int>[].obs;
}
Obx

использоватьObxВключите раздел, который должен обновить статус, нажмитеIcons.addкнопку, и у вас будет изменен весь список.

class RxListDemo extends StatelessWidget {
  const RxListDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    FFController controller = Get.put<FFController>(FFController());
    return Scaffold(
      appBar: AppBar(),
      body: Obx(
        () {
          return ListView.builder(
            itemBuilder: (BuildContext b, int index) {
              return Text('$index:${controller.list[index]}');
            },
            itemCount: controller.list.length,
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),      
        onPressed: () {
          controller.list.add(Random().nextInt(100));
        },
      ),
    );
  }
}
принцип

Во-первых, посмотрите наRxListЧто это такое. Здесь размещена только часть кода, вы можете увидетьRxListдляListИтак, методы и операции были сделаныoverride, и позвонитьrefreshметод.

  @override
  void operator []=(int index, E val) {
    _value[index] = val;
    refresh();
  }

  /// Special override to push() element(s) in a reactive way
  /// inside the List,
  @override
  RxList<E> operator +(Iterable<E> val) {
    addAll(val);
    refresh();
    return this;
  }

  @override
  E operator [](int index) {
    return value[index];
  }

  @override
  void add(E item) {
    _value.add(item);
    refresh();
  }

а такжеrefreshвыполнитьStream.addметод. ТакStreamКто его потребляет?

  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};
  void refresh() {
    subject.add(value);
  }

Давайте посмотримObxЧто спрятано внутри.

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}

а такжеObxунаследовано отObxWidget.ObxWidgetЯвляетсяStatefulWidget,существует_ObxStateпри инициализации_observerСделал прослушиватель, он сработает, когда будет уведомлен_updateTree, что является нашим общимsetState(() {});.

abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key? key}) : super(key: key);

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties..add(ObjectFlagProperty<Function>.has('builder', build));
  }

  @override
  _ObxState createState() => _ObxState();

  @protected
  Widget build();
}

class _ObxState extends State<ObxWidget> {
  final _observer = RxNotifier();
  late StreamSubscription subs;

  @override
  void initState() {
    super.initState();
    subs = _observer.listen(_updateTree, cancelOnError: false);
  }

  void _updateTree(_) {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    subs.cancel();
    _observer.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) =>
      RxInterface.notifyChildren(_observer, widget.build);
}

пока вRxInterface.notifyChildrenлейтенант метода_observerпройти в. На самом деле, мы можем видеть, что этот метод делает только одну вещь, вbuilderПеред выполнением обратного вызова установитеRxInterface.proxyдля текущего_ObxStateсередина_observer.

  /// Avoids an unsafe usage of the `proxy`
  static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
    final _observer = RxInterface.proxy;
    RxInterface.proxy = observer;
    final result = builder();
    if (!observer.canUpdate) {
      RxInterface.proxy = _observer;
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = _observer;
    return result;
  }

пока вbuilderв методеcontroller.list[index]а такжеcontroller.list.lengthкогда звонили.

return ListView.builder(
   itemBuilder: (BuildContext b, int index) {
      return Text('$index:${controller.list[index]}');
   },
   itemCount: controller.list.length,
);

будет выполнятьRxInterface.proxy?.addListener(subject);, это будет чудоRxListа такжеObxсвязаны.

  @override
  E operator [](int index) {
    return value[index];
  }

  @override
  int get length => value.length;

  @override
  @protected
  List<E> get value {
    RxInterface.proxy?.addListener(subject);
    return _value;
  }

Далее посмотримdebugИнформация о стеке может быть очень ясной, как работает весь процесс.

  • Создать прослушиватель

截屏2021-12-11 下午1.31.15.png

  • БудуRxInterface.proxyустановить как текущий_observer

截屏2021-12-11 下午1.31.50.png

  • обратный вызов построителя, вот-вот сработаетRxListмагия артефакта

截屏2021-12-11 下午1.34.34.png

  • перейти на подпискуRxListсерединаStream

截屏2021-12-11 下午1.36.12.png

  • Формальный мониторинг

截屏2021-12-11 下午1.37.42.png

  • когда мыRxListвносить изменения, напримерadd, включить монитор

截屏2021-12-11 下午1.39.56.png

  • последний триггер_ObxStateсередина_updateTree

截屏2021-12-11 下午1.41.05.png

  • Obx disposeпри закрытии потока.
  @override
  void dispose() {
    subs.cancel();
    _observer.close();
    super.dispose();
  }
резюме
  • .obsсерия, в том числе базоваяint,double,Listи другие инфраструктурные пакеты, а также включает в себяStreamдать уведомление.

  • Obxчерез паруRxInterface.proxyнастройки (блинDartОднониточка, такая ароматная! ), обязательноbuilderв обратном вызове.obsТолько текущийRxInterface.proxyзнак равноObx, чтобы гарантировать, что текущий.obsвызовет только соответствующийObxобновить.

  • вам не нужно создаватьSreamController; вам не нужно создавать по одному для каждой переменнойStreamBuilder; вам не нужно создавать для каждой переменнойValueNotifier... одно сказать, это действительно ароматно.

image.png

GetxController

часто сGetBuilderИспользуется вместе сChangeNotifierсходство.

class FFController extends GetxController {
  List<int> list = <int>[];
  void add(int i) {
    list.add(i);
    update();
  }
}

class RxListDemo extends StatelessWidget {
  RxListDemo({Key? key}) : super(key: key);
  FFController controller = Get.put<FFController>(FFController());
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: GetBuilder<FFController>(
        builder: (FFController controller) {
          return ListView.builder(
            itemBuilder: (BuildContext b, int index) {
              return Text('$index:${controller.list[index]}');
            },
            itemCount: controller.list.length,
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          controller.add(Random().nextInt(100));
        },
      ),
    );
  }
}

Основного кода ядра не много, принцип прост, пользуйтесьGetInstanceБудуFFControllerделать мониторинг и т.д.FFController updateобновить, когдаGetBuilder. существуетdisposeСледуйте условному выпуску, когдаFFController.

class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  T? controller;
  bool? _isCreator = false;
  VoidCallback? _remove;
  Object? _filter;

  @override
  void initState() {
    // _GetBuilderState._currentState = this;
    super.initState();
    widget.initState?.call(this);

    var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);

    if (widget.global) {
      if (isRegistered) {
        if (GetInstance().isPrepared<T>(tag: widget.tag)) {
          _isCreator = true;
        } else {
          _isCreator = false;
        }
        controller = GetInstance().find<T>(tag: widget.tag);
      } else {
        controller = widget.init;
        _isCreator = true;
        GetInstance().put<T>(controller!, tag: widget.tag);
      }
    } else {
      controller = widget.init;
      _isCreator = true;
      controller?.onStart();
    }

    if (widget.filter != null) {
      _filter = widget.filter!(controller!);
    }

    _subscribeToController();
  }

  /// Register to listen Controller's events.
  /// It gets a reference to the remove() callback, to delete the
  /// setState "link" from the Controller.
  void _subscribeToController() {
    _remove?.call();
    _remove = (widget.id == null)
        ? controller?.addListener(
            _filter != null ? _filterUpdate : getUpdate,
          )
        : controller?.addListenerId(
            widget.id,
            _filter != null ? _filterUpdate : getUpdate,
          );
  }

  void _filterUpdate() {
    var newFilter = widget.filter!(controller!);
    if (newFilter != _filter) {
      _filter = newFilter;
      getUpdate();
    }
  }

  @override
  void dispose() {
    super.dispose();
    widget.dispose?.call(this);
    if (_isCreator! || widget.assignId) {
      if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
        GetInstance().delete<T>(tag: widget.tag);
      }
    }

    _remove?.call();

    controller = null;
    _isCreator = null;
    _remove = null;
    _filter = null;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    widget.didChangeDependencies?.call(this);
  }

  @override
  void didUpdateWidget(GetBuilder oldWidget) {
    super.didUpdateWidget(oldWidget as GetBuilder<T>);
    // to avoid conflicts when modifying a "grouped" id list.
    if (oldWidget.id != widget.id) {
      _subscribeToController();
    }
    widget.didUpdateWidget?.call(oldWidget, this);
  }

  @override
  Widget build(BuildContext context) {
    // return _InheritedGetxController<T>(
    //   model: controller,
    //   child: widget.builder(controller),
    // );
    return widget.builder(controller!);
  }
}

а такжеGetxControllerи некоторые сохранены вGetInstanceАвтоматическое освобождение объектов в , снова с намиGexXтесно связаны с управлением маршрутизацией.

управление маршрутом

Flutterсерединаcontextочень важная вещь, многоapiнеотделимы от него. У вас, должно быть, была эта мысль, надеюсь, что безcontextВ случае использования маршрутизацииSnackBars , Dialogs , BottomSheets .

Вообще-то, нетcontextМетод маршрутизации на самом деле очень прост.

class App extends StatefulWidget {
  const App({Key? key}) : super(key: key);
  static final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey(debugLabel: 'navigate');
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: App.navigatorKey,
      home: RxListDemo(),
    );
  }
}

Вам нужно только использовать

App.navigatorKey.currentState.pushNamed('/home');

И все этоGexXЭто все упаковано для вас, вам просто нужно положитьMaterialAppзаменитьGetMaterialApp.

GetMaterialApp( // Before: MaterialApp(
  home: MyHome(),
)

Это нужно только при использовании

Get.to(NextScreen());
Get.back();
Get.back(result: 'success');
Get.toNamed("/NextScreen");
Get.toNamed("/NextScreen", arguments: 'Get is the best');
// 获取参数
print(Get.arguments);

Конечно,GexXМаршрутизация — это гораздо больше, чем то, что вы видите, ее основная задача — соединить весьGexXвселенная.

GetPage

GetPageунаследовано отPage<T>,а такжеPage<T>унаследовано отRouteSettings, Это описание страницы. пройти черезGetPageсобраны вGetPageRoute.

    GetMaterialApp(
      initialRoute: '/',
      getPages: [
      GetPage(
        name: '/',
        page: () => MyHomePage(),
      ),
      GetPage(
        name: '/profile/',
        page: () => MyProfile(),
      ),
     ],
    )

GetPageRoute

MaterialPageRouteа такжеCupertinoPageRouteКаждый должен быть знаком сGetPageRouteи они одно.

class GetPageRoute<T> extends PageRoute<T>
    with GetPageRouteTransitionMixin<T>, PageRouteReportMixin {
}

Отличие в том, что у него другие задачи, он будетinstall(Вы можете просто интерпретировать это какpush) а такжеdispose(Вы можете просто интерпретировать это какpop) ставить в известностьRouterReportManager.

mixin PageRouteReportMixin<T> on Route<T> {
  @override
  void install() {
    super.install();
    RouterReportManager.reportCurrentRoute(this);
  }

  @override
  void dispose() {
    super.dispose();
    RouterReportManager.reportRouteDispose(this);
  }
}

а такжеRouterReportManagerОдной из задач является управление различными экземплярами, которые мы зарегистрировали на текущей странице.Ниже приведен важный код.

class RouterReportManager<T> {
  static final Map<Route?, List<String>> _routesKey = {};

  static final Map<Route?, HashSet<Function>> _routesByCreate = {};

  static Route? _current;

  // ignore: use_setters_to_change_properties
  static void reportCurrentRoute(Route newRoute) {
    _current = newRoute;
  }

  /// Links a Class instance [S] (or [tag]) to the current route.
  /// Requires usage of `GetMaterialApp`.
  static void reportDependencyLinkedToRoute(String depedencyKey) {
    if (_current == null) return;
    if (_routesKey.containsKey(_current)) {
      _routesKey[_current!]!.add(depedencyKey);
    } else {
      _routesKey[_current] = <String>[depedencyKey];
    }
  }

  static void reportRouteDispose(Route disposed) {
    if (Get.smartManagement != SmartManagement.onlyBuilder) {
      WidgetsBinding.instance!.addPostFrameCallback((_) {
        _removeDependencyByRoute(disposed);
      });
    }
  }
  • pushтриггер новой страницыreportCurrentRoute, установите текущий_current.
  • при вызове на текущей страницеGet.putбудет вызван, когдаreportDependencyLinkedToRouteметод, сохраните его.
  • popЗапускается, когда страницаreportRouteDisposeПо некоторым правилам экземпляр освобождается.

FFRoute

В реальной эксплуатации к следующим 2 пунктам я никак не могу привыкнуть.

  • установить вручнуюgetPagesсобирать
  • Потому что только черезGet.argumentsПолучение параметров, слабая типизация неудобна.

Для этой цели я добавилFFRouteа такжеGetX结合的例子。 (FFRouteэто инструмент, который использует аннотации для создания маршрутов)

ff_annotation_route/example_getx at master · fluttercandies/ff_annotation_route (github.com)

  • На самом деле, вам просто нужноonGenerateRouteОбратный звонок Генерал-лейтенантFFRouteSettingsпреобразовать в соответствующийGetPageRoute.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage.name,
      onGenerateRoute: (RouteSettings settings) {
        FFRouteSettings ffRouteSettings = getRouteSettings(
          name: settings.name!,
          arguments: settings.arguments as Map<String, dynamic>?,
          notFoundPageBuilder: () => Scaffold(
            appBar: AppBar(),
            body: const Center(
              child: Text('not find page'),
            ),
          ),
        );
        Bindings? binding;
        if (ffRouteSettings.codes != null) {
          binding = ffRouteSettings.codes!['binding'] as Bindings?;
        }

        Transition? transition;
        bool opaque = true;
        if (ffRouteSettings.pageRouteType != null) {
          switch (ffRouteSettings.pageRouteType) {
            case PageRouteType.cupertino:
              transition = Transition.cupertino;
              break;
            case PageRouteType.material:
              transition = Transition.downToUp;
              break;
            case PageRouteType.transparent:
              opaque = false;
              break;
            default:
          }
        }

        return GetPageRoute(
          binding: binding,
          opaque: opaque,
          settings: ffRouteSettings,
          transition: transition,
          page: () => ffRouteSettings.builder(),
        );
      },
    );
  }
}
  • При использовании записи

Get.toNamed(Routes.itemPage.name,arguments: Routes.itemPage.d(index: index));

Суммировать

Это не введение в использованиеGetXСтатья представляет собой простое понимание с точки зрения исходного кодаGetXПринцип трех функций — не что иное, как это.

преимущество

  • Простой в использовании

    если ты правFlutterПринцип понятен,GetXАбсолютный убийца, он может значительно сократить время, которое вы тратите на написание кода.

  • Многофункциональный

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

недостаток

  • Простой в использовании

    В этом его преимущество и его недостаток. он скрываетFlutterСамый основной принцип. Это может быть удовольствие для новичка для использования, но трудно устранить неполадки, если вы столкнулись с проблемами. Очевидное явление заключается в том, что многие новички приходят к группе, чтобы спросить,GetXКак это не работает, это действительно расстраивает после долгого времени.

  • Многофункциональный

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

截屏2021-12-11 下午4.05.28.png

  • слишком преувеличенное описание

    Некоторые описания слишком преувеличены, что также приводит кGetXодеялоFlutter TeamотменитьFlutter Favoriteодна из причин.

Эпилог

GetXЭто трехсторонняя библиотека феноменального уровня, способ ее использования полностью зависит от вашей собственной ситуации. Новичкам рекомендуется использовать трехсторонние рамки, не придумывая, они будут мешать вашему пониманию.Flutterпонимание принципов. На самом деле зачастую в технологиях нет ничего плохого, просто люди, которые их используют, разные.

наконец надетьGetXОфициальная китайская документация:

  1. README

  2. управление зависимостями

  3. государственное управление

  4. управление маршрутом

ЛюбовьFlutter,Любовь糖果,Добро пожаловатьFlutter Candies, чтобы вместе производить милые конфеты FlutterГруппа QQ: 181398081

надевать в последнюю очередьFlutter CandiesВся семейная бочка, действительно ароматная.