When you open the door to programmers, there is an endless learning journey ahead. This is the Flutter development train. Please fasten your seat belts when you get on. Original address: juejin.cn/user/284079…

preface

One of the things that really bothered me so far with Bloc was that there was no real cross-page interaction! After repeated visits to official documents, a “pseudo-” cross-page interaction is achieved using a global Bloc approach, which can be seen in detail: Flutter_bloc parsing; Fish_redux’s broadcast mechanism is perfect for cross-page interaction, and I wrote a 10,000-word article on how to use the framework: Fish_redux: For small and medium projects, fish_redux can reduce development efficiency to some extent. Recently, I tried GetX related functions, which solved a lot of my pain points.

After writing the whole article, I immediately replaced all Bloc codes in one of my demos with GetX and removed the Fluro frame. It feels like using Getx saves a lot of template code, but it’s still a bit repetitive: create folders, create a few required files, and write initialization code and classes that have to be written. It was a little tedious, so I spent some time writing a plugin for GetX in order to make it easier for me to develop! The above repeated code, files, folders can all be generated in one click!

GetX related advantages

The build refresh method is extremely simple!

  • Getx: Obx(() => Text())

  • This is an aspect I care about very much, because the build refresh component method of Bloc needs to pass two generic types, plus two parameters in the build method, resulting in a build method that takes up almost four or five lines if it does not use the arrow method shorthand, which is really a cone to use. As a result, I usually write the BlocBuilder method directly to the top of the page (instead of writing the top layer). I only need to write the BlocBuilder once on a page, instead of writing BlocBuilder everywhere

Cross-page interaction

This is definitely an advantage of GetX! Cross-page interaction scenarios are all too common in complex production environments, and GetX’s cross-page interaction is almost as simple as Fish_redux’s

Routing management

Yes, getX implements route management internally, and it’s easy to use! Bloc did not implement route management, so I had to find a routing management framework with high star volume, so I chose Fluro. However, I have to say that fluro is really a torture to use. Every time I create a new page, what I resist most is to write the Fluro routing code, which goes back and forth across several files.

GetX implements dynamic route parameter transfer, that is, write parameters to the named route and get the parameters spelled on the route. That is, write H5 with flutter and transfer values directly to the Url. It’s time to ditch the complex Fluro without thinking.

Global BuildContext is implemented

Internationalization, subject realization

The advantages of build abbreviations alone make me wonder if I should use them, as well as cross-page functionality. Next will be a comprehensive introduction to the use of GetX, the article is not divided into the water reading amount, and strive to write a clear, convenient for everyone to refer to at any time.

To prepare

The introduction of

Start by importing the GetX plug-in

# getx state management framework https://pub.flutter-io.cn/packages/get get: ^3.24.0Copy the code

GetX address Github:

Github.com/jonataslaw/…

Pub:

Pub. Dev/packages/ge…

Main entrance configuration

Just change the MaterialApp to GetMaterialApp.

void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( home: CounterGetPage(), ); }}Copy the code

Each module guide package, all use the bread.

import 'package:get/get.dart';
Copy the code

The plug-in

Make fun of the process of writing plug-ins, actually write this template code to generate plug-ins, in fact, it is not difficult, there are many people on the Internet to write examples, reference ideas, can quickly come out, is some configuration more egg pain.

We recommend using Gradle mode to develop plugins, and bala bala bala lists a bunch of benefits. After much consideration, I decided to rewrite it in Gradle mode.

If you want to create a new Gradle project, you can download Gradle from your own site. If you want to create a new Gradle project, you can download Gradle from your own site. If you want to create a new Gradle project, you can download Gradle from your own site. There is a big BUG! Java folder will not be automatically generated in main folder!

