【flutter篇】Getx

899 阅读4分钟

Getx官网文档:github.com/jonataslaw/…

1. 介绍

Flutter GetX 是一个功能强大且轻量的 Flutter 状态管理解决方案,同时集成了路由管理、依赖注入、响应式编程等功能,适合中小型到中大型项目快速开发。

2. 为什么使用Getx,而不是其它的

① Flutter 中更新 UI,是需要更新状态来刷新布局,编程逻辑与 C# 或 Java 会有所不同,但幸运的是有很多状态管理框架可以帮助做到这一点,如 GetX、MobX、BLoC、Redux、Provider 等,不过这些框架中除了 GetX 之外,其他不那么容易理解和使用。

② GetX 拥有庞大的生态系统、庞大的社区和众多的协作者,并且只要 Flutter 还存在,它就会一直维护下去。GetX 也能够在 Android、iOS、Web、Mac、Linux、Windows 以及您的服务器上使用相同的代码运行。 使用**Get Server,** 您可以在后端完全复用您在前端编写的代码

GetX 专注于性能和最小资源消耗。 GetX 的关键性能优势之一在于其开销非常小。 通过最大限度地减少不必要的重新渲染和重建小部件,GetX 显著地减轻了应用程序的计算负担,从而加快了渲染速度并提高了整体性能。此外,GetX 利用了高效依赖注入的力量。 其轻量级依赖注入机制可以在不影响性能的情况下创建和管理依赖项。 通过有效管理依赖关系,GetX 有助于消除不必要的对象实例化并确保高效的内存利用。

3. Getx强大功能展示

案例:计数器应用程序

Flutter 新项目默认创建的“计数器”代码超过 50 行(含注释)。为了展示 Get 的强大功能,我将演示如何创建一个“计数器”,使其每次点击时状态都会改变,并在不同页面之间切换以及在不同屏幕之间共享状态。所有这些都以一种井然有序的方式实现,将业务逻辑与视图分离,代码(含注释)仅用 20几 行。

先看下之前的代码


