Flutter之状态管理(二) GetX框架

为什么要状态管理?

这是因为 Flutter 基于 声明式 构建 UI ,使用状态管理的目的之一就是解决「声明式」开发带来的问题。

「声明式」开发带来的问题?

  1. 逻辑和页面 UI 耦合,导致无法复用/单元测试、修改混乱等
  2. 难以跨组件 (跨页面) 访问数据
  3. 无法轻松的控制刷新范围 (页面 setState 的变化会导致全局页面的变化)

状态管理如何解决?

解决逻辑和页面UI耦合问题

 View 中的逻辑代码抽离到 Presenter 层, View 只负责视图的构建。

解决难以跨组件 (跨页面) 访问数据的问题

Provider

  • 依赖树机制,必须基于 context
  • 提供了子组件访问上层的能力

Get

  • 全局单例,任意位置可以存取
  • 存在类型重复,内存回收问题

解决高层级 setState 引起不必要刷新的问题

Flutter 通过采用观察者模式解决,其关键在于两步:

  1. 观察者去订阅被观察的对象;
  2. 被观察的对象通知观察者。

Provider

提供了 ChangeNotifierProvider

Get

只需要提前调用 Get.put 方法存储 Counter 对象,为 GetBuilder 组件指定 Counter 作为泛型。因为 Get 基于单例,所以 GetBuilder 可以直接通过泛型获取到存入的对象,并在 builder 方法中暴露。这样 Counter 便与组件建立了监听关系,之后 Counter 的变动,只会驱动以它作为泛型的 GetBuilder 组件更新。

Flutter之状态管理(一) Provider_YUFENGSHI.LJ的博客-优快云博客

GetX

  • 状态管理

简单状态管理器GetBuilder

class GetBuilder<T extends GetxController> extends StatefulWidget {
  final GetControllerBuilder<T> builder;
  final bool global;
  final Object? id;
  final String? tag;
  final bool autoRemove;
  final bool assignId;
  final Object Function(T value)? filter;
  final void Function(GetBuilderState<T> state)? initState,
      dispose,
      didChangeDependencies;
  final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
      didUpdateWidget;
  final T? init;
.............}
  • GetBuilder<T>,T是传入的业务类
  • builder是一个可选参数里的必选参数,要求传入一个回调函数,这个回调函数传入的参数就是GetBuilder泛型类的实例 

在GetController中,当变量发生改变时,使用update()来更新变量值。

在你的Stateless/Stateful类中,当调用increment时,使用GetBuilder来更新Text。

class pageTwoController extends GetxController {

  
  List<String> content = ["111", "222", "333"];

  String text = "pageTwo的controller";
  var data = "";

  
  void change() {
    data = content[Random().nextInt(content.length)];
    update();
  }
}
 GetBuilder<pageTwoController>(builder: (logic) {
                return Text("${controler.data}");
              }),

响应式状态管理器GetX

声明一个响应式变量

1 - 第一种是使用 Rx{Type}

// 建议使用初始值,但不是强制性的
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});

2 - 第二种是使用 Rx,规定泛型 Rx<Type>

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的属性。

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;
更新值 

而在UI中,当你想显示该值并在值变化时更新页面,只需在使用了观察对象的widget外面包裹一个Obx()

Obx(() => Text("${controller.name}"));

 

class pageTwoController extends GetxController {

  RxInt i = 0.obs;

  String text = "pageTwo的controller";
  var data = "";

  @override
  void onInit() {
    data = Get.arguments;
    update();
  }

  void caculate(isadd) {
    if (isadd) {
      i++;
    }
    else {
      i--;
    }
  }
}
Obx(() {
 return Text("${controler.i}");}),
Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
        ElevatedButton(
            onPressed: () {
                controler.caculate(true);
                      },
            child: Icon(Icons.add)),
        SizedBox(width: 20,),
        ElevatedButton(
            onPressed: () {
                controler.caculate(false);
                      },
            child: Icon(Icons.remove)),
                ],

 

  • 路由管理

普通路由导航

导航到新的页面。

Get.to(NextScreen());

关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。

Get.back();

进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)。

Get.off(NextScreen());

进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。

Get.offAll(NextScreen());

要导航到下一条路由,并在返回后立即接收或更新数据。

var data = await Get.to(Payment());

在另一个页面上,发送前一个路由的数据。

Get.back(result: 'success');

并使用它,例:

if(data == 'success') madeAnything();

别名路由导航

导航到下一个页面

Get.toNamed("/NextScreen");

浏览并删除前一个页面。

Get.offNamed("/NextScreen");

浏览并删除所有以前的页面。

Get.offAllNamed("/NextScreen");

要定义路由,使用GetMaterialApp。

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页面。

void main() {
  runApp(
    GetMaterialApp(
      unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => MyHomePage()),
        GetPage(name: '/second', page: () => Second()),
      ],
    )
  );
}

