first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
# #依赖管理
- [依赖管理](#依赖管理)
- [实例方法](#实例方法)
- [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来更好地组织。

View File

@@ -0,0 +1,556 @@
- [路由管理](#路由管理)
- [如何使用](#如何使用)
- [普通路由导航](#普通路由导航)
- [别名路由导航](#别名路由导航)
- [发送数据到别名路由](#发送数据到别名路由)
- [动态网页链接](#动态网页链接)
- [中间件](#中间件)
- [免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
),
],
)
);
}
```
要处理到未定义路线的导航404错误可以在GetMaterialApp中定义unknownRoute页面。
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### 发送数据到别名路由
只要发送你想要的参数即可。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
```
或像这样发送多个参数
```dart
Get.toNamed("/profile/34954?flag=true");
```
在第二个屏幕上,通常按参数获取数据
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
```
现在你需要做的就是使用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: ElevatedButton(
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: ElevatedButton(
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: ElevatedButton(
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,
// TextButton 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: TextButton(
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")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,714 @@
- [状态管理](#状态管理)
- [响应式状态管理器](#响应式状态管理器)
- [优点](#优势)
- [声明一个响应式变量](#声明一个响应式变量)
- [使用视图中的值](#使用视图中的值)
- [什么时候重建](#什么时候重建)
- [可以使用.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变量时。
user.update( (user) { // 这个参数是你要更新的类本身。
user.name = 'Jonny';
user.age = 18;
});
// 更新user变量的另一种方式。
user(User(name: 'João', age: 35));
// view
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"));
// 你也可以不使用.value来访问模型值。
user().name; // 注意是user变量而不是类变量首字母是小写的
```
你可以使用 "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代替GetXGetX只接收创建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.increment()。
/// 没有静态方法的情况下Get.find<Controller>().increment();
/// 使用这两种语法在性能上没有区别也没有任何副作用。一个不需要类型另一个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类。