State management and route management play an important role in the application framework of Flutter. At present, the mainstream solutions include Google’s official Provider, tripartite GetX, Bloc, fish-Redux, etc. GetX stands out from the rest.
GetX is a lightweight and powerful solution with high performance state management, intelligent dependency injection, and easy route management.
This article will teach you how to build your own Flutter application framework by integrating GetX from scratch.
0. GetX integration
Add the dependent
Add the GetX dependency to the pubspec.yaml file as follows:
dependencies:
flutter:
sdk: flutter
get: ^ 4.5.1
Copy the code
Initialize the GetX
To use GetX, GetX needs to be initialized. Replace the default MaterialApp with GetMaterialApp as follows:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePage(), ); }}Copy the code
1. Status management
GetX provides two methods of reactive state management: reactive variable mode and state manager mode.
Responsive variable
define
To define a responsive variable, add a.obs to the end of the variable:
var count = 0.obs;
Copy the code
Reactive variables can be used on any type:
final name = ' '.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String.int>{}.obs;
// Custom class - can be any class
final user = User().obs;
Copy the code
Gets the value of a reactive variable
Call value to get the value of the variable. You do not need to add.value to List or Map.
String nameValue = name.value
bool isLoggedValue = isLogged.value
int countValue = count.value
double numberValue = number.value
String item = items[0] //.value is not required
int value = myMap['key'] //.value is not required
String name = user.value.name
Copy the code
Update data:
For base data types, simply reassign value to update the data and refresh the interface via Obx:
name.value = "123"
isLogged.value = true
count.value = 1
number.value = 12.0
Copy the code
For other data types, call update or variable methods as follows:
user.update((value) { value? .name ="123";
});
Copy the code
Or reassign an object using the variable name method. For example, if the variable is user, the user() method can be used to update it:
user(User(name: "abcd", age: 25));
Copy the code
Refresh the interface
To use reactive variables on the interface, simply wrap Obx on the control that uses variables to achieve reactive update, that is, automatically refresh the interface when the value of the variable changes:
Obx(() => Text("${count.value}"))
Copy the code
Data change monitor
In addition to using Obx to automatically refresh interface data, GetX provides a variety of manual ways to monitor the data changes of responsive variables, and perform custom logic when data changes, such as re-request interface after data changes.
- Ever is triggered when data changes
- EverAll is much like “ever “except that it listens for changes in multiple responsive variables and triggers a callback when one of them changes
- Once is called only the first time a variable is changed
- Debounce, which is called after a certain amount of time and only the last change within a specified time will trigger a callback. If the time is set to 1 second and three data changes occur at an interval of 500 milliseconds, only the last change will trigger the callback.
- Only the last change in the interval triggers a callback. If the interval is set to 1 second, no matter how many times you click within 1 second, only the last callback will be triggered and then the next interval will be entered.
Usage:
///Every time`count`Called when changes.
ever(count, (newValue) => print("$newValue has been changed"));
///It is called only if the variable count is changed for the first time.
once(count, (newValue) => print("$newValue was changed once"));
///Anti-ddos - Called whenever the user stops typing for 1 second, for example.
debounce(count, (newValue) => print("debouce$newValue"), time: Duration(seconds: 1));
///Ignore all changes for 1 second, and only the last one triggers the callback.
interval(count, (newValue) => print("interval $newValue"), time: Duration(seconds: 1));
Copy the code
The sample
Counter functionality with reactive variables:
class CounterPage extends StatelessWidget {
var count = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter"),
),
body: Center(
child: Obx(() => Text("${count.value}", style: const TextStyle(fontSize: 50))),
),
floatingActionButton: FloatingActionButton(
child: constIcon(Icons.add), onPressed: () => count ++, ), ); }}Copy the code
This code implements a simple counter function, and a closer look shows that the count can be updated automatically without using the StatefulWidget. That’s the power of reactive variables.
State manager
GetX also provides the use of Controller to manage state, implement a custom Controller class inherited from GetxController, Controller for business logic processing, Update () is called when state data needs to be changed to notify it of the change.
Implementation method:
class CounterController extends GetxController{
int count = 0;
voidincrement(){ count ++ ; update(); }}Copy the code
When used in the interface, you need to wrap it with GetBuilder so that when using data changes in the Controller, the update() call will refresh the interface control.
GetBuilder<CounterController>(init: CounterController(), (controller) { return Text("${controller.count}", style: const TextStyle(fontSize: 50)); })Copy the code
If you use a Controller for the first time, you need to initialize it. If you use the same Controller again, you don’t need to initialize it.
After initialization, you can use get.find () to find the corresponding Controller:
The sample
Implement counters with Controller:
class CounterController extends GetxController{
int count = 0;
voidincrement(){ count ++ ; update(); }}class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter"),
),
body: Center(
child: Column(
mainAxisSize:MainAxisSize.min,
children: [
GetBuilder<CounterController>(
init: CounterController(), /// Initialize the Controller
builder: (controller) {
return Text("${controller.count}", style: const TextStyle(fontSize: 50));
}),
GetBuilder<CounterController>( ///No initialization
builder: (controller) {
return Text("${controller.count}", style: const TextStyle(fontSize: 50));
}),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
///Use find to find Controller
CounterController controller = Get.find();
///Call the Controller methodcontroller.increment(); },),); }}Copy the code
There are two numeric controls implemented, one that initializes the CounterController and the other that is used directly.
2. Dependency management
GetX dependency management is already used in the previous section. After initializing the Controller in GetBuilder, you can use get.find () to find the corresponding Controller elsewhere. GetX dependency management can inject instances of any type and provides multiple ways to insert/register dependencies.
Insert/register dependencies
Get.put
Insert dependent objects into GetX using PUT:
Get.put<CounterController>(CounterController());
Get.put<CounterController>(CounterController(), permanent: true);
Get.put<CounterController>(CounterController, tag: "counter");
Copy the code
When inserting a dependency, you can set additional parameters in addition to the instance of the dependency class:
- Permanent: Indicates whether the instance is permanent. The default value is false. The instance is destroyed when it is no longer used
- Tag: The tag used to distinguish different instances of the same class.
Get.lazyPut
Lazy initialization, where instance objects are not initialized until they are needed, that is, the first time a class is found.
///Only the first time you use get.find<CounterController>Then CounterController will be called.
Get.lazyPut<CounterController>(() => CounterController());
Get.lazyPut<CounterController>(
() {
// ... some logic if needed
return CounterController();
},
tag: Math.random().toString(),
fenix: true
)
Copy the code
LazyPut also takes extra parameters, which are basically the same as PUT.
-
Fenix: Similar to ‘permanent’, except that when not in use, the instance is discarded, but when needed again, Get recreates the instance
-
Tag: The tag used to distinguish different instances of the same class.
Get.putAsync
PutAsync can register an instance asynchronously. Used when some instance needs to be initialized asynchronously, such as SharedPreferences:
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter'.12345);
return prefs;
});
Copy the code
Put has the same permanent and tag parameters as PUT.
Get.create
Create is similar to put, except that permanent is true by default.
Get.create<CounterController>(() => CounterController());
Copy the code
use
Get the dependent instance with the find() method:
final controller = Get.find<CounterController>();
/ / or
CounterController controller = Get.find();
///Get by tag
final controller = Get.find<CounterController>("counter");
Copy the code
It is also possible to manually remove injected instances of dependencies using the delete() method. In most cases, this method is not called manually; GetX handles it internally and removes it when it is no longer needed
Get.delete<CounterController>();
Copy the code
3. Route management
Routing is also an important part of the Flutter project. Page hopping in a Flutter is achieved through routing. GetX provides common and alias routes.
Common routing
to
: Go to the next screen
Get.to(CounterPage());
Copy the code
Use arguments to pass arguments:
Get.to(CounterPage(), arguments: count);
Copy the code
Use arguments to pass arguments of any type.
Get the parameters on the next page:
dynamic args = Get.arguments;
Copy the code
off
: Goes to the next page, and the navigation does not return
Get.off(CounterPage());
Copy the code
offAll
: Go to the next screen and cancel all previous routes
Get.offAll(CounterPage());
Copy the code
back
Returns the
Get.back();
Copy the code
Return to pass parameter:
Get.back(result: 'success');
Copy the code
Get the return argument:
var data = await Get.to(CounterPage());
Copy the code
The alias routing
Create a RouteGet class to configure the route mapping: RouteGet (RouteGet)
class RouteGet {
/// page name
static const String counter = "/counter";
///pages map
static final List<GetPage> getPages = [
GetPage(
name: counter,
page: () => CounterPage(),
)
];
}
Copy the code
GetPage defines the mapping between the alias and the page.
Then initialRoute and getPages are configured in GetMaterialApp, that is, the initial page and route mapping set:
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'Flutter Demo', initialRoute: RouteGet.counter, getPages: RouteGet.getPages, theme: ThemeData( primarySwatch: Colors.blue, ) ); }}Copy the code
Jump and parameter passing
The use method is basically the same as the common route, but the method is more Named
- Route jump:
Get.toNamed(RouteGet.login);
Copy the code
- Route parameter transmission:
Get.toNamed(RouteGet.login, arguments: {"name":"aaaa"});
Copy the code
Route alias can also be directly followed by a parameter, similar to the way the Url get parameter is passed:
Get.toNamed("/NextScreen? device=phone&id=354&name=Enzo");
Copy the code
-
Receiving parameters:
Arguments (); Get arguments (); Get arguments ();
dynamic args = Get.arguments;
Copy the code
Get.parameters: get.parameters: get.parameters: get.parameters: get.parameters
Get.parameters['device']
Copy the code
Bindings
Bindings is mainly used with routing. When entering the page through GetX route, the dependencies method will be automatically called, and the dependencies can be registered here.
class CounterBinding implements Bindings {
@override
voiddependencies() { Get.lazyPut<CounterController>(() => CounterController()); Get.put<Service>(()=> Api()); }}Copy the code
Common routes:
Get.to(CounterPage(), binding: CounterBinding());
Copy the code
Alias routing is used, and the corresponding Bindings of routes are set in GetPage
///pages map
static final List<GetPage> getPages = [
GetPage(
name: counter,
page: () => CounterPage(),
binding: CounterBinding() ///Set the Binding)];Copy the code
The alias route is then used the same way
For more operations related to routes, see route_management
At this point, the integration of GetX and the use of the key features of state management, dependency management, and route management are implemented and the project is ready to begin.
4. Use of GetX plug-in
To make it easy to use GetX in your project, you can install the GetX plug-in, which allows you to quickly create page templates for GetX and quickly use getX-related functions with shortcut keys. The plug-in is shown as follows:
After installation, right-click the directory -> New, there will be GetX menu, select GetX in the popup interface can quickly create page template, plug-in use as shown in the picture:
After clicking OK, four files binding, Controller, State and View will be generated in the corresponding directory, as shown below:
The name of the file can be set in plug-in Settings.
The corresponding file content is as follows:
- **binding: ** is used to lazily load corresponding controllers
class CounterBinding extends Bindings {
@override
voiddependencies() { Get.lazyPut(() => CounterController()); }}Copy the code
- Controller: Write interface business logic code, including lifecycle callback functions
class CounterController extends GetxController {
final CounterState state = CounterState();
@override
void onReady() {
// TODO: implement onReady
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose(); }}Copy the code
- State: stores interface status data
class CounterState {
CounterState() {
///Initialize variables}}Copy the code
- View: interface control, mainly for interface development
class CounterPage extends StatelessWidget {
final controller = Get.find<CounterController>();
final state = Get.find<CounterController>().state;
@override
Widget build(BuildContext context) {
returnContainer(); }}Copy the code
The Bindings method used here automatically registers controllers in Bindings and generates find Controller code in the Page.
In addition, state is used here, in order to extract all state data separately and put them in state when there are too many state data on the page, which is more convenient for maintenance and avoids overcrowding of Controller.
For more information about the use of GetX plug-in, see the author’s article introduction: GetX code generation IDEA plug-in, super detailed function explanation (through the phenomenon to see the essence).
Internationalization of 5.
GetX provides multilingual internationalization processing to facilitate multilingual management and switching within a project.
- Start by creating a language resource class that inherits from GetX
Translations
To realizeget keys
:
class StringRes extends Translations{
@override
Map<String.Map<String.String>> get keys => {
'zh_CN': {
'hello': Hello world
},
'en_US': {'hello': 'Hello World'}}; }Copy the code
Return a multi-language configuration in keys, key is the language identifier, format is [country]_[language], value is a Map, store our actual text resources.
- Then, in
GetMaterialApp
To configure:
GetMaterialApp(
translations: StringRes(),
locale: const Locale('zh'.'CN'),
fallbackLocale: Locale('en'.'US')... ;Copy the code
Translations pass in objects that we’ve defined as inheriting from the translations class, and locale is the language we use by default, FallbackLocale is a resource that is configured to use fallbackLocale when we don’t have the default language resource.
Locale can also be used to get the system locale:
import 'dart:ui' as ui;
return GetMaterialApp(
locale: ui.window.locale,
);
Copy the code
- use
Use key. STR of the corresponding resource as follows:
Text('hello'.tr);
Copy the code
- Change the language
Use Get. UpdateLocale to change the language:
Get.updateLocale(const Locale('en'.'US'));
Copy the code
To optimize the
After the above configuration, the project realized multi-language, and can switch it, but found that if all languages are written into a file too much content is not easy to manage, so we can split the corresponding language resources, one file for each language:
str_res_zh.dart:
const zh_CN_res = {
'hello': Hello world};Copy the code
str_res_en:
const en_US_res = {
'hello': 'Hello World'};Copy the code
The StringRes is then modified as follows:
class StringRes extends Translations{
@override
Map<String.Map<String.String>> get keys => {
'zh_CN': zh_CN_res,
'en_US':en_US_res
};
}
Copy the code
So it’s easier to manage. ‘hello’.tr is used every time. This is not a friendly manual method, it is not prompt, and it can be written incorrectly, so we can optimize it by defining a class that holds a String key as we do with the Android String resource:
str_res_keys.dart
class SR{
static const hello = 'hello';
}
Copy the code
Modify the language resource configuration as follows:
const zh_CN_res = {
SR.hello: Hello world};const en_US_res = {
SR.hello: 'Hello World'};Copy the code
Then use the following:
Text(SR.hello.tr);
Copy the code
In this way, the multi-language configuration of the project is completed. The overall directory is shown in the figure below:
6. Other functions of GetX
snackbar
GetX provides a quick and easy way to use snackbar. Use the following methods:
Get.snackbar("title"."message");
Copy the code
The default pop-up is above, you can use the snackPosition to change the pop-up position, the effect is shown in the following figure:
In addition to the position, you can also set a number of properties, such as text color, background color, etc. The details of the properties can be set as follows:
String title,
String message, {
Color? colorText,
Duration? duration = const Duration(seconds: 3),
/// with instantInit = false you can put snackbar on initState
bool instantInit = true,
SnackPosition? snackPosition,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool? shouldIconPulse,
double? maxWidth,
EdgeInsets? margin,
EdgeInsets? padding,
double? borderRadius,
Color? borderColor,
double? borderWidth,
Color? backgroundColor,
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
TextButton? mainButton,
OnTap? onTap,
bool? isDismissible,
bool? showProgressIndicator,
DismissDirection? dismissDirection,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackStyle? snackStyle,
Curve? forwardAnimationCurve,
Curve? reverseAnimationCurve,
Duration? animationDuration,
double? barBlur,
double? overlayBlur,
SnackbarStatusCallback? snackbarStatus,
Color? overlayColor,
Form? userInputForm,
}
Copy the code
It can be set according to your requirements.
dialog
GetX provides a quick way to use a dialog, either by passing in the Widget displayed on the Dialog or by using the dialog style provided by GetX by default:
The first:
Get.dialog(Widget)
Copy the code
The second:
Get.defaultDialog(title: "title", middleText: "this is dialog message");
Copy the code
Effect:
In addition to title and middleText, you can also set the OK button, cancel button and corresponding callback, rounded corner, background color and other parameters as follows:
Future<T? > defaultDialog<T>({String title = "Alert",
EdgeInsetsGeometry? titlePadding,
TextStyle? titleStyle,
Widget? content,
EdgeInsetsGeometry? contentPadding,
VoidCallback? onConfirm,
VoidCallback? onCancel,
VoidCallback? onCustom,
Color? cancelTextColor,
Color? confirmTextColor,
String? textConfirm,
String? textCancel,
String? textCustom,
Widget? confirm,
Widget? cancel,
Widget? custom,
Color? backgroundColor,
bool barrierDismissible = true,
Color? buttonColor,
String middleText = "Dialog made in 3 lines of code",
TextStyle? middleTextStyle,
double radius = 20.0.// ThemeData themeData,
List<Widget>? actions,
// onWillPop Scope
WillPopCallback? onWillPop,
// the navigator used to push the dialog
GlobalKey<NavigatorState>? navigatorKey,
})
Copy the code
bottomSheet
Use as follows:
Get.bottomSheet(Container(
height: 200,
color: Colors.white,
child: const Center(
child: Text("bottomSheet"),),));Copy the code
Effect:
A closer look reveals that neither a Snackbar, dialog, nor bottomSheet needs a context, which means you can call it anywhere in the project.
If you want to cancel snackbar, dialog, bottomSheet you can use get.back ().
GetUtils
GetX also provides a number of utility methods that can be called using GetUtils, such as checking whether it is a mailbox or a file format type, as shown in the following figure:
GetX also provides some extension methods:
// Check which platform the application is running on.
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
// Check the device type
GetPlatform.isMobile
GetPlatform.isDesktop
// All platforms are independently Web-enabled!
// You can tell if you are running in the browser.
// On Windows, iOS, OSX, Android, etc.
GetPlatform.isWeb
// Equivalent to.mediaquery.of (context).size. Height,
// But cannot be changed.
Get.height
Get.width
// Provide the current context.
Get.context
/ / anywhere in your code, at the front desk provide snackbar/dialog/bottomsheet context.
Get.contextOverlay
// Note: the following methods are extensions to the context.
Since the context is accessible anywhere in your UI, you can use it anywhere in your UI code.
// If you need a variable height/width (e.g. the desktop or browser window can be zoomed), you will need to use context.
context.width
context.height
// Lets you define half of the page, one-third of the page, and so on.
// Useful for reactive applications.
// Parameters: dividedBy (double) Optional - Default: 1
// Parameter: reducedBy (double) Optional - Default: 0.
context.heightTransformer()
context.widthTransformer()
/// Similar to mediaQuery.of (context).size.
context.mediaQuerySize()
/// Similar to mediaQuery.of (context).padding.
context.mediaQueryPadding()
/// Similar to mediaQuery.of (context).viewPadding.
context.mediaQueryViewPadding()
/// Similar to mediaQuery.of (context).viewinsets.
context.mediaQueryViewInsets()
/// Similar to MediaQuery. Of (context). Orientation;
context.orientation()
///Check whether the device is in landscape mode
context.isLandscape()
///Check whether the device is in longitudinal mode.
context.isPortrait()
///Similar to mediaQuery.of (context).devicepixelRatio.
context.devicePixelRatio()
///Similar to mediaQuery.of (context).textScalefactor.
context.textScaleFactor()
///Query the shortest edge of a device.
context.mediaQueryShortestSide()
///True if the width is greater than 800.
context.showNavbar()
///True if the shortest edge is less than 600p.
context.isPhone()
///True if the shortest edge is greater than 600p.
context.isSmallTablet()
///True if the shortest side is greater than 720p.
context.isLargeTablet()
///True if the current device is a tablet
context.isTablet()
///Returns a value based on the page size<T>.
///The value can be given as:
///Watch: If the shortest edge is less than 300
///Mobile: if the shortest side is less than 600
///Tablet: If the shortestSide is less than 1200
///Desktop: if the width is greater than 1200
context.responsiveValue<T>()
Copy the code
Source: flutter_app_core
For more information, see the official documentation: GetX
References:
- Use Flutter GetX — simple charm!
- GetX code to generate IDEA plug-in, super detailed function explanation (through the phenomenon to see the essence)