发送数据到别名路由

只要发送你想要的参数即可。Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。

Get.toNamed("/NextScreen", arguments: 'Get is the best');

在你的类或控制器上:

print(Get.arguments);
//print out: Get is the best

示例: 

class pageOne extends StatelessWidget {
  var controler=Get.find<pageOneController>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Page_one"),),
      body: Container(
        color: Colors.red,
        child: Column(
          children: [
            Text("${controler.text}"),
            ElevatedButton(
              onPressed: (){
                Get.toNamed('/page_two',arguments: "这是从pageone传过来的数据");
              },
              child: Text("page_one"),
            ),
          ],
        ),
      ),
    );
  }
}
class pageTwoController extends GetxController {

  String text="pageTwo的controller";
  var data="";
  @override
  void onInit() {
    data=Get.arguments;
    update();
  }
}

动态网页链接

Get提供高级动态URL,就像在Web上一样。Web开发者可能已经在Flutter上想要这个功能了,Get也解决了这个问题。

Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");

在你的controller/bloc/stateful/stateless类上:

print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo

你也可以用Get轻松接收NamedParameters。

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  
      ),
     ],
    )
  );
}

发送别名路由数据

Get.toNamed("/second/34954");

在第二个页面上,通过参数获取数据

print(Get.parameters['user']);
// out: 34954

或像这样发送多个参数

Get.toNamed("/profile/34954?flag=true");

在第二个屏幕上,通常按参数获取数据

print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
  • 依赖管理

插入依赖的方式

Get.put()
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()

可以懒加载一个依赖,这样它只有在使用时才会被实例化。

Get.lazyPut<S>(
  // 强制性:当你的类第一次被调用时,将被执行的方法。
  InstanceBuilderCallback builder,
  
  // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
  // 必须是唯一的
  String tag,

  // 可选:类似于 "永久",
  // 不同的是,当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例,
  // 就像 bindings api 中的 "SmartManagement.keepFactory "一样。
  // 默认值为false
  bool fenix = false
  
)
Get.putAsync()

如果你想注册一个异步实例,你可以使用Get.putAsync

Get.putAsync<SharedPreferences>(() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setInt('counter', 12345);
  return prefs;
});

Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )

这都是你在使用putAsync时可以设置的选项。

Get.putAsync<S>(

  // 必备:一个将被执行的异步方法,用于实例化你的类。
  AsyncInstanceBuilderCallback<S> builder,

  // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
  // 必须是唯一的
  String tag,

  // 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
  // 默认值为false
  bool permanent = false
)

使用依赖的方法

final controller = Get.find<Controller>();
// 或者
Controller controller = Get.find();

// 是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。
// 你可以实例化100万个控制器,Get总会给你正确的控制器。

Bindings

Binding类是一个将解耦依赖注入的类,同时 "Bindings "路由到状态管理器和依赖管理器。 这使得Get可以知道当使用某个控制器时,哪个页面正在显示,并知道在哪里以及如何销毁它。

  • 创建一个类并实现Binding

class HomeBinding implements Bindings {}

 你的IDE会自动要求你重写 "dependencies"方法,然后插入你要在该路由上使用的所有类。

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());
  }
}
  • 使用别名路由:
getPages: [
  GetPage(
    name: '/',
    page: () => HomeView(),
    binding: HomeBinding(),
  ),
  GetPage(
    name: '/details',
    page: () => DetailsView(),
    binding: DetailsBinding(),
  ),
];
  • 使用正常路由
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())

Binding类在调用路由时被调用,你可以在你的GetMaterialApp中创建一个 "initialBinding "来插入所有将要创建的依赖关系。

GetMaterialApp(
  initialBinding: SampleBind(),
  home: Home(),
);
class GetXpage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "Flutter Demo",
      debugShowCheckedModeBanner: false,
      initialBinding: bind1(),
      getPages: getRouters.getpages,
      home: HYHomePage(),
    );
  }
}
class HYHomePage extends StatelessWidget {
  const HYHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX"),
      ),
      body: Center(
        child:Container(
          child:ElevatedButton(
            onPressed: (){
              Get.toNamed('/page_one');
            },
            child: Text("page_one"),
          )
        ),
      ),
    );
  }
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';

class pageOne extends StatelessWidget {
  var controler=Get.find<pageOneController>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Page_one"),),
      body: Container(
        color: Colors.red,
        child: Text("${controler.text}"),
      ),
    );
  }
}

class pageOneController extends GetxController {

  String text="pageone的controller";

}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';

class pageTwo extends StatelessWidget {
  var controler=Get.find<pageTwoController>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Page_two"),),
      body: Container(
        color: Colors.red,
        child: Text("${controler.text}"),
      ),
    );
  }
}

class pageTwoController extends GetxController {

  String text="pageTwo的controller";

}

参考资料:

Flutter 状态管理框架 Provider 和 Get 分析 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 

Getx 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值