void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return ***MaterialApp***(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

使用Getx后的代码

步骤1:在 MaterialApp 前添加“Get”,将其转换为 GetMaterialApp
void main() => runApp(GetMaterialApp(home: Home()));

注意:这不会修改 Flutter 的 MaterialApp,GetMaterialApp 并非修改后的 MaterialApp,它只是一个预配置的 Widget,其中包含默认的 MaterialApp 作为子组件。您可以手动配置它,但绝对不是必需的。GetMaterialApp 会创建路由、注入路由、注入翻译,以及注入路由导航所需的一切。如果您仅使用 Get 进行状态管理或依赖管理,则无需使用 GetMaterialApp。对于路由、Snackbars、国际化、BottomSheets、对话框以及与路由和上下文缺失相关的高级 API,GetMaterialApp 是必需的。

步骤2:创建业务逻辑类,并将所有变量,方法和控制器放入其中。您可以使用简单的.obs文件将任何变量设置为可观察变量
class Controller extends GetxController{
    var count = 0.obs;
    increment() => count++;
}
步骤3:创建视图,使用StatelessWidget并节省一些RAM,使用Get您可能不再需要使用StatefulWidget
class Home extends StatelessWidget {

  @override
  Widget build(context) {
    final Controller c = Get.put(Controller());
    return Scaffold(
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
      body: Center(child: ElevatedButton(
              child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
      floatingActionButton:
          FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
  }
}

class Other extends StatelessWidget {
  final Controller c = Get.find();
  @override
  Widget build(context){
     // Access the updated count variable
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}

4. Gex的三大核心功能

  • 状态管理(State Management)

    • 响应式变量(.obs)自动更新 UI。
    • 支持简单状态管理(.update() GetBuilder)和复杂响应式状态管理(GetX/Obx .obs + Obx)。
  • 路由管理(Route Management)

    • 不需要 BuildContext 就能跳转页面。
    • 支持动态传参、中间件、命名路由等。
  • 依赖注入(Dependency Injection)

    • 控制器生命周期管理。
    • 通过 Get.put()Get.lazyPut() 实现依赖注入。

4.1 响应式状态管理

响应式编程会令很多人望而却步,因为它被认为很复杂,GetX让响应式编程变得很简单

使用Get进行响应式标称与使用setState一样简单

案例:假设有一个名称变量,并且希望每次更改它的时候,所有使用它的小部件会自动更改
//为了便于观察在尾部加上.obs
var name = "jone".obs;

so,从现在开始,我们可能会将这种反应式.obs称为Rx

底层做了什么呢?

创建了一个stream as String,赋予了初始值“jone”,并通知所有使用“jone”这个变量的小部件,他们现在属于这个变量。当Rx的值发射管改变的时候,他们也必须发生改变 这就是GetX 的神奇之处,得益于 Dart 的功能。

您将需要创建一个StreamBuilder,订阅此变量以监听更改,StreamBuilder如果您想在同一范围内更改多个变量,则需要创建一个嵌套的“级联”,对吗?

不,您不需要StreamBuilder,但是您对静态类的看法是正确的。

嗯,在视图中,当我们想要修改某个 Widget 时,通常会有很多样板代码,这就是 Flutter 的方式。有了GetX, 你就可以不用管这些样板代码了。

StreamBuilder( … )???initialValue: …builder: …,您只需将此变量放置在Obx()Widget

Obx (() => Text (controller.name));
声明一个响应式变量

3种方法

1 - 首先是使用**Rx{Type}** 。

// initial value is recommended, but not mandatory
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**Darts 泛型,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>>({});

// Custom classes - it can be any class, literally
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;

// Custom classes - it can be any class, literally
final user = User().obs;

4.2 路由管理

如果是在没有上下文的情况使用routes/snackbars/dialogs/bottomsheets,GetX也是很适合的

在 MaterialApp 前添加“Get”,将其转换为 GetMaterialApp

GetMaterialApp( // Before: MaterialApp(
  home: MyHome(),
)
Get.to(NextScreen())跳转
Get.toNamed('/details');使用名称导航到新屏幕
Get.back();关闭快捷栏、对话框、底部菜单或任何通常使用 Navigator.pop(context) 关闭的内容
Get.off(NextScreen());转到下一个屏幕,没有返回上一个屏幕的选项(用于启动画面、登录屏幕等)
Get.toNamed('/details');使用名称导航到新屏幕
Get.offAll(NextScreen());转到下一个屏幕并取消所有先前的路由(在购物车、民意调查和测试中很有用)

4.3 依赖注入

4.3.1 介绍

Get 有一个简单而强大的依赖管理器,它允许您仅使用 1 行代码检索与您的 Bloc 或 Controller 相同的类,无需 Provider 上下文,无需 InheritedWidget:

Controller controller = Get.put(Controller()); 
// 而不是 Controller controller = Controller();

想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那你需要一个状

态管理器与Provider或Get_it一起使用来拿到它,对吗?用Get则不然,Get会自动为你的控制器找到你

想要的数据,而你甚至不需要任何额外的依赖关系。

Controller controller = Get.find();
//是的,Get会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,Get总会给你正确的控制器。这看起来是不是怪神奇的。

4.3.2 三种依赖管理方式

// 1. 立即实例化
Get.put(Service());

// 2. 懒加载
Get.lazyPut(() => ApiService());

// 3. 异步初始化
Get.putAsync(() async => await SharedPrefService());

// 获取实例
final service = Get.find<Service>();

4.3.3 作用域控制

// 绑定到路由
Get.put(CounterController(), tag: 'uniqueID');

// 路由销毁时自动移除
Get.create(() => TempController());

4.3.4 多页面之间的数据共享

Flutter默认创建的 "计数器 "项目有50多行,为了展示Get的强大功能,我将使用 GetX 重

写一个"计数器 加强版",实现:

  • 每次点击都能改变状态 
  • 在不同页面之间切换
  • 在不同页面之间共享状态
  • 将业务逻辑与界面分离
应用程序入口设置
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp();
    ...
  }
}
新建CountController
import 'package:get/get.dart';

class CountController extends GetxController {
  RxInt count = 0.obs; // 初始化一个可观察的变量count,初始值为0
   void inc() {
    count++; // 递增count的值
    update(); // 通知侦听器进行更新
  }
  void dec() {
    count--; // 递减count的值
    update(); // 通知侦听器进行更新
  }
}
examplePage.dart执行inc方法
import 'package:app_flutter01/pages/Key/messages.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class examplePage extends StatefulWidget {
  const examplePage({super.key});

  @override
  State<examplePage> createState() => _examplePageState();
}

class _examplePageState extends State<examplePage> {
  CountController countController = Get.put(CountController()); //实例化控制器
  @override
  Widget build(BuildContext context) {
    return Center(
      child:     Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Obx(() => Text("${countController.count}",
            style: Theme.of(context).textTheme.headline1)),
        ElevatedButton(
            onPressed: () {
              countController.inc();
            },
            child: const Text("数值+1"))
      ],
    ),
    );
  }
}
myHome.dart执行dec方法

