官网:https://pub.dev/packages/get
中文文档:https://github.com/jonataslaw/getx/blob/master/README.zh-cn.md
关于 GetX
现在Flutter的状态管理方案很多,redux、bloc、state、provider、Getx。
provider是官方提供的状态管理解决方案,主要功能就是状态管理。Getx是第三方的状态管理插件,不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、网络请求、数据验证等功能,相比其他状态管理插件Getx 简单、功能强大并且高性能。
简单来讲,GetX相比原始和其他方案的最大优点就是使用简单方便,功能丰富,且提供从头到脚的全家桶式的功能API。
-
GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。
-
GetX 有3个基本原则:
- 性能: GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。
- 效率: GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
- 结构: GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。
-
GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
-
Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。
通过Get Server 可以在你的后端完全重用你在前端写的代码。
GetX 主要有三大主要功能:
- 状态管理
- 路由管理
- 依赖管理
下面分别介绍。
安装
将 Get 添加到你的 pubspec.yaml 文件中。
dependencies:
get: ^4.6.5
在需要用到的文件中导入,它将被使用。
import 'package:get/get.dart';
设置应用入口
当我们导入依赖后,第一步需要在main.dart
入口文件中把原来的MaterialApp
换成 GetMaterialApp
作为顶层,如下所示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetXApp ());
}
class GetXApp extends StatelessWidget {
const GetXApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
appBarTheme: const AppBarTheme(centerTitle: true,)
),
home: const GetXHomePage(),
);
}
}
GetX 响应状态管理
例如使用 GetX 改造 Flutter 默认的计数器示例应用,只需要三步:
第一步:在定义变量时,在变量末尾加上 .obs
后缀
var _counter = 0.obs;
第二步:在使用变量的地方,使用Obx
来包裹
Obx(() => Text("${_counter}"))
第三步:点击按钮的时候,直接修改变量值,UI即可自动刷新
onPressed: () => _counter++
这就是全部,就这么简单。甚至连 setState
都不需要调用。
声明一个响应式变量三种方式:
第一种 使用 Rx{Type}
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
第二种是使用 Rx,规定泛型 Rx。
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
final user = Rx<User>(); // 自定义类 - 可以是任何类
第三种 更实用、更简单、更可取的方法,只需添加 .obs
作为value的属性。(推荐)
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;
final user = User().obs; // 自定义类 - 可以是任何类
再举个例子,比如我们定义一个List
:
RxList _list = ["张三", "李四"].obs;
然后使用它:
Obx(() {
return Column (
children: _list.map((v) { return ListTile(title: Text(v));}).toList(),
);
}),
之后修改它:
onPressed: () => _list.add("王五");
注意:Obx
和对应的状态变量是一一对应的,假如有多个变量,不能只在最外层包裹一个Obx
,每个使用Rx变量的地方都需要一个Obx
来包裹。
监听自定义类数据的变化
1. 创建 Person 类,使用响应式方式修饰成员变量
import 'package:get/get.dart';
class Person {
RxString name = "Jimi".obs; // rx 变量
RxInt age = 18.obs;
}
2. 获取类属性值以及改变值
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import './person.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var person = Person(); // 定义 Person 对象
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Getx Obx"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Obx(() => Text( // 读取时使用Obx包裹
"我的名字是 ${person.name}",
style: const TextStyle(color: Colors.red, fontSize: 30),
))
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
person.name.value = person.name.value.toUpperCase(); // 修改值
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
另一种方式,还可以定义整个类为响应式,例如:
import 'package:get/get.dart';
class Animal {
String username;
int age;
Animal(this.username,this.age);
}
在创建上面的 Animal
对象时,可以直接后面加.obs
后缀:
var a = Animal("xiao mao", 2).obs;
使用的时候还是用Obx
包装
Obx(() => Text(a.username))
修改的时候,需要修改变量指针的指向:
onPressed: () => a.value = Animal("小狗", 3)
GetX 路由管理
如果只需要配置Home页,跟MaterialApp
是一样的:
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
如果需要配置路由表:
GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
appBarTheme: const AppBarTheme(centerTitle: true)
),
initialRoute: "/",
defaultTransition: Transition.rightToLeft, // 配置全局路由页面切换动画效果
getPages: AppPage.routes, // 配置路由表
);
// routes.dart
// 路由表, 可放在单独文件中
import 'package:get/get.dart';
class AppPage {
static final routes = [
GetPage(name: "/", page: () => const Tabs()),
GetPage(name: "/shop", page: () => const ShopPage(), middlewares: [ShopMiddleWare()]),
GetPage(name: "/login", page: () => const LoginPage()),
GetPage(name: "/registerFirst", page: () => const RegisterFirstPage(), transition: Transition.fade),// 单独指定某个路由页面切换效果
GetPage(name: "/registerSecond", page: () => const RegisterSecondPage()),
GetPage(name: "/registerThird", page: () => const RegisterThirdPage()),
];
}
GetX 常用路由跳转方法:
方法 | 功能 |
---|---|
Get.to() | 跳转新页面 |
Get.toNamed() | 命名路由跳转 |
Get.back() | 路由返回 |
Get.off() | 进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等) |
Get.offAll() | 进入下一个页面并取消之前的所有路由 |
Get.offAndToNamed() | 跳转并使用下一个路由替换当前路由页面(可用于关闭中间页面,如搜索页面) |
Get.removeRoute() | 删除一个路由 |
Get.until() | 反复返回,直到表达式返回真 |
Get.offUntil() | 转到下一条路由,并删除所有之前的路由,直到表达式返回true |
Get.offNamedUntil() | 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true |
Get.arguments | 获取当前路由页面的参数 |
Get.previousRoute | 获取之前的路由名称 |
Get.rawRoute | 给出要访问的原始路由 |
路由传值:
Get.toNamed("/shop", arguments: {"id":3456});
接收页面:
print(Get.arguments);
路由中间页面:
上面路由表配置中GetPage
的 middlewares
参数可以配置路由跳转的中间页面,可以在该页面进行一些如权限判断拦截等业务,例如:
// shopMiddleware.dart
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
class ShopMiddleWare extends GetMiddleware {
RouteSettings? redirect(String? route) {
print("跳转ShopPage路由页面的中间页面-------");
// return null; // 返回空表示不做任何操作,走原来的路由跳转逻辑,即跳转到ShopPage
// 此处可判断没有权限后跳转到登录页面
return const RouteSettings(name: "/login", arguments: {});
}
}
优先级
设置中间件的优先级定义Get中间件的执行顺序。
final middlewares = [
GetMiddleware(priority: 2),
GetMiddleware(priority: 5),
GetMiddleware(priority: 4),
GetMiddleware(priority: -8),
];
这些中间件会按这个顺序执行 -8 => 2 => 4 => 5
下面是 GetMiddleware
的其他几个方法:
- onPageCalled: 在调用页面时,创建任何东西之前,这个函数会先被调用。您可以使用它来更改页面的某些内容或给它一个新页面。
GetPage onPageCalled(GetPage page) {
final authService = Get.find<AuthService>();
return page.copyWith(title: 'Welcome ${authService.UserName}');
}
- **OnBindingsStart:**这个函数将在绑定初始化之前被调用。在这里,您可以更改此页面的绑定。
List<Bindings> onBindingsStart(List<Bindings> bindings) {
final authService = Get.find<AuthService>();
if (authService.isAdmin) {
bindings.add(AdminBinding());
}
return bindings;
}
- OnPageBuildStart: 这个函数将在绑定初始化之后被调用。在这里,您可以在创建绑定之后和创建页面widget之前执行一些操作。
GetPageBuilder onPageBuildStart(GetPageBuilder page) {
print('bindings are ready');
return page;
}
-
OnPageBuilt: 这个函数将在GetPage.page调用后被调用,并给出函数的结果,并获取将要显示的widget。
-
OnPageDispose: 这个函数将在处理完页面的所有相关对象(Controllers, views, …)之后被调用。
GetX 依赖管理
GetX 依赖管理,相比 Provider
或者 InheritedWidget
更加简单方便。例如还是以计数器示例应用为例,使用依赖管理进行改造,实现如下需求:
- 每次点击都能改变状态
- 可以在不同页面之间共享状态
- 将业务逻辑与界面分离
第一步:定义一个 Controller
类
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
decrement() => count--;
}
第二步:在页面中创建并使用 Controller
class Home extends StatelessWidget {
Widget build(context) {
final Controller c = Get.put(Controller()); // 使用Get.put()实例化Controller,对所有子路由可用
return Scaffold(
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))), // 读取控制器中计数器的值
body: Center(
child: ElevatedButton(
child: Text("Go to Other"),
onPressed: () => Get.to(Other()) // 跳转Other页面
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: c.increment // 调用控制器的方法修改计数器的值
));
}
}
第三步:在其他路由页面中获取 Controller
来共享计数器的值
class Other extends StatelessWidget {
final Controller c = Get.find(); // 通过 Get.find() 找到控制器对象
Widget build(context){
return Scaffold(body: Center(child: Text("${c.count}"))); // 访问控制器的计数变量
}
}
可以看到,使用GetX依赖管理以后,你可能不再需要使用StatefulWidget
。这非常简单,GetX可以在不损失性能的情况下,加快开发速率,按时交付一切。
总结一下,GetX创建和获取控制器分别使用下面两行代码搞定:
Controller controller = Get.put(Controller());
Controller controller = Get.find();
我们要做的就是将原来分散在页面中的数据变量和修改数据变量的逻辑提取到这个 Controller
控制器的内部去即可。
是的,它看起来像魔术,GetX会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,GetX总会给你正确的控制器。然后你就可以恢复你在后面获得的控制器数据。
Text(controller.textFromApi);
想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,GetX会自动为你的控制器找到你想要的数据,而你甚至不需要任何额外的依赖关系。
关于依赖管理的更多细节请看此处。
GetX Binding
如果所有页面都要使用状态管理,在每个页面中都使用Get.put(MyController())
的方式来进行控制器实例的创建,未免太麻烦,因此 GetX 提供了更加简单的 Binding 功能,可以全局统一配置,无需单独每个页面一一创建。
先了解一下 GetX 提供的很多创建 Controller
实例的方法,可根据不同的业务来进行选择:
- Get.put(): 不使用控制器实例也会被创建
- Get.lazyPut(): 懒加载方式创建实例,只有在使用时才创建
- Get.putAsync(): Get.put() 的异步版版本
- Get.create(): 每次使用都会创建一个新的实例
下面看一下如何使用 Binding 功能
第一步:创建一个 Binding
类
import 'package:get/get.dart';
class AllControllerBinding implements Bindings {
void dependencies() {
// 所有的 Controller 都在这里配置
Get.lazyPut<BindingMyController>(() => BindingMyController()); // 懒加载方式
Get.lazyPut<BindingHomeController>(() => BindingHomeController());
}
}
第二步:在GetMaterialApp
中进行初始化绑定
return GetMaterialApp (
initialBinding: AllControllerBinding(),
home: GetXBindingExample(),
);
第三步:在页面中通过Get.find<MyController>()
获取和使用状态管理器
Obx(() => Text(
"计数器的值为 ${Get.find<BindingMyController>().count}",
style: TextStyle(color: Colors.red, fontSize: 30),
)),
ElevatedButton(
onPressed: () {
Get.find<BindingMyController>().increment();
},
child: Text("点击加1")
),
路由 + Binding + GetView
GetX 另一种使用 Binding
的方式是可以和路由相绑定,这种方式可以省去Get.find
的步骤。
第一步:在配置 GetMaterialApp
的路由表的GetPage
中设置 binding
属性
GetPage(
name: "/shop",
page: () => const ShopPage(),
binding: ShopControllerBinding(), // 设置该页面的 Binding
middlewares: [ShopMiddleWare()]
),
其中ShopControllerBinding
的内容:
import 'package:get/get.dart';
import '../controllers/shop.dart';
class ShopControllerBinding implements Bindings{
void dependencies() {
Get.lazyPut<ShopController>(() => ShopController());
}
}
ShopController
的内容:
import 'package:get/get.dart';
class ShopController extends GetxController {
RxInt counter = 10.obs;
void inc() {
counter.value++;
update();
}
}
第二步:在使用ShopController
的页面继承GetView
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/shop.dart';
class ShopPage extends GetView<ShopController> {
const ShopPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Title'),),
body: Center(
child: Column(
children: [
Obx(() => Text("${controller.counter}")),
const SizedBox(height: 40,),
ElevatedButton (
onPressed: () { controller.inc();},
child: const Text("shop counter+1"))
],
),
),
);
}
}
这样就无需写Get.find
了,直接使用即可。
但是需要注意的是,这种方式当路由页面被弹出后,对应的Controller
实例就会被销毁,而其他方式是不会销毁的(全局共享)。
Controller 的生命周期
GetX 中的 Controller 是有生命周期的,它提供了三个生命周期方法:
- onInit(): 在
widget
页面组件从内存创建时立即执行,可以用来执行初始化业务,例如页面初始数据请求。 - onReady(): 在
onInit()
之后调用1帧,它是插入导航事件的理想时机,如snackbar、dialogs, 或者一个新的路由 或异步请求等。 - onClose(): 页面销毁时调用,可以用来清理资源,或做持久化数据保存。
import 'package:get/get.dart';
class SearchController extends GetxController {
RxList hotList=[].obs;
void onInit() {
print("请求接口数据");
getHotList();
super.onInit();
}
void onReady() {
print("onReady");
super.onReady();
}
void onClose() {
print("onClose");
super.onClose();
}
void getHotList() {
hotList.add("我是一个模拟的数据");
update();
}
}
GetX 国际化和主题配置
国际化
第一步:定义一个语言包
import 'package:get/get.dart';
class Messages extends Translations {
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
第二步:在GetMaterialApp
中进行配置
- translations: 国际化配置文件
- locale: 设置默认语言,不设置的话为系统当前语言
- fallbackLocale:添加一个回调语言选项,以备上面指定的语言翻译不存在
return GetMaterialApp(
translations: Messages(), // 你的翻译语言包
locale: Locale('zh', 'CN'), // 使用指定的语言翻译
fallbackLocale: Locale('en', 'US'), // 指定的语言翻译不存在时的默认语言回调
);
第三步:使用翻译,只要将.tr
追加到指定的键上,就会使用Get.locale
和Get.fallbackLocale
的当前值进行翻译。
Text('title'.tr);
改变语言
调用Get.updateLocale(locale)
来更新语言环境。然后翻译会自动使用新的locale
。
var locale = Locale('en', 'US');
Get.updateLocale(locale);
获取系统语言
要读取系统语言,可以使用window.locale
。
import 'dart:ui' as ui;
return GetMaterialApp(
locale: ui.window.locale,
);
改变主题
请不要使用比GetMaterialApp
更高级别的widget来更新主题,这可能会造成键重复。很多人习惯于创建一个 "ThemeProvider
"的widget来改变应用主题,这在GetX™中是绝对没有必要的。
你可以创建你的自定义主题,并简单地将其添加到Get.changeTheme
中,而无需任何模板。
Get.changeTheme(ThemeData.light());
如果你想在 "onTap
"中创建类似于改变主题的按钮,你可以结合两个GetX API Get.changeTheme
+ Get.isDarkMode
来实现。 你可以把下面的代码放在onPressed
里。
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
当darkmode
被激活时,它将切换到light
主题,当light
主题被激活时,它将切换到dark
主题。
其他实用功能
GetX 的很多调用都无需传入 context
,使用非常方便。
方法 | 功能 |
---|---|
Get.defaultDialog() | 显示弹窗 |
Get.snackbar() | 显示 snackbar |
Get.bottomSheet() | 显示 BottomSheet |
Get.isSnackbarOpen | 检查 snackbar 是否打开 |
Get.isDialogOpen | 检查 dialog 是否打开 |
Get.isBottomSheetOpen | 检查 bottomsheet 是否打开 |
Get.changeTheme() | 切换主题颜色 |
Get.isDarkMode | 判断主题样式 |
Get.isAndroid Get.isIOS Get.isMacOS Get.isWindows Get.isLinux Get.isFuchsia | 检查应用程序在哪个平台上运行 |
Get.isMobile Get.isDesktop Get.isWeb | 检查设备类型,注意所有平台都是独立支持web浏览器的 |
Get.height Get.width | 相当于MediaQuery.of(context).size.height |
Get.context | 提供当前上下文 |
Get.contextOverlay | 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文 |
以下方法是对上下文context
的扩展。因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。
方法 | 功能 |
---|---|
context.width context.width | 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文 |
context.mediaQuerySize() | 类似于 MediaQuery.of(context).size |
context.mediaQueryPadding() | 类似于 MediaQuery.of(context).padding |
context.mediaQueryViewPadding() | 类似于 MediaQuery.of(context).viewPadding |
context.mediaQueryViewInsets() | 类似于 MediaQuery.of(context).viewInsets |
context.orientation() | 类似于 MediaQuery.of(context).orientation |
context.devicePixelRatio | 类似于MediaQuery.of(context).devicePixelRatio |
context.textScaleFactor | 类似于MediaQuery.of(context).textScaleFactor |
context.mediaQueryShortestSide | 查询设备最短边。 |
context.isLandscape() | 检查设备是否处于横向模式 |
context.isPortrait() | 检查设备是否处于纵向模式 |
context.heightTransformer() context.widthTransformer() | 让您可以定义一半的页面、三分之一的页面等。对响应式应用很有用。参数: dividedBy (double) 可选 - 默认值:1。参数: reducedBy (double) 可选 - 默认值:0。 |
context.showNavbar() | 如果宽度大于800,则为真 |
context.isPhone() | 如果最短边小于600p,则为真 |
context.isSmallTablet() | 如果最短边大于600p,则为真 |
context.isLargeTablet() | 如果最短边大于720p,则为真 |
context.isTablet() | 如果当前设备是平板电脑,则为真 |
context.responsiveValue<T>() | 根据页面大小返回一个值<T> 可以给值为:watch :如果最短边小于300mobile :如果最短边小于600tablet :如果最短边(shortestSide )小于1200desktop :如果宽度大于1200 |
GetUtils
GetX 还提供了一个方便的工具类 GetUtils
,里面提供了一些常用的方法,例如判断值是否为空、是否是数字、是否是视频、图片、音频、PPT、Word、APK、邮箱、手机号码、日期、MD5、SHA1等等。具体可以直接IDE查看 GetUtils
类。
GetConnect
GetConnect
可以便捷的通过http
或websockets
进行前后台通信。
默认配置
你能轻松的通过extend GetConnect
就能使用GET/POST/PUT/DELETE/SOCKET
方法与你的Rest API
或websockets
通信。
class UserProvider extends GetConnect {
// Get request
Future<Response> getUser(int id) => get('http://youapi/users/$id');
// Post request
Future<Response> postUser(Map data) => post('http://youapi/users', body: data);
// Post request with File
Future<Response<CasesModel>> postCases(List<int> image) {
final form = FormData({
'file': MultipartFile(image, filename: 'avatar.png'),
'otherFile': MultipartFile(image, filename: 'cover.png'),
});
return post('http://youapi/users/upload', form);
}
GetSocket userMessages() {
return socket('https://yourapi/users/socket');
}
}
自定义配置
GetConnect
具有多种自定义配置。你可以配置base Url
,配置响应,配置请求,添加权限验证,甚至是尝试认证的次数,除此之外,还可以定义一个标准的解码器,该解码器将把您的所有请求转换为您的模型,而不需要任何额外的配置。
class HomeProvider extends GetConnect {
void onInit() {
// All request will pass to jsonEncode so CasesModel.fromJson()
httpClient.defaultDecoder = CasesModel.fromJson;
httpClient.baseUrl = 'https://api.covid19api.com';
// baseUrl = 'https://api.covid19api.com'; // It define baseUrl to
// Http and websockets if used with no [httpClient] instance
// It's will attach 'apikey' property on header from all requests
httpClient.addRequestModifier((request) {
request.headers['apikey'] = '12345678';
return request;
});
// Even if the server sends data from the country "Brazil",
// it will never be displayed to users, because you remove
// that data from the response, even before the response is delivered
httpClient.addResponseModifier<CasesModel>((request, response) {
CasesModel model = response.body;
if (model.countries.contains('Brazil')) {
model.countries.remove('Brazilll');
}
});
httpClient.addAuthenticator((request) async {
final response = await get("http://yourapi/token");
final token = response.body['token'];
// Set the header
request.headers['Authorization'] = "$token";
return request;
});
//Autenticator will be called 3 times if HttpStatus is
//HttpStatus.unauthorized
httpClient.maxAuthRetries = 3;
}
Future<Response<CasesModel>> getCases(String path) => get(path);
}
可选的全局设置和手动配置
GetMaterialApp
为你配置了一切,但如果你想手动配置Get。
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
你也可以在GetObserver
中使用自己的中间件,这不会影响任何事情。
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
你可以为 "Get "创建全局设置。只需在推送任何路由之前将Get.config
添加到你的代码中。或者直接在你的GetMaterialApp
中做。
GetMaterialApp(
enableLog: true,
defaultTransition: Transition.fade,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: Get.defaultDurationTransition,
defaultGlobalState: Get.defaultGlobalState,
);
Get.config(
enableLog = true,
defaultPopGesture = true,
defaultTransition = Transitions.cupertino
)
你可以选择重定向所有来自Get
的日志信息。如果你想使用你自己喜欢的日志包,并想查看那里的日志。
GetMaterialApp(
enableLog: true,
logWriterCallback: localLogWriter,
);
void localLogWriter(String text, {bool isError = false}) {
// 在这里把信息传递给你最喜欢的日志包。
// 请注意,即使enableLog: false,日志信息也会在这个回调中被推送。
// 如果你想的话,可以通过GetConfig.isLogEnable来检查这个标志。
}
局部状态组件
这些Widgets允许您管理一个单一的值,并保持状态的短暂性和本地性。我们有Reactive和Simple两种风格。
例如,你可以用它们来切换TextField
中的obscureText
,也许可以创建一个自定义的可扩展面板(Expandable Panel),或者在"Scaffold "的主体中改变内容的同时修改BottomNavigationBar
中的当前索引。
ValueBuilder
StatefulWidget
的简化,它与setState
回调一起工作,并接受更新的值。
ValueBuilder<bool>(
initialValue: false,
builder: (value, updateFn) => Switch(
value: value,
onChanged: updateFn, // 你可以用( newValue )=> updateFn( newValue )。
),
// 如果你需要调用 builder 方法之外的东西。
onUpdate: (value) => print("Value updated: $value"),
onDispose: () => print("Widget unmounted"),
),
ObxValue
类似于ValueBuilder
,但这是Reactive版本,你需要传递一个Rx实例(还记得神奇的.obs吗?自动更新…是不是很厉害?)
ObxValue((data) => Switch(
value: data.value,
onChanged: data, // Rx 有一个 _callable_函数! 你可以使用 (flag) => data.value = flag,
),
false.obs,
),
有用的提示
.obs
(也称为_Rx_ Types)有各种各样的内部方法和操作符。
var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');
即使message
prints实际的字符串值,类型也是RxString所以,你不能做message.substring( 0, 4 )
。你必须在_observable_里面访问真正的value
。最常用的方法是".value", 但是你也可以用…
final name = 'GetX'.obs;
//只有在值与当前值不同的情况下,才会 "更新 "流。
name.value = 'Hey';
// 所有Rx属性都是 "可调用 "的,并返回新的值。
//但这种方法不接受 "null",UI将不会重建。
name('Hello');
// 就像一个getter,打印'Hello'。
name() ;
///数字。
final count = 0.obs;
// 您可以使用num基元的所有不可变操作!
count + 1;
// 注意!只有当 "count "不是final时,这才有效,除了var
count += 1;
// 你也可以与数值进行比较。
count > 2;
/// booleans:
final flag = false.obs;
// 在真/假之间切换数值
flag.toggle();
/// 所有类型。
// 将 "value "设为空。
flag.nil();
// 所有的toString()、toJson()操作都会向下传递到`value`。
print( count ); // 在内部调用 "toString() "来GetRxInt
final abc = [0,1,2].obs;
// 将值转换为json数组,打印RxList。
// 所有Rx类型都支持Json!
print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}');
// RxMap, RxList 和 RxSet 是特殊的 Rx 类型,扩展了它们的原生类型。
// 但你可以像使用普通列表一样使用列表,尽管它是响应式的。
abc.add(12); // 将12添加到列表中,并更新流。
abc[3]; // 和Lists一样,读取索引3。
// Rx和值是平等的,但hashCode总是从值中提取。
final number = 12.obs;
print( number == 12 ); // prints > true
///自定义Rx模型。
// toJson(), toString()都是递延给子代的,所以你可以在它们上实现覆盖,并直接打印()可观察的内容。
class User {
String name, last;
int age;
User({this.name, this.last, this.age});
String toString() => '$name $last, $age years old';
}
final user = User(name: 'John', last: 'Doe', age: 33).obs;
// `user`是 "响应式 "的,但里面的属性却不是!
// 所以,如果我们改变其中的一些变量:
user.value.name = 'Roi';
// 小部件不会重建!
// 对于自定义类,我们需要手动 "通知 "改变。
user.refresh();
// 或者我们可以使用`update()`方法!
user.update((value){
value.name='Roi';
});
print( user );
GetWidget
大多数人都不知道这个Widget,或者完全搞不清它的用法。这个用例非常少见且特殊:它 "缓存 "了一个Controller
,因为cache,所以不能成为一个const Stateless
。
那么,什么时候你需要 "缓存 "一个Controller?
如果你使用了GetX的另一个 "不常见 "的特性 Get.create()
Get.create(()=>Controller())
会在每次调用时生成一个新的Controller
Get.find<Controller>()
你可以用它来保存Todo项目的列表,如果小组件被 “重建”,它将保持相同的控制器实例。
GetxService
这个类就像一个 “GetxController”,它共享相同的生命周期(“onInit()”、“onReady()”、“onClose()”)。
但里面没有 “逻辑”。它只是通知GetX的依赖注入系统,这个子类不能从内存中删除。
所以这对保持你的 "服务 "总是可以被Get.find()
获取到并保持运行是超级有用的。比如
ApiService
,StorageService
,CacheService
。
Future<void> main() async {
await initServices(); /// 等待服务初始化.
runApp(SomeApp());
}
/// 在你运行Flutter应用之前,让你的服务初始化是一个明智之举。
因为你可以控制执行流程(也许你需要加载一些主题配置,apiKey,由用户自定义的语言等,所以在运行ApiService之前加载SettingService。
///所以GetMaterialApp()不需要重建,可以直接取值。
void initServices() async {
print('starting services ...');
///这里是你放get_storage、hive、shared_pref初始化的地方。
///或者moor连接,或者其他什么异步的东西。
await Get.putAsync(() => DbService().init());
await Get.putAsync(SettingsService()).init();
print('All services started...');
}
class DbService extends GetxService {
Future<DbService> init() async {
print('$runtimeType delays 2 sec');
await 2.delay();
print('$runtimeType ready!');
return this;
}
}
class SettingsService extends GetxService {
void init() async {
print('$runtimeType delays 1 sec');
await 1.delay();
print('$runtimeType ready!');
}
}
实际删除一个GetxService
的唯一方法是使用Get.reset()
,它就像"热重启 "你的应用程序。
所以如果你需要在你的应用程序的生命周期内对一个类实例进行绝对的持久化,请使用GetxService
。
GetX CLI和插件
GetX 提供了一个cli工具 Get CLI,通过该工具,无论是在服务器上还是在客户端,整个开发过程都可以完全自动化。
例如,Get CLI一个比较实用的功能是可以根据返回json的接口URL自动生成对应的Dart Model类
get generate [ModelName] on [Dir] from "https://api.github.com/users/CpdnCristiano"
此外,为了进一步提高您的生产效率,我们还为您准备了一些插件
- getx_template:一键生成每个页面必需的文件夹、文件、模板代码等等
- Android Studio/Intellij插件
- GetX Snippets:输入少量字母,自动提示选择后,可生成常用的模板代码
- Android Studio/Intellij扩展
- VSCode扩展
GetX 原理机制
关于 GetX 的原理机制可以参考这篇文章:Flutter GetX深度剖析 该文作者已经写的非常详细了。这里不再赘述。
不过我们可以简单总结一下:
.obs
的写法其实是利用了Dart的扩展语法,内部实现是一个具有callback
以及运算符重载的数据类型- 当读取
.obs
数据类型的value
变量值的时候(get value
),会添加一个观察者进行监听 - 当修改
.obs
数据类型的value
变量值的时候(set value
),则会触发监听回调,内部会获取value变量进一步执行setState()
自动刷新操作
简单来讲它就是 Dart的扩展语法 + 观察者模式 的应用,但是实现这样一个简洁的框架还是不简单的,从其代码中的层层包装和代理转发可见其复杂性。