Click on other folders, right click: New -> Plugin DevKit will not have Action options, almost persuaded me to quit, changed 7 or 8 versions of IDEA tried not to work! The Action option does not appear. After two days, I accidentally created a Java file under the main folder. Then I right-click the Java file: New -> Plugin DevKit, and the Action option appears. There is a huge dent problem, developing a plug-in in Gradle mode, put the template code files in the main file under, under the SRC, under the root directory, take less than the inside of the file content, this is really changed me a lot of time, searched many blogs, have found that the problem didn’t write, the official document example seen it a few times also didn’t find what, Later, I found a project from three years ago and looked through the code to find that all resource files must be placed in the Resources folder to read the file contents.

instructions

Plug-in effect

  • Take a look at an image of the plugin using the fish_redux plugin style

  • There are some optional functions, so make it multi-button style, and you can do it according to your own needs

Explain the function of the plug-in

  • Model: Generate GetX’s pattern,

  • Default: generates three files: state, logic, and View

  • Easy: simple mode, generate two files: Logic, view

  • Function: Function selection

  • UseFolder: Use a file. After selecting a folder, a folder is generated. The name of a large hump is automatically changed to lowercase and underscore

  • UsePrefix: a prefix is added to the generated file. The prefix is “big hump”. The name is automatically changed to “lowercase” and “underscore”

  • Module Name: Name of the Module, please use the big hump Name

The installation

  • In your Settings select Plugins — type “getx” — select “GeX” — then install — then click “Apply”
  • If there is any problem when using this plug-in, please send me an issue on github of the project, and I will deal with it as soon as possible after I see it

counter

rendering

implementation

The first page, of course, is to implement a simple counter and see how GetX decouples the logical layer from the interface layer. To generate a simple file using the plugin:

  • Mode selection: Easy

  • Function Selection: useFolder

Take a look at the generated default code, which is very simple and explained in detail in two state managers.

logic

import 'package:get/get.dart';

class CounterGetLogic extends GetxController {

}

Copy the code

view

import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'logic.dart'; class CounterGetPage extends StatelessWidget { final CounterGetLogic logic = Get.put(CounterGetLogic()); @override Widget build(BuildContext context) { return Container(); }}Copy the code

Responsive state management

When the data source changes, methods to refresh the component are performed automatically. Because the logic layer deals with page logic and the word “Controller” is too long to be confused with some control controllers that come with Flutter, this layer ends with “Logic”. Of course, this layer can be defined as “Event” or “Controller” depending on personal intention. Obs indicates that the variable is defined as a responsive variable. When the value of the variable changes, the page refresh method will be automatically refreshed. Base types, List, classes, you can add.obs to make them responsive

class CounterGetLogic extends GetxController { var count = 0.obs; Void increase() => ++count; }Copy the code

The view layer

WTF, why is the operation of the Logic layer in the build method? Tease me? ——— No, the STL is stateless, which means it will not be reorganized twice, so the instance operation will only be executed once, and the Obx() method can refresh the component, which is the perfect solution to refresh the component.

