Jonny Borges
Committed by GitHub

Merge pull request #644 from idootop/doc_zh_cn

Adding Chinese documentation.
![](get.png)
*Idiomas: Español (este archivo), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Polaco](README.pl.md).*
*Idiomas: Español (este archivo), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Polaco](README.pl.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ...
![](get.png)
_Languages: English (this file), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md),[Polish](README.pl.md)._
_Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md),[Polish](README.pl.md)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ...
![](get.png)
*Languages: [English](README.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), Polish (Jesteś tu).*
*Languages: [English](README.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), Polish (Jesteś tu).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ...
![](get.png)
*Idiomas: [Inglês](README.md), Português Brasileiro (este arquivo), [Espanhol](README-es.md), [Polaco](README.pl.md).*
*Idiomas: [Inglês](README.md), [Língua chinesa](README.zh-cn.md), Português Brasileiro (este arquivo), [Espanhol](README-es.md), [Polaco](README.pl.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ...
![](get.png)
_语言: 中文, [英文](README.md), [巴西葡萄牙语](README.pt-br.md), [西班牙语](README-es.md), [波兰语](README.pl.md)_
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx)
[![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g)
<a href="https://github.com/Solido/awesome-flutter">
<img alt="Awesome Flutter" src="https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square" />
</a>
<a href="https://www.buymeacoffee.com/jonataslaw" target="_blank"><img src="https://i.imgur.com/aV6DDA7.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" > </a>
![](getx.png)
- [关于 Get](#关于Get)
- [安装](#安装)
- [GetX 的计数器示例](#GetX的计数器示例)
- [三大功能](#三大功能)
- [状态管理](#状态管理)
- [响应式状态管理器](#响应式状态管理器)
- [关于状态管理的更多细节](#关于状态管理的更多细节)
- [路由管理](#路由管理)
- [关于路由管理的更多细节](#关于路由管理的更多细节)
- [依赖管理](#依赖管理)
- [关于依赖管理的更多细节](#关于依赖管理的更多细节)
- [实用工具](#实用工具)
- [国际化](#国际化)
- [翻译](#翻译)
- [使用翻译](#使用翻译)
- [语言](#语言)
- [改变语言](#改变语言)
- [系统语言](#系统语言)
- [改变主题](#改变主题)
- [其他高级API](#其他高级API)
- [可选的全局设置和手动配置](#可选的全局设置和手动配置)
- [局部状态组件](#局部状态组件)
- [ValueBuilder](#valuebuilder)
- [ObxValue](#obxvalue)
- [有用的提示](#有用的提示)
- [GetView](#getview)
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [从2.0开始的兼容性变化](#从2.0开始的兼容性变化)
- [为什么选择Getx?](#为什么选择Getx?)
- [社区](#社区)
- [社区频道](#社区频道)
- [如何做贡献](#如何做贡献)
- [文章和视频](#文章和视频)
# 关于Get
- GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。
- GetX 有3个基本原则:
- **性能:** GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。如果你感兴趣,这里有一个[性能测试](https://github.com/jonataslaw/benchmarks)
- **效率:** GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
- **结构:** GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。
- GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
- Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。
**通过[Get Server](https://github.com/jonataslaw/get_server)** 可以在你的后端完全重用你在前端写的代码。
**此外,通过[Get CLI](https://github.com/jonataslaw/get_cli)**,无论是在服务器上还是在前端,整个开发过程都可以完全自动化。
**此外,为了进一步提高您的生产效率,我们还为您准备了
[VSCode扩展](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)[Android Studio/Intellij扩展](https://plugins.jetbrains.com/plugin/14975-getx-snippets)**
# 安装
将 Get 添加到你的 pubspec.yaml 文件中。
```yaml
dependencies:
get:
```
在需要用到的文件中导入,它将被使用。
```dart
import 'package:get/get.dart';
```
# GetX的计数器示例
Flutter默认创建的 "计数器 "项目有100多行(含注释),为了展示Get的强大功能,我将使用 GetX 重写一个"计数器 Plus版",实现:
- 每次点击都能改变状态
- 在不同页面之间切换
- 在不同页面之间共享状态
- 将业务逻辑与界面分离
而完成这一切只需 **26 行代码(含注释)**
- 步骤1.
在你的MaterialApp前添加 "Get",将其变成GetMaterialApp。
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- 注意:这并不能修改Flutter的MaterialApp,GetMaterialApp并不是修改后的MaterialApp,它只是一个预先配置的Widget,它的子组件是默认的MaterialApp。你可以手动配置,但绝对没有必要。GetMaterialApp会创建路由,注入它们,注入翻译,注入你需要的一切路由导航。如果你只用Get来进行状态管理或依赖管理,就没有必要使用GetMaterialApp。GetMaterialApp对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文(context)的情况下是必要的。
- 注2: 只有当你要使用路由管理(`Get.to()`, `Get.back()`等)时才需要这一步。如果你不打算使用它,那么就不需要做第1步。
- 第二步:
创建你的业务逻辑类,并将所有的变量,方法和控制器放在里面。
你可以使用一个简单的".obs "使任何变量成为可观察的。
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}
```
- 第三步:
创建你的界面,使用StatelessWidget节省一些内存,使用Get你可能不再需要使用StatefulWidget。
```dart
class Home extends StatelessWidget {
// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
// 使用Obx(()=>每当改变计数时,就更新Text()。
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
// 用一个简单的Get.to()即可代替Navigator.push那8行,无需上下文!
body: Center(child: RaisedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
class Other extends StatelessWidget {
// 你可以让Get找到一个正在被其他页面使用的Controller,并将它返回给你。
final Controller c = Get.find();
@override
Widget build(context){
// 访问更新后的计数变量
return Scaffold(body: Center(child: Text("${c.count}")));
}
}
```
结果:
![](counter-app-gif.gif)
这是一个简单的项目,但它已经让人明白Get的强大。随着项目的发展,这种差异将变得更加显著。
Get的设计是为了与团队合作,但它也可以让个人开发者的工作变得更简单。
加快开发速率,在不损失性能的情况下按时交付一切。Get并不适合每一个人,但如果你认同这句话,Get就是为你准备的!
# 三大功能
## 状态管理
目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个很糟糕的方法。你可以在Flutter的官方文档中查看到,[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它们实际上无法用于任何中等或大型应用。
Get 并不是比任何其他状态管理器更好或更差,而是说你应该分析这些要点以及下面的要点来选择只用Get,还是与其他状态管理器结合使用。
Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,既可以单独使用,也可以与其他状态管理器结合使用。
Get有两个不同的状态管理器:简单的状态管理器(GetBuilder)和响应式状态管理器(GetX)。
### 响应式状态管理器
响应式编程可能会让很多人感到陌生,因为觉得它很复杂,但是GetX将响应式编程变得非常简单。
- 你不需要创建StreamControllers.
- 你不需要为每个变量创建一个StreamBuilder。
- 你不需要为每个状态创建一个类。
- 你不需要为一个初始值创建一个get。
使用 Get 的响应式编程就像使用 setState 一样简单。
让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。
这就是你的计数变量。
```dart
var name = 'Jonatas Borges';
```
要想让它变得可观察,你只需要在它的末尾加上".obs"。
```dart
var name = 'Jonatas Borges'.obs;
```
而在UI中,当你想显示该值并在值变化时更新页面,只需这样做。
```dart
Obx(() => Text("${controller.name}"));
```
这就是全部,就这么简单。
### 关于状态管理的更多细节
**关于状态管理更深入的解释请查看[这里](./documentation/zh_CN/state_management.md)。在那里你将看到更多的例子,以及简单的阶段管理器和响应式状态管理器之间的区别**
你会对GetX的能力有一个很好的了解。
## 路由管理
如果你想免上下文(context)使用路由/snackbars/dialogs/bottomsheets,GetX对你来说也是极好的,来吧展示:
在你的MaterialApp前加上 "Get",把它变成GetMaterialApp。
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
导航到新页面
```dart
Get.to(NextScreen());
```
用别名导航到新页面。查看更多关于命名路由的详细信息[这里](./documentation/zh_CN/route_management.md#navigation-with-named-routes)
```dart
Get.toNamed('/details');
```
要关闭snackbars, dialogs, bottomsheets或任何你通常会用Navigator.pop(context)关闭的东西。
```dart
Get.back();
```
进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等)。
```dart
Get.off(NextScreen());
```
进入下一个页面并取消之前的所有路由(在购物车、投票和测试中很有用)。
```dart
Get.offAll(NextScreen());
```
注意到你不需要使用context来做这些事情吗?这就是使用Get路由管理的最大优势之一。有了它,你可以在你的控制器类中执行所有这些方法,而不用担心context在哪里。
### 关于路由管理的更多细节
**关于别名路由,和对路由的低级控制,请看[这里](./documentation/zh_CN/route_management.md)**
## 依赖管理
Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider context,无需inheritedWidget。
```dart
Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller();
```
- 注意:如果你使用的是Get的状态管理器,请多注意绑定api,这将使你的界面更容易连接到你的控制器。
你是在Get实例中实例化它,而不是在你使用的类中实例化你的类,这将使它在整个App中可用。
所以你可以正常使用你的控制器(或类Bloc)。
**提示:** Get依赖管理与包的其他部分是解耦的,所以如果你的应用已经使用了一个状态管理器(任何一个,都没关系),你不需要全部重写,你可以使用这个依赖注入。
```dart
controller.fetchApi();
```
想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那你需要一个状态管理器与Provider或Get_it一起使用来拿到它,对吗?用Get则不然,Get会自动为你的控制器找到你想要的数据,而你甚至不需要任何额外的依赖关系。
```dart
Controller controller = Get.find();
//是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,Get总会给你正确的控制器。
```
然后你就可以恢复你在后面获得的控制器数据。
```dart
Text(controller.textFromApi);
```
### 关于依赖管理的更多细节
**关于依赖管理的更深入解释请看[此处](./documentation/zh_CN/dependency_management.md)**
# 实用工具
## 国际化
### 翻译
翻译被保存为一个简单的键值字典映射。
要添加自定义翻译,请创建一个类并扩展`翻译`
```dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
```
#### 使用翻译
只要将`.tr`追加到指定的键上,就会使用`Get.locale`和`Get.fallbackLocale`的当前值进行翻译。
```dart
Text('title'.tr);
```
### 语言
传递参数给`GetMaterialApp`来定义语言和翻译。
```dart
return GetMaterialApp(
translations: Messages(), // 你的翻译
locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译
fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在
);
```
#### 改变语言
调用`Get.updateLocale(locale)`来更新语言环境。然后翻译会自动使用新的locale。
```dart
var locale = Locale('en', 'US');
Get.updateLocale(locale);
```
#### 系统语言
要读取系统语言,可以使用`window.locale`
```dart
import 'dart:ui' as ui;
return GetMaterialApp(
locale: ui.window.locale,
);
```
## 改变主题
请不要使用比`GetMaterialApp`更高级别的widget来更新主题,这可能会造成键重复。很多人习惯于创建一个 "ThemeProvider "的widget来改变应用主题,这在**GetX™**中是绝对没有必要的。
你可以创建你的自定义主题,并简单地将其添加到`Get.changeTheme`中,而无需任何模板。
```dart
Get.changeTheme(ThemeData.light());
```
如果你想在 "onTap "中创建类似于改变主题的按钮,你可以结合两个**GetX™** API来实现。
- 检查是否使用了深色的 "Theme "的API,以及 "Theme "更改API。
-`Theme` Change API,你可以把下面的代码放在`onPressed`里。
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
`.darkmode`被激活时,它将切换到light主题,当light主题被激活时,它将切换到dark主题。
## 其他高级API
```dart
// 给出当前页面的args。
Get.arguments
// 给出上一条路由的参数
Get.previousArguments
//给出以前的路由名称
Get.previousRoute
// 给出要访问的原始路由,例如,rawRoute.isFirst()
Get.rawRoute
// 允许从GetObserver访问Rounting API。
Get.routing
// 检查 snackbar 是否打开
Get.isSnackbarOpen
// 检查 dialog 是否打开
Get.isDialogOpen
// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen
// 删除一个路由。
Get.removeRoute()
//反复返回,直到表达式返回真。
Get.until()
// 转到下一条路由,并删除所有之前的路由,直到表达式返回true。
Get.offUntil()
// 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。
Get.offNamedUntil()
//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
//检查设备类型
GetPlatform.isMobile
GetPlatform.isDesktop
//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb
// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height
Get.width
// 提供当前上下文。
Get.context
// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay
// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。
// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width
context.height
// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值:1
// 参数: reducedBy (double) 可选 - 默认值:0。
context.heightTransformer()
context.widthTransformer()
/// 类似于 MediaQuery.of(context).size。
context.mediaQuerySize()
/// 类似于 MediaQuery.of(context).padding。
context.mediaQueryPadding()
/// 类似于 MediaQuery.of(context).viewPadding。
context.mediaQueryViewPadding()
/// 类似于 MediaQuery.of(context).viewInsets。
context.mediaQueryViewInsets()
/// 类似于 MediaQuery.of(context).orientation;
context.orientation()
///检查设备是否处于横向模式
context.isLandscape()
///检查设备是否处于纵向模式。
context.isPortrait()
///类似于MediaQuery.of(context).devicePixelRatio。
context.devicePixelRatio()
///类似于MediaQuery.of(context).textScaleFactor。
context.textScaleFactor()
///查询设备最短边。
context.mediaQueryShortestSide()
///如果宽度大于800,则为真。
context.showNavbar()
///如果最短边小于600p,则为真。
context.isPhone()
///如果最短边大于600p,则为真。
context.isSmallTablet()
///如果最短边大于720p,则为真。
context.isLargeTablet()
///如果当前设备是平板电脑,则为真
context.isTablet()
///根据页面大小返回一个值<T>。
///可以给值为:
///watch:如果最短边小于300
///mobile:如果最短边小于600
///tablet:如果最短边(shortestSide)小于1200
///desktop:如果宽度大于1200
context.responsiveValue<T>()
```
### 可选的全局设置和手动配置
GetMaterialApp为你配置了一切,但如果你想手动配置Get。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
你也可以在`GetObserver`中使用自己的中间件,这不会影响任何事情。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
```
你可以为 "Get "创建_全局设置。只需在推送任何路由之前将`Get.config`添加到你的代码中。
或者直接在你的`GetMaterialApp`中做。
```dart
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`的日志信息。
如果你想使用你自己喜欢的日志包,并想查看那里的日志。
```dart
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`回调一起工作,并接受更新的值。
```dart
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`](#valuebuilder),但这是Reactive版本,你需要传递一个Rx实例(还记得神奇的.obs吗?自动更新......是不是很厉害?)
```dart
ObxValue((data) => Switch(
value: data.value,
onChanged: data, // Rx 有一个 _callable_函数! 你可以使用 (flag) => data.value = flag,
),
false.obs,
),
```
## 有用的提示
`.obs`ervables (也称为_Rx_ Types)有各种各样的内部方法和操作符。
> `.obs`的属性**是**实际值,不要搞错了!
> 我们避免了变量的类型声明,因为Dart的编译器足够聪明,而且代码
> 看起来更干净,但:
```dart
var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');
```
即使`message` _prints_实际的字符串值,类型也是**RxString**
所以,你不能做`message.substring( 0, 4 )`
你必须在_observable_里面访问真正的`value`
最常用的方法是".value", 但是你也可以用...
```dart
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});
@override
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 );
```
#### GetView
我很喜欢这个Widget,很简单,很有用。
它是一个对已注册的`Controller`有一个名为`controller`的getter的`const Stateless`的Widget,仅此而已。
```dart
class AwesomeController extends GetxController {
final String title = 'My Awesome View';
}
// 一定要记住传递你用来注册控制器的`Type`!
class AwesomeView extends GetView<AwesomeController> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text( controller.title ), // 只需调用 "controller.something"。
);
}
}
```
#### GetWidget
大多数人都不知道这个Widget,或者完全搞不清它的用法。
这个用例非常少见且特殊:它 "缓存 "了一个Controller,由于_cache_,不能成为一个 "const Stateless"(因为_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`
```dart
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`
# 从2.0开始的兼容性变化
1- Rx类型。
| Before | After |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
现在RxController和GetBuilder已经合并了,你不再需要记住你要用哪个控制器,只要用GetxController就可以了,它可以用于简单的状态管理,也可以用于响应式。
2- 别名路由
之前:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
现在:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
为什么要做这样的改变?
通常情况下,可能需要通过一个参数,或者一个登录令牌来决定显示哪个页面。
将页面插入到一个函数中,大大降低了RAM的消耗,因为自从应用程序启动后,路由将不会在内存中分配。
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```
# 为什么选择Getx?
1- Flutter更新后,很多时候,你的很多包都会坏掉。有时会发生编译错误,经常出现的错误,至今仍没有答案,开发者需要知道错误的来源,跟踪错误,才会尝试在相应的仓库中开一个问题,并看到其问题的解决。Get集中了开发的主要资源(状态、依赖和路由管理),让你可以在pubspec中添加一个包,然后开始工作。Flutter更新后,你唯一需要做的就是更新Get依赖,然后开始工作。Get还可以解决兼容性问题。有多少次,一个包的版本与另一个包的版本不兼容,因为一个包在一个版本中使用了依赖,而另一个包在另一个版本中使用了依赖?使用Get也不用担心这个问题,因为所有的东西都在同一个包里,是完全兼容的。
2- Flutter很简单,Flutter很不可思议,但是Flutter仍然有一些代码,对于大多数开发者来说可能是不需要的,比如`Navigator.of(context).push (context, builder [...]`,你写了8行代码仅仅只为了调用一个路由。而使用Get只需`Get.to(Home())`就完成了,你将进入下一个页面。动态网页URL是目前Flutter中非常痛苦的一件事,而用GetX则非常简单。在Flutter中管理状态,管理依赖关系也产生了很多讨论,因为pub中的模式有上百种。但是没有什么比在你的变量末尾加一个".obs "更简单的了,把你的widget放在一个Obx里面,就这样,所有对这个变量的更新都会在页面上自动更新。
3-轻松,不用担心性能。Flutter的性能已经很惊人了,但是想象一下,你使用一个状态管理器,和一个定位器来分布你的blocs/stores/controllers/等等类。当你不需要那个依赖的时候,你必须手动调用排除它。但是,你有没有想过简单地使用你的控制器,当它不再被任何人使用时,它会简单地从内存中删除?这就是GetX所做的。有了SmartManagement,所有不被使用的东西都会从内存中删除,除了编程,您不应该担心任何事情。GetX将保证您消耗的是最低限度的必要资源,甚至没有为此创建一个逻辑。
4-实际解耦。你可能听说过 "将界面与业务逻辑分离 "的概念。这并不是BLoC、MVC、MVVM的特例,市面上的其他标准都有这个概念。但是,由于使用了上下文(context),这个概念在Flutter中往往可以得到缓解。
如果你需要上下文来寻找InheritedWidget,你需要在界面中找到它,或者通过参数传递上下文。我特别觉得这种解决方案非常丑陋,要在团队中工作,我们总会对View的业务逻辑产生依赖。Getx与标准的做法不一样,虽然它并没有完全禁止使用StatefulWidgets、InitState等,但它总有类似的方法,可以更干净。控制器是有生命周期的,例如当你需要进行APIREST请求时,你不依赖于界面中的任何东西。你可以使用onInit来启动http调用,当数据到达时,变量将被填充。由于GetX是完全响应式的(真的,在流下工作),一旦项目被填充,所有使用该变量的widgets将在界面中自动更新。这使得具有UI专业知识的人只需要处理widget,除了用户事件(比如点击按钮)之外,不需要向业务逻辑发送任何东西,而处理业务逻辑的人将可以自由地单独创建和测试业务逻辑。
这个库会一直更新和实现新的功能。欢迎提供PR,并为其做出贡献。
# 社区
## 社区渠道
GetX拥有一个非常活跃且乐于助人的社区。如果你有问题,或者想得到关于这个框架使用的任何帮助,请加入我们的社区频道。这个资源库是提问、申请资源的专用库,欢迎随时加入GetX社区。
| **Slack** | **Discord** | **Telegram** |
| :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) |
## 如何做贡献
_想为项目做贡献吗?我们将自豪地强调你是我们的合作者之一。以下是您可以做出贡献并使Get(和Flutter)变得更好的几点。
- 帮助将readme翻译成其他语言。
- 为readme添加文档(Get的很多功能还没有被记录下来)。
- 撰写文章或制作视频,教大家如何使用Get(它们将被记录到readme和未来的Wiki中)。
- 提供代码/测试的PR。
- 包括新功能。
欢迎任何贡献
## 文章和视频
- [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr).
- [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder.
- [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder.
- [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - utils, storage, bindings and other features video by Amateur Coder.
- [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder.
- [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder.
- [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman).
- [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli.
- [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli.
- [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris.
- [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter.
- [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter.
... ...
# #依赖管理
- [依赖管理](#依赖管理)
- [实例方法](#实例方法)
- [Get.put()](#Get.put())
- [Get.lazyPut](#Get.lazyPut)
- [Get.putAsync](#Get.putAsync)
- [Get.create](#Get.create)
- [使用实例化方法/类](#使用实例化方法/类)
- [方法之间的差异](#方法之间的差异)
- [Bindings](#Bindings)
- [Bindings类](#Bindings类)
- [BindingsBuilder](#BindingsBuilder)
- [智能管理](#智能管理)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#SmartManagement.onlyBuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepFactory)
- [Bindings的工作原理](#Bindings的工作原理)
- [注释](#注释)
Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider上下文,无需 inheritedWidget。
```dart
Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller();
```
你是在Get实例中实例化它,而不是在你正在使用的类中实例化你的类,这将使它在整个App中可用。
所以你可以正常使用你的控制器(或Bloc类)。
- 注意:如果你使用的是Get的状态管理器,请多关注[Bindings](#Bindings)api,这将使你的视图更容易连接到你的控制器。
- 注意事项²。Get的依赖管理与包中的其他部分是分离的,所以如果你的应用已经使用了一个状态管理器(任何一个,都没关系),你不需要修改也可以同时使用这个依赖注入管理器,完全没有问题。
## 实例方法
这些方法和它的可配置参数是:
### Get.put()
最常见的插入依赖关系的方式。例如,对于你的视图的控制器来说:
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
这是你使用put时可以设置的所有选项。
```dart
Get.put<S>(
// 必备:你想得到保存的类,比如控制器或其他东西。
// 注:"S "意味着它可以是任何类型的类。
S dependency
// 可选:当你想要多个相同类型的类时,可以用这个方法。
// 因为你通常使用Get.find<Controller>()来获取一个类。
// 你需要使用标签来告诉你需要哪个实例。
// 必须是唯一的字符串
String tag,
// 可选:默认情况下,get会在实例不再使用后进行销毁
// (例如:一个已经销毁的视图的Controller)
// 但你可能需要这个实例在整个应用生命周期中保留在那里,就像一个sharedPreferences的实例或其他东西。
//所以你设置这个选项
// 默认值为false
bool permanent = false,
// 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。
// 默认为false
bool overrideAbstract = false,
// 可选:允许你使用函数而不是依赖(dependency)本身来创建依赖。
// 这个不常用
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
可以懒加载一个依赖,这样它只有在使用时才会被实例化。这对于计算代价高的类来说非常有用,或者如果你想在一个地方实例化几个类(比如在Bindings类中),而且你知道你不会在那个时候使用这个类。
```dart
///只有当第一次使用Get.find<ApiMock>时,ApiMock才会被调用。
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() => {
// ... some logic if needed
return FirebaseAuth()
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
这是你在使用lazyPut时可以设置的所有选项。
```dart
Get.lazyPut<S>(
// 强制性:当你的类第一次被调用时,将被执行的方法。
InstanceBuilderCallback builder,
// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选:类似于 "永久",
// 不同的是,当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例,
// 就像 bindings api 中的 "SmartManagement.keepFactory "一样。
// 默认值为false
bool fenix = false
)
```
### Get.putAsync
如果你想注册一个异步实例,你可以使用`Get.putAsync`
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
这都是你在使用putAsync时可以设置的选项。
```dart
Get.putAsync<S>(
// 必备:一个将被执行的异步方法,用于实例化你的类。
AsyncInstanceBuilderCallback<S> builder,
// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
// 默认值为false
bool permanent = false
)
```
### Get.create
这个就比较棘手了。关于这个是什么和其他的区别,可以在[方法之间的差异](#方法之间的差异)部分找到详细的解释。
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
这是你在使用create时可以设置的所有选项。
```dart
Get.create<S>(
// 需要:一个返回每次调用"Get.find() "都会被新建的类的函数。
// 示例: Get.create<YourClass>(()=>YourClass())
FcBuilderFunc<S> builder,
// 可选:就像Get.put()一样,但当你需要多个同类的实例时,会用到它。
// 当你有一个列表,每个项目都需要自己的控制器时,这很有用。
// 需要是一个唯一的字符串。只要把标签改成名字
String name,
// 可选:就像 Get.put() 一样,
// 它是为了当你需要在整个应用中保活实例的时候
// 区别在于 Get.create 的 permanent默认为true
bool permanent = true
```
## 使用实例化方法/类
想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那么你会需要一个状态管理器与Provider或Get_it相结合,对吗?用Get则不然,你只需要让Get为你的控制器自动 "寻找",你不需要任何额外的依赖关系。
```dart
final controller = Get.find<Controller>();
// 或者
Controller controller = Get.find();
// 是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。
// 你可以实例化100万个控制器,Get总会给你正确的控制器。
```
然后你就可以恢复你在后面获得的控制器数据。
```dart
Text(controller.textFromApi);
```
由于返回的值是一个正常的类,你可以做任何你想做的事情。
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
移除一个Get实例:
```dart
Get.delete<Controller>(); //通常你不需要这样做,因为GetX已经删除了未使用的控制器。
```
## 方法之间的差异
首先,让我们来看看Get.lazyPut的 "fenix "和其他方法的 "permanent"。
`permanent`和`fenix`的根本区别在于你想如何存储实例。
强化:默认情况下,GetX会在不使用实例时删除它们。
这意味着 如果页面1有控制器1,页面2有控制器2,而你从堆栈中删除了第一个路由,(比如你使用`Get.off()`或`Get.offNamed()`)控制器1失去了它的使用,所以它将被删除。
但是如果你想选择使用`permanent:true`,那么控制器就不会在这个过渡中丢失--这对于你想在整个应用程序中保持生命的服务来说非常有用。
`fenix`则是针对那些你不担心在页面变化之间丢失的服务,但当你需要该服务时,你希望它还活着。所以基本上,它会处理未使用的控制器/服务/类,但当你需要它时,它会 "从灰烬中重新创建 "一个新的实例。
继续说说方法之间的区别:
- Get.put和Get.putAsync的创建顺序是一样的,不同的是,第二个方法使用的是异步方法创建和初始化实例。put是直接插入内存,使用内部方法`insert`,参数`permanent: false`和`isSingleton: true`(这个isSingleton参数只是告诉它是使用`dependency`上的依赖,还是使用`FcBuilderFunc`上的依赖),之后,调用`Get.find()`,立即初始化内存中的实例。
- Get.create。顾名思义,它将 "创建 "你的依赖关系!类似于`Get.put()`。与`Get.put()`类似,它也会调用内部方法`insert`来实例化。但是`permanent`变成了true,而`isSingleton`变成了false(因为我们是在 "创建 "我们的依赖关系,所以它没有办法成为一个单例,这就是为什么是false)。因为它有`permanent: true`,所以我们默认的好处是不会在页面跳转之间销毁它。另外,`Get.find()`并不是立即被调用,而是等待在页面中被调用,这样创建是为了利用 "permanent "这个参数,值得注意的是,`Get.create()`的目标是创建不共享的实例,但不会被销毁,比如listView中的一个按钮,你想为该列表创建一个唯一的实例--正因为如此,Get.create必须和GetWidget一起使用。
- Get.lazyPut。顾名思义,这是一个懒加载的过程。实例被创建了,但它并没有被调用来立即使用,而是一直等待被调用。与其他方法相反,这里没有调用 "insert"。取而代之的是,实例被插入到内存的另一个部分,这个部分负责判断实例是否可以被重新创建,我们称之为 "工厂"。如果我们想创建一些以后使用的东西,它不会和现在使用的东西混在一起。这就是 "fenix "的魔力所在:如果你选择留下 "fenix: false",并且你的 "smartManagement "不是 "keepFactory",那么当使用 "Get.find "时,实例将把内存中的位置从 "工厂 "改为普通实例内存区域。紧接着,默认情况下,它将从 "工厂 "中移除。现在,如果你选择 "fenix: true",实例将继续存在这个专用的部分,甚至进入公共区域,以便将来再次被调用。
## Bindings
这个包最大的区别之一,也许就是可以将路由、状态管理器和依赖管理器完全集成。
当一个路由从Stack中移除时,所有与它相关的控制器、变量和对象的实例都会从内存中移除。如果你使用的是流或定时器,它们会自动关闭,你不必担心这些。
在2.10版本中,Get完全实现了Bindings API。
现在你不再需要使用init方法了。如果你不想的话,你甚至不需要键入你的控制器。你可以在适当的地方启动你的控制器和服务来实现。
Binding类是一个将解耦依赖注入的类,同时 "Bindings "路由到状态管理器和依赖管理器。
这使得Get可以知道当使用某个控制器时,哪个页面正在显示,并知道在哪里以及如何销毁它。
此外,Binding类将允许你拥有SmartManager配置控制。你可以配置依赖关系,当从堆栈中删除一个路由时,或者当使用它的widget被布置时,或者两者都不布置。你将有智能依赖管理为你工作,但即使如此,你也可以按照你的意愿进行配置。
### Bindings类
- 创建一个类并实现Binding
```dart
class HomeBinding implements Bindings {}
```
你的IDE会自动要求你重写 "dependencies"方法,然后插入你要在该路由上使用的所有类。
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
现在你只需要通知你的路由,你将使用该 Binding 来建立路由管理器、依赖关系和状态之间的连接。
- 使用别名路由:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- 使用正常路由。
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
至此,你不必再担心你的应用程序的内存管理,Get将为你做这件事。
Binding类在调用路由时被调用,你可以在你的GetMaterialApp中创建一个 "initialBinding "来插入所有将要创建的依赖关系。
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
创建Bindings的默认方式是创建一个实现Bindings的类,但是,你也可以使用`BindingsBuilder`回调,这样你就可以简单地使用一个函数来实例化任何你想要的东西。
例子:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() => {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() => {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
这样一来,你就可以避免为每条路径创建一个 Binding 类,使之更加简单。
两种方式都可以完美地工作,我们希望您使用最适合您的风格。
### 智能管理
GetX 默认情况下会将未使用的控制器从内存中移除。
但是如果你想改变GetX控制类的销毁方式,你可以用`SmartManagement`类设置不同的行为。
#### 如何改变
如果你想改变这个配置(你通常不需要),就用这个方法。
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //这里
home: Home(),
)
)
}
```
#### SmartManagement.full
这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下,你会希望保持这个配置不受影响。如果你是第一次使用GetX,那么不要改变这个配置。
#### SmartManagement.onlyBuilders
使用该选项,只有在`init:`中启动的控制器或用`Get.lazyPut()`加载到Binding中的控制器才会被销毁。
如果你使用`Get.put()`或`Get.putAsync()`或任何其他方法,SmartManagement将没有权限移除这个依赖。
在默认行为下,即使是用 "Get.put "实例化的widget也会被移除,这与SmartManagement.onlyBuilders不同。
#### SmartManagement.keepFactory
就像SmartManagement.full一样,当它不再被使用时,它将删除它的依赖关系,但它将保留它们的工厂,这意味着如果你再次需要该实例,它将重新创建该依赖关系。
### Bindings的工作原理
Bindings会创建过渡性工厂,在你点击进入另一个页面的那一刻,这些工厂就会被创建,一旦换屏动画发生,就会被销毁。
这种情况发生得非常快,以至于分析器甚至都来不及注册。
当你再次导航到这个页面时,一个新的临时工厂将被调用,所以这比使用SmartManagement.keepFactory更可取,但如果你不想创建Bindings,或者想让你所有的依赖关系都在同一个Binding上,它肯定会帮助你。
Factories占用的内存很少,它们并不持有实例,而是一个具有你想要的那个类的 "形状 "的函数。
这在内存上的成本很低,但由于这个库的目的是用最少的资源获得最大的性能,所以Get连工厂都默认删除。
请使用对你来说最方便的方法。
## 注释
- 如果你使用多个Bindings,不要使用SmartManagement.keepFactory。它被设计成在没有Bindings的情况下使用,或者在GetMaterialApp的初始Binding中链接一个Binding。
- 使用Bindings是完全可选的,你也可以在使用给定控制器的类上使用`Get.put()`和`Get.find()`
然而,如果你使用Services或任何其他抽象,我建议使用Bindings来更好地组织。
... ...
- [路由管理](#路由管理)
- [如何使用](#如何使用)
- [普通路由导航](#普通路由导航)
- [别名路由导航](#别名路由导航)
- [发送数据到别名路由](#发送数据到别名路由)
- [动态网页链接](#动态网页链接)
- [中间件](#中间件)
- [免context导航](#免context导航)
- [SnackBars](#SnackBars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomSheets)
- [嵌套导航](#嵌套导航)
# 路由管理
这是关于Getx在路由管理方面的完整解释。
## 如何使用
将此添加到你的pubspec.yaml文件中。
```yaml
dependencies:
get:
```
如果你要在没有context的情况下使用路由/SnackBars/Dialogs/BottomSheets,或者使用高级的Get API,你只需要在你的MaterialApp前面加上 "Get",就可以把它变成GetMaterialApp,享受吧!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## 普通路由导航
导航到新的页面。
```dart
Get.to(NextScreen());
```
关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。
```dart
Get.back();
```
进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)。
```dart
Get.off(NextScreen());
```
进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。
```dart
Get.offAll(NextScreen());
```
要导航到下一条路由,并在返回后立即接收或更新数据。
```dart
var data = await Get.to(Payment());
```
在另一个页面上,发送前一个路由的数据。
```dart
Get.back(result: 'success');
```
并使用它,例:
```dart
if(data == 'success') madeAnything();
```
你不想学习我们的语法吗?
只要把 Navigator(大写)改成 navigator(小写),你就可以拥有标准导航的所有功能,而不需要使用context,例如:
```dart
// 默认的Flutter导航
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// 使用Flutter语法获得,而不需要context。
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// get语法 (这要好得多)
Get.to(HomePage());
```
## 别名路由导航
- 如果你喜欢用别名路由导航,Get也支持。
导航到下一个页面
```dart
Get.toNamed("/NextScreen");
```
浏览并删除前一个页面。
```dart
Get.offNamed("/NextScreen");
```
浏览并删除所有以前的页面。
```dart
Get.offAllNamed("/NextScreen");
```
要定义路由,使用GetMaterialApp。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
### 发送数据到别名路由
只要发送你想要的参数即可。Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
在你的类或控制器上:
```dart
print(Get.arguments);
//print out: Get is the best
```
### 动态网页链接
Get提供高级动态URL,就像在Web上一样。Web开发者可能已经在Flutter上想要这个功能了,Get也解决了这个问题。
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
在你的controller/bloc/stateful/stateless类上:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
你也可以用Get轻松接收NamedParameters。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
发送别名路由数据
```dart
Get.toNamed("/second/34954");
```
在第二个页面上,通过参数获取数据
```dart
print(Get.parameters['user']);
// out: 34954
```
现在,你需要做的就是使用Get.toNamed()来导航你的别名路由,不需要任何context(你可以直接从你的BLoC或Controller类中调用你的路由),当你的应用程序被编译到web时,你的路由将出现在URL中。
### 中间件
如果你想通过监听Get事件来触发动作,你可以使用routingCallback来实现。
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
如果你没有使用GetMaterialApp,你可以使用手动API来附加Middleware观察器。
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
创建一个MiddleWare类
```dart
class MiddleWare {
static observer(Routing routing) {
///你除了可以监听路由外,还可以监听每个页面上的SnackBars、Dialogs和Bottomsheets。
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
现在,在你的代码上使用Get:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## 免context导航
### SnackBars
用Flutter创建一个简单的SnackBar,你必须获得Scaffold的context,或者你必须使用一个GlobalKey附加到你的Scaffold上。
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// 在小组件树中找到脚手架并使用它显示一个SnackBars。
Scaffold.of(context).showSnackBar(snackBar);
```
用Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
有了Get,你所要做的就是在你代码的任何地方调用你的Get.snackbar,或者按照你的意愿定制它。
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// 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,
// FlatButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
如果您喜欢传统的SnackBars,或者想从头开始定制,包括只添加一行(Get.snackbar使用了一个强制性的标题和信息),您可以使用
`Get.rawSnackbar();`它提供了建立Get.snackbar的RAW API。
### Dialogs
打开Dialogs:
```dart
Get.dialog(YourDialogWidget());
```
打开默认Dialogs:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
你也可以用Get.generalDialog代替showGeneralDialog。
对于所有其他的FlutterDialogs小部件,包括cupertinos,你可以使用Get.overlayContext来代替context,并在你的代码中任何地方打开它。
对于不使用Overlay的小组件,你可以使用Get.context。
这两个context在99%的情况下都可以代替你的UIcontext,除了在没有导航context的情况下使用 inheritedWidget的情况。
### BottomSheets
Get.bottomSheet类似于showModalBottomSheet,但不需要context:
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () => {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () => {},
),
],
),
)
);
```
## 嵌套导航
Get让Flutter的嵌套导航更加简单。
你不需要context,而是通过Id找到你的导航栈。
- 注意:创建平行导航堆栈可能是危险的。理想的情况是不要使用NestedNavigators,或者尽量少用。如果你的项目需要它,请继续,但请记住,在内存中保持多个导航堆栈可能不是一个好主意(消耗RAM)。
看看它有多简单:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: FlatButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```
... ...
- [状态管理](#状态管理)
- [响应式状态管理器](#响应式状态管理器)
- [优点](#优势)
- [声明一个响应式变量](#声明一个响应式变量)
- [使用视图中的值](#使用视图中的值)
- [什么时候重建](#什么时候重建)
- [可以使用.obs的地方](#可以使用.obs的地方)
- [关于List的说明](#关于List的说明)
- [为什么使用.value](#为什么使用.value)
- [Obx()](#obx)
- [Workers](#Workers)
- [简单状态管理器](#简单状态管理器)
- [优点](#优点)
- [用法](#用法)
- [如何处理controller](#如何处理controller)
- [无需StatefulWidgets](#无需StatefulWidgets)
- [为什么它存在](#为什么它存在)
- [其他使用方法](#其他使用方法)
- [唯一的ID](#唯一的ID)
- [与其他状态管理器混用](#与其他状态管理器混用)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#GetBuilder-vs-GetX-vs-Obx-vs-MixinBuilder)
# 状态管理
目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个糟糕的方法。你可以在Flutter官方文档中查到,[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它实际上无法用于任何中等或大型应用。
其他的状态管理器也不错,但有其细微的差别。
- BLoC非常安全和高效,但是对于初学者来说非常复杂,这使得人们无法使用Flutter进行开发。
- MobX比BLoC更容易,而且是响应式的,几乎是完美的,但是你需要使用一个代码生成器,对于大型应用来说,这降低了生产力,因为你需要喝很多咖啡,直到你的代码在`flutter clean`之后再次准备好(这不是MobX的错,而是codegen真的很慢!)。
- Provider使用InheritedWidget来传递相同的监听器,以此来解决上面报告的ChangeNotifier的问题,这意味着对其ChangeNotifier类的任何访问都必须在widget树内。
Get并不是比任何其他状态管理器更好或更差,而是说你应该分析这些要点以及下面的要点来选择是只用Get,还是与其他状态管理器结合使用。Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,它的状态管理功能既可以单独使用,也可以与其他状态管理器结合使用。
## 响应式状态管理器
响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。
- 你不需要创建StreamControllers.
- 你不需要为每个变量创建一个StreamBuilder。
- 你不需要为每个状态创建一个类。
- 你不需要为一个初始值创建一个get。
使用 Get 的响应式编程就像使用 setState 一样简单。
让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。
这是你的计数变量:
```dart
var name = 'Jonatas Borges';
```
要想让它变得可观察,你只需要在它的末尾加上".obs"。
```dart
var name = 'Jonatas Borges'.obs;
```
就这么简单!
我们把这个reactive-".obs"(ervables)变量称为_Rx_。
我们做了什么?我们创建了一个 "Stream "的 "String",分配了初始值 "Jonatas Borges",我们通知所有使用 "Jonatas Borges "的widgets,它们现在 "属于 "这个变量,当_Rx_的值发生变化时,它们也要随之改变。
这就是GetX**的**魔力,这要归功于Dart的能力。
但是,我们知道,一个`Widget`只有在函数里面才能改变,因为静态类没有 "自动改变 "的能力。
你需要创建一个`StreamBuilder`,订阅这个变量来监听变化,如果你想在同一个范围内改变几个变量,就需要创建一个 "级联 "的嵌套`StreamBuilder`,对吧?
不,你不需要一个`StreamBuilder`,但你对静态类的理解是对的。
在视图中,当我们想改变一个特定的Widget时,我们通常有很多Flutter方式的模板。
有了**GetX**,你也可以忘记这些模板代码了。
`StreamBuilder( ... )`? `initialValue: ...`? `builder: ...`? 不,你只需要把这个变量放在`Obx()`这个Widget里面就可以了。
```dart
Obx (() => Text (controller.name));
```
_你只需记住 `Obx(()=>`
你只需将Widget通过一个箭头函数传递给 `Obx()`(_Rx_的 "观察者")。
`Obx`是相当聪明的,只有当`controller.name`的值发生变化时才会改变。
如果`name`是`"John"`,你把它改成了`"John"`(`name.value="John"`),因为它和之前的`value`是一样的,所以界面上不会有任何变化,而`Obx`为了节省资源,会直接忽略新的值,不重建Widget。**这是不是很神奇**
> 那么,如果我在一个`Obx`里有5个_Rx_(可观察的)变量呢?
当其中**任何**一个变量发生变化时,它就会更新。
> 如果我在一个类中有 30 个变量,当我更新其中一个变量时,它会更新该类中**所有**的变量吗?
不会,只会更新使用那个 _Rx_ 变量的**特定 Widget**
所以,只有当_Rx_变量的值发生变化时,**GetX**才会更新界面。
```
final isOpen = false.obs;
//什么都不会发生......相同的值。
void onButtonTap() => isOpen.value=false;
```
### 优势
**当你需要对更新的内容进行**精细的控制时,**GetX()** 可以帮助你。
如果你不需要 "unique IDs",比如当你执行一个操作时,你的所有变量都会被修改,那么就使用`GetBuilder`
因为它是一个简单的状态更新器(以块为单位,比如`setState()`),只用几行代码就能完成。
它做得很简单,对CPU的影响最小,只是为了完成一个单一的目的(一个_State_ Rebuild),并尽可能地花费最少的资源。
如果你需要一个**强大的**状态管理器,用**GetX**是不会错的。
它不能和变量一起工作,除了__flows__,它里面的东西本质都是`Streams`
你可以将_rxDart_与它结合使用,因为所有的东西都是`Streams`
你可以监听每个"_Rx_变量 "的 "事件"。
因为里面的所有东西都是 "Streams"。
这实际上是一种_BLoC_方法,比_MobX_更容易,而且没有代码生成器或装饰。
你可以把**任何东西**变成一个_"Observable"_,只需要在它末尾加上`.obs`
### 最高性能
除了有一个智能的算法来实现最小化的重建,**GetX**还使用了比较器以确保状态已经改变。
如果你的应用程序中遇到错误,并发送重复的状态变更,**GetX**将确保它不会崩溃。
使用**GetX**,只有当`value`改变时,状态才会改变。
这就是**GetX**,和使用MobX_的_`computed`的主要区别。
当加入两个__observable__,其中一个发生变化时,该_observable_的监听器也会发生变化。
使用**GetX**,如果你连接了两个变量,`GetX()`(类似于`Observer()`)只有在它的状态真正变化时才会重建。
### 声明一个响应式变量
你有3种方法可以把一个变量变成是 "可观察的"。
1 - 第一种是使用 **`Rx{Type}`**
```dart
// 建议使用初始值,但不是强制性的
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>({});
```
2 - 第二种是使用 **`Rx`**,规定泛型 `Rx<Type>`
```dart
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>();
```
3 - 第三种更实用、更简单、更可取的方法,只需添加 **`.obs`** 作为`value`的属性。
```dart
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;
```
##### 有一个反应的状态,很容易。
我们知道,_Dart_现在正朝着_null safety_的方向发展。
为了做好准备,从现在开始,你应该总是用一个**初始值**来开始你的_Rx_变量。
> 用**GetX**将一个变量转化为一个_observable_ + _initial value_是最简单,也是最实用的方法。
你只需在变量的末尾添加一个"`.obs`",即可把它变成可观察的变量,
然后它的`.value`就是_初始值_)。
### 使用视图中的值
```dart
// controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// 视图
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
如果我们把`count1.value++`递增,就会打印出来:
- `count 1 rebuild`
- `count 3 rebuild`
如果我们改变`count2.value++`,就会打印出来。
- `count 2 rebuild`
- `count 3 rebuild`
因为`count2.value`改变了,`sum`的结果现在是`2`
- 注意:默认情况下,第一个事件将重建小组件,即使是相同的`值`
这种行为是由于布尔变量而存在的。
想象一下你这样做。
```dart
var isLogged = false.obs;
```
然后,你检查用户是否 "登录",以触发`ever`的事件。
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
如果 "hasToken "是 "false","isLogged "就不会有任何变化,所以 "ever() "永远不会被调用。
为了避免这种问题,_observable_的第一次变化将总是触发一个事件,即使它包含相同的`.value`
如果你想删除这种行为,你可以使用:
`isLogged.firstRebuild = false;`
### 什么时候重建
此外,Get还提供了精细的状态控制。你可以根据特定的条件对一个事件进行条件控制(比如将一个对象添加到List中)。
```dart
// 第一个参数:条件,必须返回true或false。
// 第二个参数:如果条件为真,则为新的值。
list.addIf(item < limit, item);
```
没有装饰,没有代码生成器,没有复杂的程序: 爽歪歪!
你知道Flutter的计数器应用吗?你的Controller类可能是这样的。
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
用一个简单的。
```dart
controller.count.value++
```
你可以在你的UI中更新计数器变量,不管它存储在哪里。
### 可以使用.obs的地方
你可以在 obs 上转换任何东西,这里有两种方法:
* 可以将你的类值转换为 obs
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* 或者可以将整个类转换为一个可观察的类。
```dart
class User {
User({String name, int age});
var name;
var age;
}
//实例化时。
final user = User(name: "Camila", age: 18).obs;
```
### 关于List的说明
List和它里面的对象一样,是完全可以观察的。这样一来,如果你在List中添加一个值,它会自动重建使用它的widget。
你也不需要在List中使用".value",神奇的dart api允许我们删除它。
不幸的是,像String和int这样的原始类型不能被扩展,使得.value的使用是强制性的,但是如果你使用get和setter来处理这些类型,这将不是一个问题。
```dart
// controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// view
Text(controller.title.value), // 字符串后面需要有.value。
ListView.builder (
itemCount: controller.list.length // List不需要它
)
```
当你在使自己的类可观察时,有另外一种方式来更新它们:
```dart
// model
// 我们将使整个类成为可观察的,而不是每个属性。
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// controller
final user = User().obs;
//当你需要更新用户变量时。
user.update( (user) { // 这个参数是你要更新的类本身。
user.name = 'Jonny';
user.age = 18;
});
// 更新用户变量的另一种方式。
user(User(name: 'João', age: 35));
// view
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// 你也可以不使用.value来访问模型值。
user().name; // 注意是用户变量,而不是类变量(首字母是小写的)。
```
你可以使用 "assign "和" assignAll "。
"assign "会清除你的List,并添加一个单个对象。
"assignAll "将清除现有的List,并添加任何可迭代对象。
### 为什么使用.value
我们可以通过一个简单的装饰和代码生成器来消除使用"String "和 "int "的值的义务,但这个库的目的正是为了避免外部依赖。我们希望提供一个准备好的编程的环境(路由、依赖和状态的管理),以一种简单、轻量级和高性能的方式,而不需要一个外部包。
你可以在你的pubspec中添加3个字母(get)和一个冒号,然后开始编程。从路由管理到状态管理,所有的解决方案都是默认的,目的是为了方便、高效和高性能。
这个库的总大小还不如一个状态管理器,尽管它是一个完整的解决方案。
如果你被`.value`困扰,喜欢代码生成器,MobX是一个很好的选择,也可以和Get一起使用。对于那些想在pubspec中添加一个单一的依赖,然后开始编程,而不用担心一个包的版本与另一个包不兼容,也不用担心状态更新的错误来自于状态管理器或依赖,还是不想担心控制器的可用性,get都是刚刚好。
如果你对MobX代码生成器或者BLoC模板熟悉,你可以直接用Get来做路由,而忘记它有状态管理器。如果有一个项目有90多个控制器,MobX代码生成器在标准机器上进行Flutter Clean后,需要30多分钟才能完成任务。如果你的项目有5个、10个、15个控制器,任何一个状态管理器都能很好的满足你。如果你的项目大得离谱,代码生成器对你来说是个问题,但你已经获得了Get这个解决方案。
显然,如果有人想为项目做贡献,创建一个代码生成器,或者类似的东西,我将在这个readme中链接,作为一个替代方案,我的需求并不是所有开发者的需求,但现在我要说的是,已经有很好的解决方案,比如MobX。
### Obx()
在Get中使用Bindings的类型是不必要的。你可以使用Obx widget代替GetX,GetX只接收创建widget的匿名函数。
如果你不使用类型,你将需要有一个控制器的实例来使用变量,或者使用`Get.find<Controller>()`.value或Controller.to.value来检索值。
### Workers
Workers将协助你在事件发生时触发特定的回调。
```dart
///每次`count1`变化时调用。
ever(count1, (_) => print("$_ has been changed"));
///只有在变量$_第一次被改变时才会被调用。
once(count1, (_) => print("$_ was changed once"));
///防DDos - 每当用户停止输入1秒时调用,例如。
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
///忽略1秒内的所有变化。
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
所有Workers(除 "debounce "外)都有一个名为 "condition"的参数,它可以是一个 "bool "或一个返回 "bool "的回调。
这个`condition`定义了`callback`函数何时执行。
所有worker都会返回一个`Worker`实例,你可以用它来取消(通过`dispose()`)worker。
- **`ever`**
每当_Rx_变量发出一个新的值时,就会被调用。
- **`everAll`**
和 "ever "很像,但它需要一个_Rx_值的 "List",每次它的变量被改变时都会被调用。就是这样。
- **`once`**
'once'只在变量第一次被改变时被调用。
- **`debounce`**
debounce'在搜索函数中非常有用,你只希望API在用户完成输入时被调用。如果用户输入 "Jonny",你将在API中进行5次搜索,分别是字母J、o、n、n和y。使用Get不会发生这种情况,因为你将有一个 "debounce "Worker,它只会在输入结束时触发。
- **`interval`**
'interval'与debouce不同,debouce如果用户在1秒内对一个变量进行了1000次修改,他将在规定的计时器(默认为800毫秒)后只发送最后一次修改。Interval则会忽略规定时间内的所有用户操作。如果你发送事件1分钟,每秒1000个,那么当用户停止DDOS事件时,debounce将只发送最后一个事件。建议这样做是为了避免滥用,在用户可以快速点击某样东西并获得一些好处的功能中(想象一下,用户点击某样东西可以赚取硬币,如果他在同一分钟内点击300次,他就会有300个硬币,使用间隔,你可以设置时间范围为3秒,无论是点击300次或100万次,1分钟内他最多获得20个硬币)。debounce适用于防DDOS,适用于搜索等功能,每次改变onChange都会调用你的api进行查询。Debounce会等待用户停止输入名称,进行请求。如果在上面提到的投币场景中使用它,用户只会赢得1个硬币,因为只有当用户 "暂停 "到既定时间时,它才会被执行。
- 注意:Worker应该总是在启动Controller或Class时使用,所以应该总是在onInit(推荐)、Class构造函数或StatefulWidget的initState(大多数情况下不推荐这种做法,但应该不会有任何副作用)。
## 简单状态管理器
Get有一个极其轻巧简单的状态管理器,它不使用ChangeNotifier,可以满足特别是对Flutter新手的需求,而且不会给大型应用带来问题。
GetBuilder正是针对多状态控制的。想象一下,你在购物车中添加了30个产品,你点击删除一个,同时List更新了,价格更新了,购物车中的徽章也更新为更小的数字。这种类型的方法使GetBuilder成为杀手锏,因为它将状态分组并一次性改变,而无需为此进行任何 "计算逻辑"。GetBuilder就是考虑到这种情况而创建的,因为对于短暂的状态变化,你可以使用setState,而不需要状态管理器。
这样一来,如果你想要一个单独的控制器,你可以为其分配ID,或者使用GetX。这取决于你,记住你有越多的 "单独 "部件,GetX的性能就越突出,而当有多个状态变化时,GetBuilder的性能应该更优越。
### 优点
1. 只更新需要的小部件。
2. 不使用changeNotifier,状态管理器使用较少的内存(接近0mb)。
3. 忘掉StatefulWidget! 使用Get你永远不会需要它。对于其他的状态管理器,你可能需要使用StatefulWidget来获取你的Provider、BLoC、MobX控制器等的实例。但是你有没有停下来想一想,你的appBar,你的脚手架,以及你的类中的大部分widget都是无状态的?那么如果你只能保存有状态的Widget的状态,为什么要保存整个类的状态呢?Get也解决了这个问题。创建一个无状态类,让一切都成为无状态。如果你需要更新单个组件,就用GetBuilder把它包起来,它的状态就会被维护。
4. 真正的解耦你的项目! 控制器一定不要在你的UI中,把你的TextEditController,或者你使用的任何控制器放在你的Controller类中。
5. 你是否需要触发一个事件来更新一个widget,一旦它被渲染?GetBuilder有一个属性 "initState",就像StatefulWidget一样,你可以从你的控制器中调用事件,直接从控制器中调用,不需要再在你的initState中放置事件。
6. 你是否需要触发一个动作,比如关闭流、定时器等?GetBuilder也有dispose属性,只要该widget被销毁,你就可以调用事件。
7. 仅在必要时使用流。你可以在你的控制器里面正常使用你的StreamControllers,也可以正常使用StreamBuilder,但是请记住,一个流消耗合理的内存,响应式编程很美,但是你不应该滥用它。30个流同时打开会比changeNotifier更糟糕(而且changeNotifier非常糟糕)。
8. 更新widgets而不需要为此花费ram。Get只存储GetBuilder的创建者ID,必要时更新该GetBuilder。get ID存储在内存中的消耗非常低,即使是成千上万的GetBuilders。当你创建一个新的GetBuilder时,你实际上是在共享拥有创建者ID的GetBuilder的状态。不会为每个GetBuilder创建一个新的状态,这为大型应用节省了大量的内存。基本上你的应用程序将是完全无状态的,而少数有状态的Widgets(在GetBuilder内)将有一个单一的状态,因此更新一个状态将更新所有的状态。状态只是一个。
9. Get是全知全能的,在大多数情况下,它很清楚地知道从内存中取出一个控制器的时机,你不需要担心什么时候移除一个控制器,Get知道最佳的时机。
### 用法
```dart
// 创建控制器类并扩展GetxController。
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // 当调用增量时,使用update()来更新用户界面上的计数器变量。
}
}
// 在你的Stateless/Stateful类中,当调用increment时,使用GetBuilder来更新Text。
GetBuilder<Controller>(
init: Controller(), // 首次启动
builder: (_) => Text(
'${_.counter}',
),
)
//只在第一次时初始化你的控制器。第二次使用ReBuilder时,不要再使用同一控制器。一旦将控制器标记为 "init "的部件部署完毕,你的控制器将自动从内存中移除。你不必担心这个问题,Get会自动做到这一点,只是要确保你不要两次启动同一个控制器。
```
**完成!**
- 你已经学会了如何使用Get管理状态。
- 注意:你可能想要一个更大的规模,而不是使用init属性。为此,你可以创建一个类并扩展Bindings类,并在其中添加将在该路由中创建的控制器。控制器不会在那个时候被创建,相反,这只是一个声明,这样你第一次使用Controller时,Get就会知道去哪里找。Get会保持懒加载,当不再需要Controller时,会自动移除它们。请看pub.dev的例子来了解它是如何工作的。
如果你导航了很多路由,并且需要之前使用的Controller中的数据,你只需要再用一次GetBuilder(没有init)。
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
如果你需要在许多其他地方使用你的控制器,并且在GetBuilder之外,只需在你的控制器中创建一个get,就可以轻松地拥有它。(或者使用`Get.find<Controller>()`)
```dart
class Controller extends GetxController {
/// 你不需要这个,我推荐使用它只是为了方便语法。
/// 用静态方法:Controller.to.counter()。
/// 没有静态方法的情况下:Get.find<Controller>().counter();
/// 使用这两种语法在性能上没有区别,也没有任何副作用。一个不需要类型,另一个IDE会自动完成。
static Controller get to => Get.find(); // 添加这一行
int counter = 0;
void increment() {
counter++;
update();
}
}
```
然后你可以直接访问你的控制器,这样:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // 是不是贼简单!
child: Text("${Controller.to.counter}"),
),
```
当你按下FloatingActionButton时,所有监听'counter'变量的widget都会自动更新。
### 如何处理controller
比方说,我们有这样的情况。
`Class a => Class B (has controller X) => Class C (has controller X)`
在A类中,控制器还没有进入内存,因为你还没有使用它(Get是懒加载)。在类B中,你使用了控制器,并且它进入了内存。在C类中,你使用了与B类相同的控制器,Get会将控制器B的状态与控制器C共享,同一个控制器还在内存中。如果你关闭C屏和B屏,Get会自动将控制器X从内存中移除,释放资源,因为a类没有使用该控制器。如果你再次导航到B,控制器X将再次进入内存,如果你没有去C类,而是再次回到a类,Get将以同样的方式将控制器从内存中移除。如果类C没有使用控制器,你把类B从内存中移除,就没有类在使用控制器X,同样也会被处理掉。唯一能让Get乱了阵脚的例外情况,是如果你意外地从路由中删除了B,并试图使用C中的控制器,在这种情况下,B中的控制器的创建者ID被删除了,Get被设计为从内存中删除每一个没有创建者ID的控制器。如果你打算这样做,在B类的GetBuilder中添加 "autoRemove: false "标志,并在C类的GetBuilder中使用adopID = true;
### 无需StatefulWidgets
使用StatefulWidgets意味着不必要地存储整个界面的状态,甚至因为如果你需要最小化地重建一个widget,你会把它嵌入一个Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx中,这将是另一个StatefulWidget。
StatefulWidget类是一个比StatelessWidget大的类,它将分配更多的RAM,只使用一两个类之间可能不会有明显的区别,但当你有100个类时,它肯定会有区别!
除非你需要使用混合器,比如TickerProviderStateMixin,否则完全没有必要使用StatefulWidget与Get。
你可以直接从GetBuilder中调用StatefulWidget的所有方法。
例如,如果你需要调用initState()或dispose()方法,你可以直接调用它们。
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
比这更好的方法是直接从控制器中使用onInit()和onClose()方法。
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- 注意:如果你想在控制器第一次被调用的那一刻启动一个方法,你不需要为此使用构造函数,使用像Get这样面向性能的包,这样做反而是糟糕的做法,因为它偏离了控制器被创建或分配的逻辑(如果你创建了这个控制器的实例,构造函数会立即被调用,你会在控制器还没有被使用之前就填充了一个控制器,你在没有被使用的情况下就分配了内存,这绝对违背这个库的原则)。onInit();和onClose();方法就是为此而创建的,它们会在Controller被创建,或者第一次使用时被调用,这取决于你是否使用Get.lazyPut。例如,如果你想调用你的API来填充数据,你可以忘掉老式的initState/dispose方法,只需在onInit中开始调用api,如果你需要执行任何命令,如关闭流,使用onClose()来实现。
### 为什么它存在
这个包的目的正是为了给你提供一个完整的解决方案,用于导航路线,管理依赖和状态,使用尽可能少的依赖,高度解耦。Get将所有高低级别的Flutter API都纳入自身,以确保你在工作中尽可能减少耦合。我们将所有的东西集中在一个包中,以确保你在你的项目中没有任何形式的耦合。这样一来,你就可以只在视图中放置小组件,而让你的团队中负责业务逻辑的那部分人自由地工作,不需要依赖视图中的任何元素来处理业务逻辑。这样就提供了一个更加干净的工作环境,这样你的团队中的一部分人只用widget工作,而不用担心将数据发送到你的controller,你的团队中的一部分人只用业务逻辑工作,而不依赖于视图的任何元素。
所以为了简化这个问题。
你不需要在initState中调用方法,然后通过参数发送给你的控制器,也不需要使用你的控制器构造函数,你有onInit()方法,在合适的时间被调用,以启动你的服务。
你不需要调用设备,你有onClose()方法,它将在确切的时刻被调用,当你的控制器不再需要时,将从内存中删除。这样一来,只给widget留下视图,不要从中进行任何形式的业务逻辑。
不要在GetxController里面调用dispose方法,它不会有任何作用,记住控制器不是Widget,你不应该 "dispose "它,它会被Get自动智能地从内存中删除。如果你在上面使用了任何流,想关闭它,只要把它插入到close方法中就可以了。例如
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
///关闭流用onClose方法,而不是dispose
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
控制器的生命周期。
- onInit()是创建控制器的地方。
- onClose(),关闭控制器,为删除方法做准备。
- deleted: 你不能访问这个API,因为它实际上是将控制器从内存中删除。它真的被删除了,不留任何痕迹。
### 其他使用方法
你可以直接在GetBuilder值上使用Controller实例。
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
你可能还需要在GetBuilder之外的控制器实例,你可以使用这些方法来实现。
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
//view
GetBuilder<Controller>(
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
或者
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
- 你可以使用 "非规范 "的方法来做这件事。如果你正在使用一些其他的依赖管理器,比如get_it、modular等,并且只想交付控制器实例,你可以这样做。
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### 唯一的ID
如果你想用GetBuilder完善一个widget的更新控件,你可以给它们分配唯一的ID。
```dart
GetBuilder<Controller>(
id: 'text' 、、这里
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
并更新它:
```dart
update(['text']);
```
您还可以为更新设置条件。
```dart
update(['text'], counter < 10);
```
GetX会自动进行重建,并且只重建使用被更改的变量的小组件,如果您将一个变量更改为与之前相同的变量,并且不意味着状态的更改,GetX不会重建小组件以节省内存和CPU周期(界面上正在显示3,而您再次将变量更改为3。在大多数状态管理器中,这将导致一个新的重建,但在GetX中,如果事实上他的状态已经改变,那么widget将只被再次重建)
## 与其他状态管理器混用
有人开了一个功能请求,因为他们只想使用一种类型的响应式变量,而其他的则手动去更新,需要为此在GetBuilder中插入一个Obx。思来想去,MixinBuilder应运而生。它既可以通过改变".obs "变量进行响应式改变,也可以通过update()进行手动更新。然而,在4个widget中,他是消耗资源最多的一个,因为除了有一个Subscription来接收来自他的子代的变化事件外,他还订阅了他的控制器的update方法。
扩展GetxController是很重要的,因为它们有生命周期,可以在它们的onInit()和onClose()方法中 "开始 "和 "结束 "事件。你可以使用任何类来实现这一点,但我强烈建议你使用GetxController类来放置你的变量,无论它们是否是可观察的。
## GetBuilder vs GetX vs Obx vs MixinBuilder
在十年的编程工作中,我能够学到一些宝贵的经验。
我第一次接触到响应式编程的时候,是那么的 "哇,这太不可思议了",事实上响应式编程是不可思议的。
但是,它并不适合所有情况。通常情况下,你需要的是同时改变2、3个widget的状态,或者是短暂的状态变化,这种情况下,响应式编程不是不好,而是不适合。
响应式编程对RAM的消耗比较大,可以通过单独的工作流来弥补,这样可以保证只有一个widget被重建,而且是在必要的时候,但是创建一个有80个对象的List,每个对象都有几个流,这不是一个好的想法。打开dart inspect,查看一个StreamBuilder的消耗量,你就会明白我想告诉你什么。
考虑到这一点,我创建了简单的状态管理器。它很简单,这正是你应该对它提出的要求:以一种简单的方式,并且以最高效的方式更新块中的状态。
GetBuilder在RAM中是非常高效的,几乎没有比他更高效的方法(至少我无法想象,如果存在,请告诉我们)。
然而,GetBuilder仍然是一个手动的状态管理器,你需要调用update(),就像你需要调用Provider的notifyListeners()一样。
还有一些情况下,响应式编程真的很有趣,不使用它就等于重新发明轮子。考虑到这一点,GetX的创建是为了提供状态管理器中最现代和先进的一切。它只在必要的时候更新必要的东西,如果你出现了错误,同时发送了300个状态变化,GetX只在状态真正发生变化时才会过滤并更新界面。
GetX比其他响应式状态管理器还是比较高效的,但它比GetBuilder多消耗一点内存。思前想后,以最大限度地消耗资源为目标,Obx应运而生。与GetX和GetBuilder不同的是,你将无法在Obx内部初始化一个控制器,它只是一个带有StreamSubscription的Widget,接收来自你的子代的变化事件,仅此而已。它比GetX更高效,但输给了GetBuilder,这是意料之中的,因为它是响应式的,而且GetBuilder有最简单的方法,即存储widget的hashcode和它的StateSetter。使用Obx,你不需要写你的控制器类型,你可以从多个不同的控制器中监听到变化,但它需要在之前进行初始化,或者使用本readme开头的示例方法,或者使用Bindings类。
... ...