likes
comments
collection
share

flutter插件get使用指南

作者站长头像
站长
· 阅读数 9

介绍

GetX,是一个 flutter 插件,它的 LIKES 非常非常多,目前为止是 LIKES 最多的 Flutter package。它功能很强大,而且使用便捷,支持路由管理,状态管理,响应式开发。有了它就可以直接一把梭哈,神速开发 Flutter 应用。 插件官方地址 get

安装

flutter pub add get

响应式开发

import 'package:flutter/material.dart';
import 'package:get/get.dart'; // 引入依赖包

// 在 MaterialApp 前面加上 "Get"
void main() => runApp(const GetMaterialApp(home: Home()));

// 创建一个控制器
class Controller extends GetxController {
  var count = 0.obs; // 在变量后面加上 'obs', 声明响应式变量

  increment() => count++;
}

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(context) {
    // 使用 Get.put() 对我们的控制器进行初始化,之后所有的子 widget 都可以访问到它了
    final Controller c = Get.put(Controller());

    return Scaffold(
        // 使用 Obx() 方法返回一个 widget, 每当依赖的 c.count 发送变化,widget 都会重新新 build
        appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

        body: const Center(child: Text('get')),
        floatingActionButton: FloatingActionButton(onPressed: c.increment, child: const Icon(Icons.add)));
  }
}

现在,当我们点击添加按钮时,标题上的点击次数就会跟着变化,我们已经实现了响应式的开发。 太简单了!

第一眼看过去,".bos"是什么东西。这其实是一个拓展语法,平时可能不太常见。简单的说就是在其他类上新增了一个方法。可以查看官网解释拓展方法

在编辑器里面点击 ".bos",跳转到源码的位置。

extension IntExtension on int {
  /// Returns a `RxInt` with [this] `int` as initial value.
  RxInt get obs => RxInt(this);
}

翻译结果就是,返回一个 RxInt 类型的值,使用 .bos 前面的值作为初始值。

它其实是一个语法糖,类似的我们还可以使用 RxString,声明一个响应式的 String。

RxString s = RxString("s");

想要声明一个响应式的变量,我们直接在原有类型的变量后面加上 ".bos",一把梭哈就完事了。

点击 RxInt,直接查看源码。

class RxInt extends Rx<int> {
  RxInt(int initial) : super(initial);

  /// Addition operator.
  RxInt operator +(int other) {
    value = value + other;
    return this;
  }

  /// Subtraction operator.
  RxInt operator -(int other) {
    value = value - other;
    return this;
  }
}

翻译一下:operator 运算符重载,实现一个加法和减法操作,然后 RxInt 的 value 可以和 int 类型进行加减操作,然后返回 RxInt。

我们看到 RxInt 继承了 Rx, 然后我们看 Rx 相关的源码。

/// Foundation class used for custom `Types` outside the common native Dart
/// types.
/// For example, any custom "Model" class, like User().obs will use `Rx` as
/// wrapper.
class Rx<T> extends _RxImpl<T> {
  Rx(T initial) : super(initial);

  @override
  dynamic toJson() {
    try {
      return (value as dynamic)?.toJson();
    } on Exception catch (_) {
      throw '$T has not method [toJson]';
    }
  }
}

Rx修饰了我们自定义的类,使用 Rx 包装之后它就是响应式的了。Rx 类还重写了父类的 toJson() 方法。在 toJson() 方法中,它尝试调用被包装对象的 toJson() 方法将其转换为 JSON 格式的数据。如果被包装对象没有定义 toJson() 方法,则会抛出一个异常。

示例代码:

import 'package:get/get.dart';

class Person {
  String? name, last;
  int age;

  Person({this.name, this.last, required this.age});
}

class Controller extends GetxController {
  final person = Person(name: 'John', last: 'Doe', age: 18).obs;

  grow() {
    person.update((val) {
      val!.age++;
    });
  }
}

...