class CounterGetPage extends StatelessWidget { @override Widget build(BuildContext context) { CounterGetLogic logic = Get.put(CounterGetLogic()); Return Scaffold(appBar: appBar (title: const Text('GetX counter ')), body: Center(Child: Obx(() => Text(' click ${logic.count.value} times ', style: TextStyle(fontSize: 30.0),), floatingActionButton: FloatingActionButton( onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); }}Copy the code

Of course, you could write it this way.

class CounterGetPage extends StatelessWidget { final CounterGetLogic logic = Get.put(CounterGetLogic()); @override Widget build(BuildContext context) {return Scaffold(appBar: appBar (title: const Text('GetX counter '))), body: Center(child: Obx(() => Text(' click ${logic.count. Value} times ', style: TextStyle(fontSize: (),),), floatingActionButton: floatingActionButton (onPressed: () => Logic. Increase (), child: const Icon(Icons.add), ), ); }}Copy the code

You’ll find that the way to refresh a component is extremely simple: Obx(), which makes it fun to write fixed-point refresh operations around.

Conditions for the Obx() method to refresh

The refresh operation is performed only when the value of a responsive variable changes. When a variable is initially set to test and then set to test, the refresh operation is not performed.

When you define a reactive variable and it changes, the Obx() method that wraps the reactive variable does not refresh. Cool!

How do you update an entire class object if you set it as a response?

The following explanation comes from the official README document.

When you change one of the variables of the class and then perform an update operation, any Obx() that wraps the response variable will be refreshed. Set the whole class to the response type, which needs to be used in practical scenarios.

// model // We will make the entire class observable instead of each attribute. class User() { User({this.name = '', this.age = 0}); String name; int age; } // controller final user = User().obs; // When you need to update the user variable. User.update ((user) {// This parameter is the class itself that you want to update. user.name = 'Jonny'; user.age = 18; }); // Another way to update the user variable. User (user name: 'Joao, age: 35)); // view Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) user().name; // Note that this is a user variable, not a class variable (the first letter is lowercase).Copy the code

Simple State Management

GetBuilder: This is an extremely lightweight state manager that uses very few resources! Logic: Let’s start with the Logic layer

class CounterEasyGetLogic extends GetxController { var count = 0; void increase() { ++count; update(); }}Copy the code

view

class CounterEasyGetPage extends StatelessWidget { final CounterEasyGetLogic logic = Get.put(CounterEasyGetLogic()); @override Widget build(BuildContext context) {return Scaffold(appBar: appBar (title: const Text(' counter - simple ')), body: Center(Child: GetBuilder<CounterEasyGetLogic>(Builder: (logicGet) => Text(' click ${logicGet. Count} times ', style: TextStyle(fontSize: 30.0),),),), floatingActionButton: floatingActionButton (onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); }}Copy the code

Analysis: GetBuilder method.

  • Init: Although the above code is useless, this parameter is present in GetBuilder because the GetBuilder automatically looks for the CounterEasyGetLogic object when the variable is loaded, so it does not use the init parameter

  • Builder: Method parameter that has an input parameter and the type is the type of the generic passed in by GetBuilder

  • InitState, Dispose, etc. : GetBuilder has all periodic callbacks to the StatefulWidget and can do something within the corresponding callbacks

conclusion

Analysis of the

  • GetBuilder is essentially a wrapper around StatefulWidget internally, so the footprint is minimal

  • Reactive variables, because you are using StreamBuilder, consume resources

Usage scenarios

  • In general, you can use reactive variables for most scenarios

  • However, in a List with a large number of objects, all using reactive variables will generate a large number of StreamBuilders, which will strain memory. In this case, simple state management should be considered

Cross-page interaction

Cross-page interaction is a very important feature in complex scenarios. Let’s see how GetX implements cross-page event interaction.

implementation

Page 1, general code.

logic

The increment event here is called by another page, which is not used by the page itself.

class JumpOneLogic extends GetxController { var count = 0.obs; Void toJumpTwo() {get.tonamed (RouteConfig. JumpTwo, arguments: {' MSG ': 'I am the data from my last page '}); Void increase() => count++; } </pre> **view** Here is a text and jump function. <pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box ! important; word-wrap: break-word ! important; font-size: inherit; color: inherit; line-height: inherit;" >class JumpOnePage extends StatelessWidget {/// instantiate your class with get.put () to make it available to all current child routes. final JumpOneLogic logic = Get.put(JumpOneLogic()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: Text(' cross-page-one '), floatingActionButton: floatingActionButton (onPressed: () => logic.toJumpTwo(), child: const Icon(Icons.arrow_forward_outlined), ), body: Center( child: Obx(() => Text(' cross page -Two click ${logic.count.value} times ', style: TextStyle(fontSize: 30.0),),); }}Copy the code

Page two, this page is the focus.

logic

  • Will show how to call the event of the previous page

  • How to receive last page data

  • Note that GetxController contains a more complete lifecycle callback that accepts passed data in onInit(); If the received data needs to be refreshed to the interface, please receive the data operation in the onReady callback. OnReady is called in the addPostFrameCallback callback. The data refresh operation is performed in onReady to ensure that the interface is refreshed after the initial loading

class JumpTwoLogic extends GetxController { var count = 0.obs; var msg = ''.obs; @override void onReady() { var map = Get.arguments; msg.value = map['msg']; super.onReady(); Void increase() => count++; }Copy the code

view

  • Plus click event, click, can realize the transformation of two pages of data

  • GetXController = GetXController = GetXController = GetXController = GetXController = GetXController = GetXController

class JumpTwoPage extends StatelessWidget { final JumpOneLogic oneLogic = Get.find(); final JumpTwoLogic twoLogic = Get.put(JumpTwoLogic()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: Text(' Two'), floatingActionButton: floatingActionButton (onPressed: () {oneLogic. Increase (); twoLogic.increase(); }, child: const Icon(Icons.add), ), body: Center( child: Column(mainAxisSize: MainAxisSize.min, children: [// count Obx(() => Text(' cross page -Two click ${twoLogic.count. Value} times ', style: TextStyle(fontSize: 30.0),), / / transfer data Obx (() = > Text (' transfer data: ${twoLogic. MSG. Value} ', style: TextStyle (fontSize: 30.0),),),),); }}Copy the code

conclusion

GetX cross-page interaction events are very simple and very low intrusive. There is no need to configure anything in the main entry. In complex business scenarios, such a simple cross-page interaction can achieve a lot of things.

Advanced! counter

We may encounter many complex business scenarios, in complex business scenarios, just a module about the initialization of variables may be very much, at this time, if will also state (state) and logic (logical) write together, maintain may see more dizzy, there will be a state and logic layer separation, This way GetX can be used in slightly larger projects with a clean structure! Continue with the counter example here!

implementation

There are three structures: State, Logic, and View. Here is the template code generated using the plug-in:

  • Model: Select Default

  • Function: useFolder (default)

Take a look at the generated template code. state

class CounterHighGetState {
  CounterHighGetState() {
    ///Initialize variables
  }
} 
Copy the code

logic

import 'package:get/get.dart';

import 'state.dart';

class CounterHighGetLogic extends GetxController {
  final state = CounterHighGetState();
} 
Copy the code

view

import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'logic.dart'; import 'state.dart'; class CounterHighGetPage extends StatelessWidget { final CounterHighGetLogic logic = Get.put(CounterHighGetLogic()); final CounterHighGetState state = Get.find<CounterHighGetLogic>().state; @override Widget build(BuildContext context) { return Container(); }}Copy the code

Why are these three modules written? State needs to be mentioned separately. Please quickly browse below. The reason for using this variable type is that all operations are initialized in the constructor. If var is used directly and no value is immediately assigned, it cannot be derived as Rx. Therefore, it is directly defined as RxInt, which is actually very simple. Base types capitalize the initial letter and prefix it with Rx. It is possible to use var directly, but when using the.value response variable, you will need to write it by hand, so you should specify the Rx type.

class CounterHighGetState { RxInt count; CounterHighGetState() { count = 0.obs; }}Copy the code

logic

The logical layer is simpler, but note that the state class needs to be instantiated to begin with.

class CounterHighGetLogic extends GetxController { final state = CounterHighGetState(); // add () => ++state.count; // add () => ++state.count; }Copy the code

The view layer is essentially the same as the view layer, except that the state layer is separate. Because CounterHighGetLogic is instantiated, use get.find () directly to fetch the logic layer that was just instantiated, and then fetch state, which is received using a separate variable. Ok, at this point: Logic only focuses on triggering event interactions, and state only focuses on data.

class CounterHighGetPage extends StatelessWidget { final CounterHighGetLogic logic = Get.put(CounterHighGetLogic()); final CounterHighGetState state = Get.find<CounterHighGetLogic>().state; @override Widget build(BuildContext context) {return Scaffold(appBar: appBar (title: const Text(' counter - responsive ')), body: Center(child: Obx(() => Text(' hit ${state.count.value} times ', style: TextStyle(fontSize: (),),), floatingActionButton: floatingActionButton (onPressed: () => Logic. Increase (), child: const Icon(Icons.add), ), ); }}Copy the code

After seeing the above transformation, you may want to ridicule the screen: Pit bi ah, before the simple logic layer, was split into two, still make so much trouble, are you invited by the monkey funny than? Flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use: flutter_use:

Github.com/CNAD666/flu…

state

Class MainState {/// select index - RxInt selectedIndex; RxBool isUnfold; List<BtnInfo> List; //Navigation <BtnInfo> itemList; ///PageView pageList <Widget> pageList; PageController pageController; MainState() {// Initialize index selectedIndex = 0.obs; // Default isUnfold = false. Obs; PageList = [keepAliveWrapper(FunctionPage())), keepAliveWrapper(ExamplePage())), keepAliveWrapper(Center(child: Container())), ]; //item column itemList = [BtnInfo(title: "function ", icon: icon (Icons. Bubble_chart),), BtnInfo(title:" example ", icon: Icon (the Icons. Opacity),), BtnInfo (title: "Settings" Icon: Icon (the Icons. Settings),),]; // pageController pageController = pageController (); }}Copy the code

logic

class MainLogic extends GetxController { final state = MainState(); / / / switch TAB void switchTap (int index) {state. SelectedIndex. Value = index; } /// Whether to expand the sidebar void onUnfold(bool unfold) {state.isunfold. Value =! state.isUnfold.value; } } </pre> **view** <pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box ! important; word-wrap: break-word ! important; font-size: inherit; color: inherit; line-height: inherit;" >class MainPage extends StatelessWidget { final MainLogic logic = Get.put(MainLogic()); final MainState state = Get.find<MainLogic>().state; @override Widget build(BuildContext context) { return BaseScaffold( backgroundColor: Colors.white, body: Row(children: [/ / / sidebar area Obx (() = > SideNavigation (selectedIndex: state. SelectedIndex. Value, sideItems: state. The itemList, onItem: (index) { logic.switchTap(index); state.pageController.jumpToPage(index); }, isUnfold: state.isUnfold.value, onUnfold: (unfold) {logic.onunfold (unfold);},),), ///Expanded Expanded(Child: pageview.Builder (physics: NeverScrollableScrollPhysics(), itemCount: state.pageList.length, itemBuilder: (context, index) => state.pageList[index], controller: state.pageController, ), ) ]), ); }}Copy the code

As can be seen from the above, there are a lot of states in the state layer. When some modules involve a lot of: submitting form data, jumping data, displaying data, etc., the code in the state layer will be quite a lot. Believe me, it is really very much. In complex businesses, it is always wise to separate the state layer from the business logic layer.

The last

Simple business module, can use two layer structure: Logic, view; For complex service modules, it is recommended to use a three-tier structure: State, Logic, and View.

Routing management

GetX implements a set of very simple route management, which can be navigated in a very simple way, or navigated using named routes.

Simple routing

Main entrance configuration

void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( home: MainPage(), ); }} </pre> Routing is very simple to use, using API such as get.to (), briefly demonstrated here, detailed API description, at the end of this section. <pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box ! important; word-wrap: break-word ! important; font-size: inherit; color: inherit; line-height: inherit;" Get.to(SomePage());Copy the code

Named route navigation

Here is the recommended way to navigate using named routes.

  • Unified management of all pages

  • You might not feel it in your app, but on the Web side, the URL to load the page is the named route string that you set, which means that on the Web, you can navigate directly to the relevant page through the URL

Here’s how to use it. First, under the main entry/exit configuration

void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( initialRoute: RouteConfig.main, getPages: RouteConfig.getPages, ); }}Copy the code

RouteConfig class below is my related page, and its mapping page, please write according to their own page.

Class RouteConfig{// static final String main = "/"; Static final String dialog = "/dialog"; /// Bloc counter module static final String counter = "/counter"; Static final String testLayout = "/testLayout"; Static final String SmartDialog = "/ SmartDialog "; Bloc spanOne = "/spanOne"; static final String spanTwo = "/spanOne/spanTwo"; Static final String counterGet = "/counterGet"; static final String jumpOne = "/jumpOne"; static final String jumpTwo = "/jumpOne/jumpTwo"; Static final List<GetPage> getPages = [GetPage(name: main, page: () => MainPage())), GetPage(name: MainPage())) dialog, page: () => Dialog()), GetPage(name: counter, page: () => CounterPage()), GetPage(name: testLayout, page: () => TestLayoutPage()), GetPage(name: smartDialog, page: () => SmartDialogPage()), GetPage(name: spanOne, page: () => SpanOnePage()), GetPage(name: spanTwo, page: () => SpanTwoPage()), GetPage(name: counterGet, page: () => CounterGetPage()), GetPage(name: jumpOne, page: () => JumpOnePage()), GetPage(name: jumpTwo, page: () => JumpTwoPage()), ]; }Copy the code

Routing API

Note the Named route, just add Named to the end of the API, for example:

  • Default: Get to (SomePage ());
  • Named route: get.tonamed (” /somePage “);

For details on the Api, the following is a README document from GetX. Navigate to a new page.

Get.to(NextScreen());
Get.toNamed("/NextScreen"); 
Copy the code

Close SnackBars, Dialogs, BottomSheets, or anything else you would normally close with navigator.pop (context).

Get.back(); 
Copy the code

Go to the next page without the option to return to the previous one (for SplashScreens, login pages, etc.).

Get.off(NextScreen());
Get.offNamed("/NextScreen"); 
Copy the code

Go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests).

Get.offAll(NextScreen());
Get.offAllNamed("/NextScreen"); 
Copy the code

To send data to other pages, just send the parameters you want. Get accepts anything here, whether it’s a string, a Map, a List, or even an instance of a class.

Get.to(NextScreen(), arguments: 'Get is the best');
Get.toNamed("/NextScreen", arguments: 'Get is the best');
Copy the code

On your class or controller:

print(Get.arguments);
//print out: Get is the best
Copy the code

To navigate to the next route and receive or update data as soon as it returns:

var data = await Get.to(Payment());
var data = await Get.toNamed("/payment"); 
Copy the code

On another page, send data from the previous route:

Get.back(result: 'success'); If (data == 'success') madeAnything();Copy the code

If you don’t want to use GetX syntax, just change Navigator (uppercase) to Navigator (lowercase) and you can have all the functionality of standard navigation without using context, for example:

// The default Flutter navigation Navigator. Of (context). Push (context, MaterialPageRoute(Builder: (BuildContext context) { return HomePage(); },),); // Use the Flutter syntax, without the context. navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); },),); // get the syntax get. To (HomePage());Copy the code

Dynamic Web links

This is a very important feature, on the Web side, to ensure that parameters are passed to the page through the URL. Get provides advanced dynamic urls, just like on the Web. Web developers may already want this feature on Flutter, and Get solves this problem.

Get.offAllNamed("/NextScreen? device=phone&id=354&name=Enzo");Copy the code

In your controller/bloc/stateful/stateless class:

print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
Copy the code

You can also easily receive NamedParameters with Get.

void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(),), // You can define a different page for routes with parameters, or a different page for routes with no parameters, but you must use a slash "/" on routes that do not receive parameters, as described above. '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); }Copy the code

Sending named route data:

Get.toNamed("/profile/34954");
Copy the code

On the second page, get the data with the parameters:

print(Get.parameters['user']);
// out: 34954 
Copy the code

Now, all you need to do is use get.tonamed () to navigate your named route, without any context(you can call your route directly from your BLoC or Controller class), and your route will appear in the URL when your application is compiled to the Web.

The last

DEMO address:

https://github.com/CNAD666/flutter_use (click to enter our one thousand technical exchange rings)

GetX plugin address:

Github.com/CNAD666/get…

Welcome to join us and learn and progress together!