preface

Get | Flutter Package (Flutter – IO. Cn) has been controversial in the Flutter of a third-party library. Just because it is controversial, we should have our own judgment, no need to take sides.

On the one hand,

It was the first library to be liked in pub.dev

The number of Github stars exceeds 5500

Has 140+ contributors

The Issue of 1400 +

All of which suggests that this is a very popular library of tripartite components.

On the other hand

It is also the object of developer ridicule.

As you can see, the slots are still full, so let’s click on the blank list to see what GetX is.

To the chase

GetX is a lightweight and powerful solution for Flutter: high-performance state management, intelligent dependency injection, and convenient route management — according to the official description.

The same is true of the three features described in the official documentation.

Let’s download the GetX project and open it up to see what the structure looks like.

  • From the folder above you can roughly see each section responsible for the function.

Get_connect: network related

Get_instance: related to injection

Get_navigation: indicates route correlation

Get_rx: Magic related (Dog head)

Get_state_manager: state related

  • I have to say, supporting documentation for multiple countries is pretty cool. Of course, this is for people who can read documents.

I’ll take a look at the three main features of GetX from a source code perspective.

Dependency management

I’ll mention dependency management up front because the other two functions are more or less based on it.

Define the class

In most cases, this class needs to inherit GetxController so that the entire system can dispose of it automatically (explained in route management).

class FFController extends GetxController {}
Copy the code

registered

// Common mode
Get.put<FFController>(FFController());
// If you want the instance to last forever, set the permanent to true
Get.put<FFController>(FFController(), permanent: true);
// If there are multiple instances of the same FFController in your scenario, you can use tags to distinguish them
Get.put<FFController>(FFController(), tag: 'unique key');
// Create class only when used
Get.lazyPut<FFController>(() => FFController());
// Register an asynchronous instance
Get.putAsync<FFController>(() async => FFController());
Copy the code

To obtain

// Common mode
FFController controller = Get.find<FFController>();
// If there are multiple instances of the same FFController in your scenario, you can use tags to distinguish them
FFController controller = Get.find<FFController>(tag: 'unique key');
Copy the code

The principle of

In fact, you follow the code to Get. Put or Get. Find, and both end up pointing to GetInstance.

GetInstance is a singleton. It uses a _singL Map to store your registered object/factory methods.

class GetInstance {
  factoryGetInstance() => _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 = {};
}
Copy the code

State management

Before we get to this part, again, no matter how much the framework manipulates, it will eventually revert to setState(() {}); .

Obx

This is GetX’s biggest magic, so let’s see how it works.

obs

We will add an

[] array variable to FFController. Obs is an extension method that will return RxList

. We will not go into RxList here.

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

Using Obx to contain the part of the list that needs to be updated, click on Icons. Add and you will have the whole list changed.

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)); },),); }}Copy the code
The principle of

First, let’s see what an RxList is. Putting just a bit of code here, you can see that RxList override all the methods and operations on the List and call 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+ (可迭代<E> val) {
    addAll(val);
    refresh();
    return this;
  }

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

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

In refresh, the stream.add method is executed. So who’s consuming Stream?

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

Let’s see what’s inside Obx.

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}
Copy the code

Obx inherits from ObxWidget. The ObxWidget is a StatefulWidget that listens when _ObxState is initialized and when it is notified triggers _updateTree, also known as 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);
}
Copy the code

In RxInterface. NotifyChildren method will _observer passed into it. In fact, we can see that the only thing this method does is set rxinterface.proxy to _observer in the current _ObxState before the Builder callback is executed.

  /// 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;
  }
Copy the code

In the Builder method, when controller.list[index] and controller.list. Length are called.

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

Performs RxInterface. Proxy? .addListener(subject); “, you can associate the magic RxList with Obx.

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

  @override
  int get length => value.length;

  @override
  @protected
  List<E> getvalue { RxInterface.proxy? .addListener(subject);return _value;
  }
Copy the code

Looking at the debug stack will give you a good idea of how the process works.

  • Create a listener

  • willRxInterface.proxySet to current_observer

  • Builder callback, about to fireRxListArtifact magic

  • To subscribe toRxListIn theStream

  • Formal monitoring

  • When we areRxListMake changes, likeadd“, triggers the listening

  • Eventually trigger_ObxStateIn the_updateTree

  • Obx disposeWhen closing the stream.
  @override
  void dispose() {
    subs.cancel();
    _observer.close();
    super.dispose();
  }
Copy the code
summary
  • The.obs family contains packages for basic ints, doubles, lists, etc., and includes a Stream for notifications.

  • Obx sets rxinterface.proxy (damn the Dart single thread, that smells good!) To ensure that the.obs in the Builder callback is associated only with the current rxinterface.proxy = Obx to ensure that the current.obs only triggers a refresh of the corresponding Obx.

  • You don’t need to create SreamController; You don’t need to create a StreamBuilder for each variable; You need to create ValueNotifier for each variable… It smells good.