// 访问属性
Obx(() => Text('${c.person.value.age}'));

对于自定义的类,对类的实例使用 ".bos",更新属性值时使用 update 方法,使用 value 属性去访问实例成员属性。

对于 _RxImpl 类,代码就比较复杂,它是响应式的核心。具体源码这里就不展示了,感兴趣的小伙伴可以自己去查看源码,这里只做一个简单的分析。

_RxImpl(T initial) {
    _value = initial;
}

RxImpl 内部使用私有属性 _value 保存初始化的 value。这时候就有人要问了,你刚刚在说通过 value 属性去访问实例的成员属性的,这是怎么回事?

源码里面有一个抽象类 "abstract class _RxImpl extends RxNotifier with RxObjectMixin"。

注意这个 RxObjectMixin,它的内部有一段代码。

/// Returns the current [value]
T get value {
   RxInterface.proxy?.addListener(subject);
   return _value;
}

这里我们可以看到成员有一个 getter,当我们访问 value,实际上返回的是 _value,也就是存储的原始值。

如果我们需要设计一个响应式的系统,也就是说当数据源发送改变的时候,widget 会重新 build。是的,在前两篇文章我介绍了 Stream 和 StreamBuilder, 大体上就是通过 StreamController 添加数据,然后 listen 监听到数据流时会重新 build,从而更新 UI。

_RxImpl 内部有一个 Stream, 主要通过 StreamController 和 StreamSubscription 控制。当我们重新设置 value 的时候,内部会发送一个 Stream,然后监听者们就会收到通知做响应的处理。当然其内部逻辑具体实现还有很多细节,比如设置同样的值并不会触发更新,Stream 关闭的时候不能设置值等。

当我们访问属性值的时候,就会添加 GetStream 到监听者列表,方便查询监听者数量等。然后我们看 Obx(() => Text('${c.person.value.age}')) 方法,它实际上返回的是一个 StatefulWidget, 内部的 build 方法被重写了,实际上调用的就是 Obx 传入的一个返回 widget 的回调函数。在 _ObxState 内部的 initState 生命周期监听 Stream,如果有数据流/事件通知,就调用 setState 重新触发 bulid,更新UI。

简化流程就是,首先设置响应式数据生成 Stream,改变响应式数据的时候,发送通知事件,Obx 内部监听Stream 重新 build,更新UI。具体内容请查看源码分析。

另外关于响应式的状态管理,还有 GetBuilder 可用,这里不在赘述。具体请查看 GetBuilder vs GetX vs Obx vs MixinBuilder

路由管理

路由跳转

  • 导航到新的页面
Get.to(NextScreen());
  • 关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。
Get.back();
  • 替换当前页面,无法回到上一个页面
Get.off(NextScreen());
  • 删除所有路由记录,跳转到一个新的页面
Get.offAll(NextScreen());
  • 要导航到下一条路由,并在返回后立即接收或更新数据。
var data = await Get.to(Payment());
  • 返回上一个页面并传递数据
Get.back(result: 'success');

结合起来就是下面这样:

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

命名路由导航

  • 导航到新的页面
Get.toNamed(NextScreen());
  • 替换当前页面,无法回到上一个页面
Get.offNamed(NextScreen());
  • 删除所有路由记录,跳转到一个新的页面
Get.offAllNamed(NextScreen());

路由传参

发送任意类型的数据,如一个Map

Get.toNamed('/second', arguments: {'id': 1});

接收参数

print(Get.arguments['id']);

类似web使用querystring

Get.toNamed('/second?id=2');

接收参数

print(Get.parameters['id']);

路由参数(需要定义路由)

void main() {
  runApp(GetMaterialApp(
    initialRoute: '/',
    getPages: [
      GetPage(name: '/', page: () => const Home()),
      GetPage(
        name: '/other/:user', // 定义路由参数
        page: () => const Other(),
        transition: Transition.leftToRight, // 定义路由跳转动画
      ),
    ],
  ));
}