你可以让Get找到一个正在被其他页面使用的Controller,并将它返回给你。

final countController= Get.find<CountController>();
或者
final CountController countController = Get.find();

代码:

import 'package:app_flutter01/pages/Key/messages.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class myHome extends StatefulWidget {
  const myHome({super.key});

  @override
  State<myHome> createState() => _myHomeState();
}

class _myHomeState extends State<myHome> {
  final CountController countController = Get.find();
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Obx(() => Text("${countController.count}" )),
          ElevatedButton(
              onPressed: () {
                countController.dec();
              },
              child: const Text("数值-1"))
        ],
      ),
    );
  }
}
GetxController 绑定数据的几种方法
方法1:
CountController countController = Get.put(CountController()); //建议使用
或者
final CountController countController = Get.find();

  Obx(()=>Text("${countController.count}",style:Theme.of(context).textTheme.headline1)),

方法2:

只是绑定数据无需调用 Get.put(CountController());

GetX<CountController>(
          init: CountController(),
          builder: (controller) {
             return Text(
              "${controller.count}",
              style: const TextStyle(color: Colors.green, fontSize: 30),
           );
          },
),
方法3:
CountController countController = Get.put(CountController());
或者
final CountController countController = Get.find();

GetBuilder<CountController>(
       init: countController,
       builder: (controller) {
         return Text(
            "${controller.count}",
            style: const TextStyle(color: Colors.green, fontSize: 30),
       );
      },
)

4.3.5 GetX Binding

需求:所有页面都要使用状态管理

在我们使用 GetX 状态管理器的时候,往往每次都是用需要手动实例化一个控制器,这样的话基本页面

都需要实例化一次,这样就太麻烦了,而 Binding 能解决上述问题,可以在项目初始化时把所有需要

进行状态管理的控制器进行统一初始化,接下来看代码演示:

在前面的文章中,我们经常使用 Get.put(MyController()) 来进行控制器实例的创建,这样我们就算

不使用控制器实例也会被创建,其实 GetX 还提供很多创建实例的方法,可根据不同的业务来进行创

建,接下来我们简单介绍一下几个最常用的

  • Get.put(): 不使用控制器实例也会被创建
  • Get.lazyPut(): 懒加载方式创建实例,只有在使用时才创建
  • Get.putAsync(): Get.put() 的异步版版本
  • Get.create(): 每次使用都会创建一个新的实例
第一步:声明需要进行的绑定控制器类
import 'package:get/get.dart';

class CountController extends GetxController {
  RxInt count = 0.obs; // 初始化一个可观察的变量count,初始值为0

   void inc() {
    count++; // 递增count的值
    update(); // 通知侦听器进行更新
  }

  void dec() {
    count--; // 递减count的值
    update(); // 通知侦听器进行更新
  }
}
import 'package:get/get.dart';
class BindingMyController extends GetxController {
  var count = 0.obs;
  void increment() {
    count++;
  }
}
import 'package:app_flutter01/pages/Key/messages.dart';
import 'package:get/get.dart';

class AllControllerBinding implements Bindings {
  @override
  void dependencies() {
// TODO: implement dependencies
    Get.lazyPut<CountController>(() => CountController()); //懒加载
    Get.lazyPut<BindingHomeController>(() => BindingHomeController());
  }
}
第二步:在项目启动时进行初始化绑定
void main() {
  runApp(GetMaterialApp(
      title: "flutterAPP___test",
      initialBinding: AllControllerBinding(), //全局绑定GetxController
      home: Scaffold(body: MyFlutter1())));
}
第三步:在页面中使用状态管理器
class myHome extends StatefulWidget {
  const myHome({super.key});

  @override
  State<myHome> createState() => _myHomeState();
}

class _myHomeState extends State<myHome> {
  CountController countController = Get.find<CountController>(); //实例化控制器

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Obx(() => Text("${countController.count}" )),
          ElevatedButton(
              onPressed: () {
                countController.dec();
              },
              child: const Text("数值-1"))
        ],
      ),
    );
  }
}