Fluttet getx状态管理
1.Get介绍
Bloc问题:使用Bloc的时候,有一个让我至今为止十分在意的问题,无法真正的跨页面交互!在反复的查阅官方文档后,使用一个全局Bloc的方式,实现了“伪”跨页面交互。fishRedux,在中小型项目中使用时,会降低一定开发效率,但是在大型项目中,是非常好的
Getx优势:
- 路由管理
- build刷新方式
- 跨页面交互,在StreamController篇介绍过,当时是通过它实现的
- 实现了全局BuildContext
- 国际化,主题实现
GetMaterialApp 对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文(context)的情况下是起重要作用的,它只是一个Widget,它的子组件是默认的MaterialApp。
路由管理
1.依赖
get: ^4.1.4
导包
import 'package:get/get.dart';
2.使用
在MaterialApp之前添加“获取”,将其转换为GetMaterialApp
- 注意:这不会修改Flutter的MaterialApp,GetMaterialApp并不是经过修改的MaterialApp,它只是一个预配置的小部件,其默认值为MaterialApp作为子级。您可以手动配置,但是绝对没有必要。GetMaterialApp将创建路线,注入路线,注入翻译,注入路线导航所需的一切。如果将“获取”仅用于状态管理或依赖项管理,则不必使用GetMaterialApp。GetMaterialApp对于路线,小吃店,国际化,bottomSheets,对话框以及与路线和缺少上下文相关的高级api是必需的。
- 注意²:仅当您要使用路由管理(
Get.to()
,Get.back()
依此类推)时,才需要执行此步骤。如果您不使用它,则无需执行步骤1
1.动态路由Get.to
Get.to(TestAPage());
2.替换路由replace
Get.off(NextScreen());
3.返回 Navigator.pop(context
Get.back();
4.返回根,取消前面所有路线
Get.offAll(NextScreen());
5.静态路由
Get.toNamed('/details');
6.发送前一个路由的数据
Get.back(result: 'success');
7.Get提供高级动态URL
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
8.在系统Dialog上跳转页面,可以这样写
Get.to(XxxxPage(), preventDuplicates: false);
// 或者
Get.toNamed('xxx', preventDuplicates: false);
9.传参
Get.toNamed("/home/list/detail", arguments: {"id": 999})');
场景
A->B,B传参回A,A获取值,类似于Android中startActivityForResult
//动态 路由的方式
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext context) {
return new TestA();
},
),
).then((value) {
//获取上一个页面的数据
});
//Gex 动态的方式 获取数据
void fun5() async {
//动态 态的方式 并获取A页面的返回值
var value = await Get.to(new TestA());
print("A页面的返回值 $value");
}
退出登录时,关闭所有的页面,然后打开登录页面
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(
builder: (BuildContext context) {
return new TestAPage();
},
),
(Route route) => false,
);
Get.offAll(new TestAPage());
代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'MyHomePage.dart';
import 'TestAPage.dart';
//程序入口
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//使用 GetX第一步
return GetMaterialApp(
theme: ThemeData(
primarySwatch: Colors.yellow,
highlightColor: Color.fromRGBO(255, 255, 255, 0.5),
splashColor: Colors.blue),
//静态路径
routes: {
"/testa": (context) => TestAPage(),
},
//默认显示的首页页面
home: MyHomePage(),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class TestAPage extends StatefulWidget {
TestAPage({Key key}) : super(key: key);
@override
_TestAPageState createState() {
return _TestAPageState();
}
}
class _TestAPageState extends State<TestAPage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
color: Colors.red,
child: OutlinedButton(
onPressed: (){
Get.back();
},
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: OutlinedButton(
onPressed: (){
Get.toNamed("/testa");
},
),
),
),
);
}
}
3.实现Provider类似的功能
注意:使用Getx之后,基本不需要使用StatelessWidget,提出疑问?那是widget是怎么改变状态的了,怎么点击事情,onPress
答:StatelessWidget仅仅只是代表自身无状态,无setState,不能更改自身的widget,但是不代表子widget没有自身的状态,他们如果自身是statefulWidget,
则说明,子widget可以改变自身的状态,这也是一种实现局部刷新的方式
class StateTest extends StatelessWidget {
StateTest({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: OutlinedButton(
onPressed: (){
print("StateTest");
},
),
),
);
}
}
点击时:onPressed会被执行,但是StateTest自身widget不能被改变,无法执行自身的setStatus
1.被观察的属性成员,先申请为被观察者
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';
class CountController extends GetxController{
//声明为被观察者
var _count = 0.obs;
var _myPerson = PersonMy('zhangsan', 20).obs;
RxInt get getCount => _count;
void addCount() {
_count++;
}
}
2.GetX创建GetxController对象,并被动监听
GetX<CountController>(
//初始化控制器
init: CountController(),
//监听回调
builder: (CountController controller) {
return Text("当前 count 的值为 ${controller.getCount}");
},
),
3.观察者自动更新,并被动监听
Obx(() {
return Text(
"Obx 当前 count 的值为 ${Get.find<CountController>().getCount}");
}),
——————这种更新都是局部更新,实现了Provider中Selector的功能
注意:使用依赖管理时,应该使用Get.put
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'CountObsMainPage.dart';
//程序入口
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return GetMaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
//默认显示的首页页面
home: CountObsMainPage(),
);
}
}
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';
///第一步定义 Controller
class CountController extends GetxController {
//声明为被观察者
var _count = 0.obs;
var _age = 0.obs;
get age => _age;
var _myPerson = PersonMy('zhangsan', 20).obs;
RxInt get getCount => _count;
//操作方法
void addCount() {
_count++;
}
void addAge() {
_age++;
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'CountController.dart';
class CountObsMainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Gex 响应编程"),
),
backgroundColor: Colors.white,
///填充布局
body: Container(
padding: EdgeInsets.all(30),
width: double.infinity,
height: double.infinity,
child: Column(
children: [
GetX<CountController>(
//初始化控制器
init: Get.put(CountController()),
//监听回调
builder: (CountController controller) {
return Text("当前 count 的值为 ${controller.getCount}");
},
),
//观察者自动更新
Obx(() {
return Text(
"Obx 当前 count 的值为 ${Get.find<CountController>().getCount}");
}),
Obx(() {
return Text("Obx2 ${Get.find<CountController>().age}");
})
],
)),
//点击按钮修改值
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Get.find<CountController>().addCount();
},
),
);
}
}
4.GetX小操作
当前页面
final Controller c = Get.put(Controller());
则,可以通过Get找到一个正在被其他页面使用的Controller,并将它返回给你。
final Controller c = Get.find();
5.使用过程注意
-
GetxController 无需自行手动释放,原因 :
在Getx中,Getx继承StatefulWidget,执行了GetInstance().delete(tag: widget.tag);
@override void dispose() { if (widget.dispose != null) widget.dispose!(this); if (_isCreator! || widget.assignId) { if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) { GetInstance().delete<T>(tag: widget.tag); } } _subs.cancel(); _observer!.close(); controller = null; _isCreator = null; super.dispose(); }
-
如果使用到Getx的obs,则全部整体需要使用GetX.to ,toNamed那一套,否则可能会出错,原因
通过上面会在GetPage注册可知,说明在我们跳转页面的时候,GetX会拿你到页面信息存储起来,加以管理,下面俩种场景会导致GetxController无法释放
- 未使用GetX提供的路由跳转:直接使用原生路由api的跳转操作
- 这样会直接导致GetX无法感知对应页面GetxController的生命周期,会导致其无法释放
- 在GetPage注册页面,不使用Get.toName,这样无法释放;GetPage+Get.toName配套使用可释放
- 直接使用Get.to,可释放
4.跨页面通信,实现StreamController
A页面和B页面能找到相同的GetxController,然后B页面在GetxController更新数据,GetxController则通过发布订阅模式,将数据发送到A页面,起始本质与StreamController相同
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'CountObsMainPage01.dart';
//程序入口
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return GetMaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
//默认显示的首页页面
home: CountObsMainPage01(),
);
}
}
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';
///第一步定义 Controller
class CountController01 extends GetxController {
//声明为被观察者
var _count = 0.obs;
var _age = 0.obs;
get age => _age;
var _myPerson = PersonMy('zhangsan', 20).obs;
RxInt get getCount => _count;
//操作方法
void addCount() {
_count++;
}
void addAge() {
_age++;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_first/day54Getx/day3/CountObsMainPage02.dart';
import 'package:get/get.dart';
import 'CountController01.dart';
class CountObsMainPage01 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Gex 响应编程"),
),
backgroundColor: Colors.white,
///填充布局
body: Container(
padding: EdgeInsets.all(30),
width: double.infinity,
height: double.infinity,
child: Column(
children: [
GetX<CountController01>(
//初始化控制器
init: Get.put(CountController01()),
//监听回调
builder: (CountController01 controller) {
return Text("当前 count 的值为 ${controller.getCount}");
},
),
//观察者自动更新
Obx(() {
return Text(
"Obx 当前 count 的值为 ${Get.find<CountController01>().getCount}");
}),
Obx(() {
return Text("Obx2 ${Get.find<CountController01>().age}");
}),
OutlinedButton(
onPressed: () {
Get.to(CountObsMainPage02());
},
child: Text('进入2页面'))
],
)),
//点击按钮修改值
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Get.find<CountController01>().addCount();
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'CountController01.dart';
class CountObsMainPage02 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Gex 响应编程"),
),
backgroundColor: Colors.white,
///填充布局
body: Container(
padding: EdgeInsets.all(30),
width: double.infinity,
height: double.infinity,
child: Column(
children: [
//观察者自动更新
Obx(() {
return Text(
"Obx 当前 count 的值为 ${Get.find<CountController01>().getCount()}");
}),
OutlinedButton(
onPressed: () {
Get.find<CountController01>().addCount();
},
child: Text("点击"))
],
)),
//点击按钮修改值
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Get.find<CountController01>().addCount();
},
),
);
}
}
——————B页面点击时,A页面也会发生改变
5.GetBuilder
可以提升性能,但不太常用,主动观察,类似于Provider中notifyListeners,主动通知
通过GetBuilder实现响应式控件,控制器必须继承自GetxController,所定义的目标状态变量之后无需后缀".obs ",但需要定义方法update;并且加载指定控制器,不仅需要使用Get.put进行注入,而且GetBuilder还需要通过指定泛型绑定目标注入的控制器。
GetBuilder<CountController>(
builder: (CountController controller) {
return Text("当前Height值${controller.height}");
},
),
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';
///第一步定义 Controller
class CountController extends GetxController {
//声明为被观察者
var _count = 0.obs;
var _age = 0.obs;
var _height = 0;
get height => _height;
get age => _age;
var _myPerson = PersonMy('zhangsan', 20).obs;
RxInt get getCount => _count;
get controller => Get.find<CountController>();
//操作方法
void addCount() {
_count++;
}
void addAge() {
_age++;
}
void addHeight() {
_height++;
update();
}
}
唯一的ID
如果你想用GetBuilder完善一个widget的更新控件,你可以给它们分配唯一的ID。不太常用
GetBuilder<Controller>(
id: 'text' 、、这里
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
对比Getx vs obs vs GetBuilder
GetX比其他响应式状态管理器还是比较高效的,但它比GetBuilder多消耗一点内存。思前想后,以最大限度地消耗资源为目标,Obx应运而生。与GetX和GetBuilder不同的是,你将无法在Obx内部初始化一个控制器,它只是一个带有StreamSubscription的Widget,接收来自你的子代的变化事件,仅此而已。它比GetX更高效,但输给了GetBuilder,这是意料之中的,因为它是响应式的,而且GetBuilder有最简单的方法,即存储widget的hashcode和它的StateSetter。使用Obx,你不需要写你的控制器类型,你可以从多个不同的控制器中监听到变化,但它需要在之前进行初始化,或者使用本readme开头的示例方法,或者使用Bindings类。
6.workder的使用
///每次`count1`变化时调用。
ever(_count, (callback) {
print("$callback has been changed");
});
///只有在变量$_第一次被改变时才会被调用。
once(_age, (callback) => print("$callback was changed once"));
///防DDos - 每当用户停止输入1秒时调用,debounce将只发送最后一个事件。
debounce(_count, (callback) => print("debouce$callback"), time: Duration(seconds: 3));
///忽略1秒内的所有变化。
interval(_age, (callback) => print("interval $callback"), time: Duration(seconds: 1));
——————个人觉得debounce最有用,用于搜索时,当连续输入时,只用最后一次的进行网络请求搜索
7.依赖注入方式
这样,不影响其他的状态管理器
class Controller extends GetxController {
StreamController<PersonMy> user = StreamController<PersonMy>();
StreamController<String> name = StreamController<String>();
@override
void onInit() {
super.onInit();
}
///关闭流用onClose方法,而不是dispose
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
8.GetView
这个挺有用的,封装了StatelessWidget,方便简洁使用,获取controller
abstract class GetView<T> extends StatelessWidget {
const GetView({Key? key}) : super(key: key);
final String? tag = null;
T get controller => GetInstance().find<T>(tag: tag)!;
@override
Widget build(BuildContext context);
}
9.依赖管理
1. Get.put()
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
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,
)
——————————tag的使用场景,同一个Controller时,可以使用,区别开来
——————————permanent : 生命周期,在整个应用中
2.Get.lazyPut
可以懒加载一个依赖,这样它只有在使用时才会被实例化。 在Get.find时才会被调用
3.Get.create
————————创建新的Controller, 使用不多
4.GetxService
但里面没有 “逻辑”。它只是通知GetX的依赖注入系统,这个子类不能从内存中删除。
所以这对保持你的 "服务 "总是可以被Get.find()
获取到并保持运行是超级有用的。比如
ApiService
,StorageService
,CacheService
。
4.Bindings 重点,依赖注入
Bindings,常与Get.lazyPut/GetView一起使用,达到简洁的目的,这样可以不必要在Widget中写入Controller
1.定义Controller
import 'package:get/get.dart';
class HomeController extends GetxController {
var _count = 0.obs;
var _age = 0.obs;
get count => _count;
set count(value) {
_count = value;
} //操作方法
void addCount() {
_count++;
}
void addAge() {
_age++;
}
get age => _age;
set age(value) {
_age = value;
}
}
2.定义HomeBinding
import 'package:get/get.dart';
import 'HomeController.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
3.定义GetView
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'HomeController.dart';
class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.linearToSrgbGamma(),
image: NetworkImage(
"https://images.pexels.com/photos/3902882/pexels-photo-3902882.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"),
),
),
child: Scaffold(
appBar: AppBar(
title: Text("Gex 响应编程"),
),
backgroundColor: Colors.white,
body: buildWidget(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
controller.addCount();
controller.addAge();
},
),
),
);
}
Widget buildWidget() {
return Container(
padding: EdgeInsets.all(30),
width: double.infinity,
height: double.infinity,
child: Column(
children: [
GetBuilder<HomeController>(
builder: (HomeController controller) {
return Text("当前Height值${controller.count}");
},
),
GetX<HomeController>(
//监听回调
builder: (HomeController controller) {
return Text("当前 count 的值为 ${controller.count}");
},
),
//观察者自动更新
Obx(() {
return Text("Obx 当前 count 的值为 ${controller.age}");
}),
Obx(() {
return Text("Obx2 ${controller.age}");
})
],
));
}
}
4.将GetMaterialApp设置为widget视图顶端
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter/cupertino.dart';
import 'AppPages.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return GetMaterialApp(
initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
);
}
}
5.路由管理
import 'package:flutter_first/day54Getx/day4/routes/app_pages.dart';
import 'package:get/get.dart';
import 'HomeBinding.dart';
import 'HomeView.dart';
class AppPages {
static const INITIAL = Routes.HOME;
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
// children: [
// GetPage(
// name: Routes.COUNTRY,
// page: () => CountryView(),
// children: [
// GetPage(
// name: Routes.DETAILS,
// page: () => DetailsView(),
// ),
// ],
// ),
// ]
),
];
}
6.route
part of 'app_pages.dart';
abstract class Routes {
static const HOME = '/home';
static const COUNTRY = '/country';
static const DETAILS = '/details';
}
6.argument传值
import 'package:flutter_first/day54Getx/day4/routes/app_pages.dart';
import 'package:get/get.dart';
import 'CountryView.dart';
import 'HomeBinding.dart';
import 'HomeView.dart';
class AppPages {
static const INITIAL = Routes.HOME;
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
children: [
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
),
]
),
];
}
HomeView:
OutlinedButton(
onPressed: () {
Get.toNamed('/home/country',
arguments: {'name': 'zhangsan', 'age': 40});
},
child: Text('点击'))
countryView
final country = Get.arguments as Map; //获取传输值