传递参数

Get.toNamed('/other/1')

接收参数

print(Get.parameters['user']); // 1

路由中间件

类似前端的vue开发,vue-router中路由的全局前置守卫和后置守卫在项目中经常被使用到。利用 Get 插件,我们可以实现类似的功能。

假如我们需要实现一个权限管理功能,没有登录的用户,点击某个页面需要跳转到登录页。

首先,定义一个路由中间件它继承于 GetMiddleware 并重写 redirect 方法,GetMiddleware有很多方法,读者可自行查看源码。

class MyMiddleWare extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    print('route:$route');
    bool isLogin = false;
    return isLogin ? null : const RouteSettings(name: '/login');
  }
}

然后将它配置到路由表使用,我们也可以使用多个路由中间件,并可以设置中间件的权重,也就是控制多个中间价的执行顺序。

void main() {
  runApp(GetMaterialApp(
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => const Home()),
        GetPage(name: '/login', page: () => const Login()),
        GetPage(
            name: '/other/:user',
            page: () => const Other(),
            transition: Transition.leftToRight, // 定义路由跳转动画
            middlewares: [MyMiddleWare()] // 使用路由中间件
            ),
      ],
      routingCallback: (routing) {
        print('current route:${routing?.current}');
      }));
}

类似路由跳转后触发的后置守卫就是代码中的 routingCallback 方法,可以监听路由跳转的发生然后做相应的处理。

非跳转导航

在 flutter 中经常需要打开消息弹窗等操作,页面没有发送跳转,但是需要使用 Navigator.pop(context) 关闭。他们的使用通知都依赖于 context,现在使用 get 插件,我们可以很容易实现类似消息弹窗的功能(使用 Get.back() 关闭它们)。

打开 SnackBar

Get.snackbar('Hi', 'i am a modern snackbar');

打开Dialog

Get.dialog(YourDialogWidget());

打开默认Dialog

Get.defaultDialog(
  onConfirm: () => print("Ok"),
  middleText: "Dialog made in 3 lines of code"
);

打开BottomSheets

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.put() 插入了依赖关系,并且返回了一个控制器,我们可以直接使用它。还有可能我们在父 widget 插入了依赖关系。在子 widget 使用,我们就可以使用下面的代码获取到控制器。

final controller = Get.find<Controller>(); // 通过范型获取

集成管理

将路由、状态管理器和依赖管理器完全集成,可以简化许多操作。

创建一个类并实现Binding

class Controller extends GetxController {
  var count = 0.obs;
  increment() => count++;
}

class CountBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut<Controller>(() => Controller());
  }
}

然后在你的命名路由定义的时候绑定它们即可。

void main() => runApp(
      GetMaterialApp(initialRoute: '/', getPages: [
        GetPage(
          name: '/',
          page: () => const Home(),
          binding: CountBinding(),
        ),
        GetPage(
          name: '/details',
          page: () => Other(),
          binding: CountBinding(),
        ),
      ]),
    );

现在所有的绑定的路由都可以获取并使用 Controller。

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

你也可以通过 "initialBinding" 来插入所有将要创建的依赖。在 GetMaterialApp 内部,把所有控制器绑定到一起,然后实例化。

GetMaterialApp(
  initialBinding: SampleBind(),
  home: Home(),
);

通过 GetView 可以控制器的注册操作。

// GetView<T> 通过范型注册了对应的控制器
class Other extends GetView<Controller> {
  
  Other({super.key});

  @override
  Widget build(context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('other'),
        ),
        // 访问控制器 'controller.xxx'
        body: Center(child: Text("${controller.count}")));
  }
}

如果你想了解更多内容,建议查看 github包管理地址,通过编辑器的点击跳转,阅读对应的源码,能更好的帮助你了解它的内部机制。

转载自:https://juejin.cn/post/7273758446112653366
评论
请登录