GetxController

Often used with GetBuilder, similar to ChangeNotifier.

class FFController extends GetxController {
  List<int> list = <int> [];void add(inti) { 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)); },),); }}Copy the code

The main core code is not much, the principle is simple, use GetInstance to listen to FFController, and refresh GetBuilder when FFController update. Follow up conditional release FFController when Dispose.

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() {
    varnewFilter = 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!);
  }
}
Copy the code

The automatic release of GetxController and some objects stored in GetInstance is closely related to GexX route management.

Routing management

The context of a Flutter is very important and many apis are dependent on it. You’ve probably had the idea that you want to use routing without context, SnackBars, Dialogs, BottomSheets.

In fact, the contextless routing method is actually quite simple.

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) {
    returnMaterialApp( navigatorKey: App.navigatorKey, home: RxListDemo(), ); }}Copy the code

You just need to use it

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

And GexX encapsulates all of that for you, you just replace the MaterialApp with GetMaterialApp.

GetMaterialApp( // Before: MaterialApp(
  home: MyHome(),
)
Copy the code

That’s all you need to use it

Get.to(NextScreen());
Get.back();
Get.back(result: 'success');
Get.toNamed("/NextScreen");
Get.toNamed("/NextScreen", arguments: 'Get is the best');
// Get parameters
print(Get.arguments);
Copy the code

GexX routing, of course, is much more than what you see, and its mission is to connect the entire GexX universe.

GetPage

GetPage inherited from Page

, and Page

inherited from RouteSettings. It is a description of a page. GetPageRoute is assembled from GetPage.

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

GetPageRoute

MaterialPageRoute and CupertinoPageRoute are all familiar with GetPageRoute and they are the same thing.

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

The difference is that it has other tasks as well, notifying the RouterReportManager of install(which you can easily interpret as push) and Dispose (which you can easily interpret as POP).

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

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

One of the tasks of the RouterReportManager is to manage the various instances that we register on the current page. Here’s some of the important code.

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); }); }}Copy the code
  • pushNew page triggerreportCurrentRoute, set the current_current
  • When called on the current pageGet.put“Will be calledreportDependencyLinkedToRouteMethod, save it.
  • popWhen the page is triggeredreportRouteDisposeAccording to some rules, the instance is freed.

FFRoute

In practice, there are two things I can’t get used to.

  • Manual settinggetPagesA collection of
  • Because only throughGet.argumentsGet parameters. Weak types are uncomfortable.

I added the FFRoute and GetX example for this purpose. (FFRoute is a tool that generates routes using annotations.)

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

  • In fact, you just need to beonGenerateRouteThe callback will beFFRouteSettingsConvert to the correspondingGetPageRoute.
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.argumentsas 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'] asBindings? ; } 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:}}returnGetPageRoute( binding: binding, opaque: opaque, settings: ffRouteSettings, transition: transition, page: () => ffRouteSettings.builder(), ); }); }}Copy the code
  • Write it this way when you use it

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

conclusion

This is not an article on how to use GetX, but a simple understanding of how the three functions of GetX work from a source code point of view.

advantages

  • Using a simple

    If you understand how Flutter works, GetX is a killer that can greatly reduce the time you spend writing code.

  • feature-rich

    In addition to state management, dependency management, routing management three functions, it also contains internationalization, topics, network requests, and so on, a kind of family bucket feeling.

disadvantages

  • Using a simple

    This is both its strength and its weakness. It hides the fundamental principle of Flutter. It can be fun for beginners, but difficult to troubleshoot if there’s a problem. The obvious thing is that a lot of newbies come to the group and ask why GetX isn’t working, which is frustrating for a long time.

  • feature-rich

    There is so much encapsulation that one has to think about what would happen if the library stopped updating. Despite official promises, I think the word always should be used with caution.

  • An exaggerated description

    Some of the descriptions are overblown and this is one of the reasons why GetX was removed from the Flutter Favorite by the Flutter Team.

conclusion

GetX is a phenomenal three-way library, and how you use it is entirely up to you. Tripartite frameworks are not recommended for beginners. They will hinder your understanding of Flutter principles. In fact, there is often nothing wrong with technology, just different people using it.

Finally, put GetX official Chinese document:

  1. README

  2. Dependency management

  3. State management

  4. Routing management

loveFlutterLove,candyWelcome to join usFlutter CandiesTogether to make cute little Flutter candiesQQ group: 181398081

And finally, put Flutter Candies on it. It smells sweet.