详解Flutter 响应式状态管理框架GetX

目录
  • 一、状态管理框架对比
    • Provider
    • BLoC
    • GetX
  • 二、基本使用
    • 2.1 安装与引用
    • 2.2 使用GetX改造Counter App
    • 2.3 GetX代码插件
  • 三、其他功能
    • 3.1 路由管理
    • 3.2 依赖关系管理
    • 3.3 工具
    • 3.4 改变主题
    • 3.5 GetConnect
    • 3.6 GetPage中间件
      • Priority
      • Redirect
      • onPageCalled
      • OnBindingsStart
      • OnPageBuildStart
    • 3.7 全局设置和手动配置
    • 3.8 StateMixin
    • 3.9 GetxService

一、状态管理框架对比

在Flutter的状态管理框架中,主流的状态管理框架有四个:GetX(又称为Get)、BLoC、MobX、Provider。

Provider

其中,Provider是Flutter社区提供的一种状态管理工具,本质上是对InheritedWidget组件的封装,具有如下一些优点:

  • 简化的资源分配与处置
  • 懒加载
  • 创建新类时减少大量的模板代码
  • 支持 DevTools
  • 更通用的调用 InheritedWidget 的方式
  • 提升类的可扩展性,整体的监听架构时间复杂度以指数级增长

BLoC

BLoC是Business Logic Component的英文缩写,中文译为业务逻辑组件,是一种使用响应式编程来构建应用的方式。BLoC最早由谷歌的Paolo Soares和Cong Hui设计并开发,设计的初衷是为了实现页面视图与业务逻辑的分离。下图演示了BLoC模式的应用程序的架构示意图。

BLoC依赖Stream和StreamController,组件通过Sink发送状态事件,然后再通过Stream通知其他组件进行状态刷新,事件的处理和通知更新都由BLoC负责,如下图所示。

GetX

GetX是一种轻量级且强大的Flutter解决方案,集高性能状态管理、智能依赖注入和路由管理于一体,是Flutter开发不可多得的工具。具有如下优势:

  • 依赖注入: 依赖注入是一种消除组件之间依赖的方式,用来降低使用者与其依赖组件之间的耦合度。使用依赖注入具有便于重构和便于扩展的好处,比如获取实例无需BuildContext、GetBuilder自动化处理及减少入参等等。
  • 跨页面交互: 跨页面交互是一种常见的场景,GetX可以很优雅的实现跨页面交互,如参数传递和跨页面的状态管理。
  • 路由管理:Getx内部实现了路由管理,而且使用起来也很简单。同时,GetX实现了动态路由传参,也就是说直接在命名路由上拼参数。

二、基本使用

2.1 安装与引用

和其他的Flutter插件一样,使用GetX之前需要先在项目中导入GetX插件,GetX插件分为非空安全和空安全两个版本,分别是应对2.0之前和之后的版本。

#非空安全最后一个版本(flutter 2.0之前版本)
get: ^3.26.0
#空安全版本
get: ^4.6.5

然后,在需要使用的地方引入get。

import 'package:get/get.dart';

2.2 使用GetX改造Counter App

为了展示GetX的强大功能,我们将对Flutter官方的计数器示例使用GetX进行改造。并且,使用GetX之后,业务逻辑和屏幕之间的共享状态的管理将会发生变化,经过改造之后,原本需要近100行代码才能使用的功能,现在26行就能够实现。

第1步,将项目的MaterialApp变成GetMaterialApp,如下所示。

void main() => runApp(GetMaterialApp(home: Home()));

需要说明的是,在应用的最顶层使用GetMaterialApp会创建路由,如果您只是想使用GetX进行状态管理或依赖项管理,则没有必要将MaterialApp修改为GetMaterialApp。如果项目中需要使用诸如路由、snackbar、国际化、bottomSheets、对话框和上下文相关的高级api,那么可以使用GetMaterialApp。

同时,如果使用了GetMaterialApp的路由功能,那么可以使用Get.to(), Get.back()等函数来管理路由时。如果不打算使用它,那么就没有必要执行第一步。

第2步,创建业务逻辑类,并将所有变量、方法和控制器放在里面。然后使用.obs创建可观察变量。

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

第3步,创建视图,然后GetX的Controller获取状态,在创建视图时使用StatelessWidget即可,不再需要使用Statfulwidget,更加节约内存和性能开销。

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){
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}

从示例可以看到,引入GetX状态管理框架之后,代码的结构也相对的发生了变化,并且逻辑上也变得更加清晰,非常适合中大型项目。

2.3 GetX代码插件

由于GetX的使用流程比较固定,我们完全可以将它开发成一个IDE插件,下面是社区上开源的一个IDE插件,大家可以将其下载下来,然后进行自定义的升级。

目前,插件已经支持生成GetX的所有文件。首先,我们打开Android Studuo,在Plugins 搜索GeX,然后点击安装。

接着,生成GetX的模式,目前提供了两种生成方式:

  • Default:默认模式,生成三个文件:state,logic,view
  • Easy:简单模式,生成俩个文件:logic,view

同时,插件还提供后缀名修改和数据的持久化。

插件还提供了快捷键功能,如使用【Alt + Enter】快捷键可以调出包裹Widget,有四种可选:GetBuilder、GetBuilder(Auto Dispose),Obx、GetX。

三、其他功能

除了基本的状态管理外,GetX还支持依赖关系管理、路由管理、snackbar、国际化、bottomSheets、上下文相关的API等功能。

3.1 路由管理

如果在项目中有涉及路由、snackbar、对话框、bottomSheets的功能需求,那么GetX也是非常不错的。为了使用GetX的路由功能,我们需要使用GetMaterialApp替换MaterialApp。

GetMaterialApp(
  home: MyHome(),
)

然后,要打开一个新页面时,使用Get.to()或者Get.toNamed()。

Get.to(NextScreen());
Get.toNamed('/details');

使用命名路由打开一个新的页面时,需要先在GetMaterialApp中进行申明,比如。

void main() {
  runApp(
    GetMaterialApp(
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => MyHomePage()),
        GetPage(name: '/second', page: () => Second()),
        GetPage(
          name: '/third',
          page: () => Third(),
          transition: Transition.zoom
        ),
      ],
    )
  );
}

如果使用的是系统默认的AppBar,使用的是Navigator.pop(context)来关闭页面,如果是自定义的AppBar,并且使用了GetX的路由功能,那么需要使用下面得代码来关闭页面。

Get.back();

有时候,我们打开一个新页面时候,在关闭得时需要返回上一个页面,那么可以使用Get.off()。

Get.off(NextScreen());

如果打开页面并且需要清除路由栈的其他页面,那么可以使用Get.offAll()。

Get.offAll(NextScreen());

同时,GetX的路由提供了获取上下文的功能,这也是GetX路由管理的最大优点之一。有了它,开发者可以从控制器类中获取路由的所有方法。

3.2 依赖关系管理

除了状态管理和路由管理外,GetX还支持依赖关系管理,它能够实现Bloc或Controller相同功能,而实现上只需要1行代码。

Controller controller = Get.put(Controller());

同时,Get依赖管理与包的其他部分是分离的,如果你的应用程序已经在使用状态管理器,那么不需要再重写它,比如。

controller.fetchApi();

如果您的项目中使用了很多的路由,并且这些路由已经存在了GetX的路由栈中,那么可以使用Get.find()来获取路由栈的相关信息。

Controller controller = Get.find();
Text(controller.textFromApi);

3.3 工具

除了上面介绍的核心功能外,GetX还提供了很多的工具功能,比如国际化。使用GetX实现国际化时,首先需要创建一个继承自Translations的类。

import 'package:get/get.dart';
class Messages extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en_US': {
          'hello': 'Hello World',
        },
        'de_DE': {
          'hello': 'Hallo Welt',
        }
      };
}

然后,只需将.tr附加到指定的键,它将使用GetX的当前值进行转换。系统将会依据地区和Get.fallbackLocale的返回值进行转换。

Text('title'.tr);

除此之外,我们还可以使用参数的方式进行转换。

import 'package:get/get.dart';
Map<String, Map<String, String>> get keys => {
    'en_US': {
        'logged_in': 'logged in as @name with email @email',
    },
    'es_ES': {
       'logged_in': 'iniciado sesión como @name con e-mail @email',
    }
};
Text('logged_in'.trParams({
  'name': 'Jhon',
  'email': 'jhon@example.com'
  }));

最后,还需要在GetMaterialApp中定义语言的环境和翻译内容。

return GetMaterialApp(
    translations: Messages(),
    locale: Locale('en', 'US'),
    fallbackLocale: Locale('en', 'UK'),
);

如果需要改变本地的默认的语言环境,可以使用下面的方式。

var locale = Locale('en', 'US');
Get.updateLocale(locale);

3.4 改变主题

当然,我们可以可以使用GetX来实现默认主题的修改。这种功能类似于使用ThemeProvider来改变应用的主题。首先,您需要创建自定义主题并将其添加到Get中。

Get.changeTheme(ThemeData.light());

比如,我们想在onTap中创建一个按钮来改变主题。

Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());

3.5 GetConnect

GetConnect是一种可以使用http或websockets实现和服务器通信的方法,可以使用它实现GET/POST/PUT/DELETE/SOCKET网络请求。

class UserProvider extends GetConnect {
  // Get request
  Future<Response> getUser(int id) => get('http://youapi/users/$id');
  // Post request
  Future<Response> postUser(Map data) => post('http://youapi/users', body: data);
  // Post request with File
  Future<Response<CasesModel>> postCases(List<int> image) {
    final form = FormData({
      'file': MultipartFile(image, filename: 'avatar.png'),
      'otherFile': MultipartFile(image, filename: 'cover.png'),
    });
    return post('http://youapi/users/upload', form);
  }
  GetSocket userMessages() {
    return socket('https://yourapi/users/socket');
  }
}

和其他的网络请求库一样,GetConnect也支持自定义基Url、请求头、自定义请求修饰符,以及请求重试,以及解码器和将请求结果转换为model等。

class HomeProvider extends GetConnect {
  @override
  void onInit() {
    // All request will pass to jsonEncode so CasesModel.fromJson()
    httpClient.defaultDecoder = CasesModel.fromJson;
    httpClient.baseUrl = 'https://api.covid19api.com';
    // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to
    // Http and websockets if used with no [httpClient] instance
    // It's will attach 'apikey' property on header from all requests
    httpClient.addRequestModifier((request) {
      request.headers['apikey'] = '12345678';
      return request;
    });
    // Even if the server sends data from the country "Brazil",
    // it will never be displayed to users, because you remove
    // that data from the response, even before the response is delivered
    httpClient.addResponseModifier<CasesModel>((request, response) {
      CasesModel model = response.body;
      if (model.countries.contains('Brazil')) {
        model.countries.remove('Brazilll');
      }
    });
    httpClient.addAuthenticator((request) async {
      final response = await get("http://yourapi/token");
      final token = response.body['token'];
      // Set the header
      request.headers['Authorization'] = "$token";
      return request;
    });
    //Autenticator will be called 3 times if HttpStatus is
    //HttpStatus.unauthorized
    httpClient.maxAuthRetries = 3;
  }
  }
  @override
  Future<Response<CasesModel>> getCases(String path) => get(path);
}

3.6 GetPage中间件

Priority

GetPage是一个新的属性,它接受一个GetMiddleWare列表,然后按照列表的顺序来执行这些中间件。要运行的中间件的顺序,可以通过GetMiddleware中的顺序来设置。

final middlewares = [
  GetMiddleware(priority: 2),
  GetMiddleware(priority: 5),
  GetMiddleware(priority: 4),
  GetMiddleware(priority: -8),
];

Redirect

当需要执行路由的重定向时,就可以调用此函数,比如使用它实现强制登录逻辑,即没有登录时跳转登录逻辑。

RouteSettings redirect(String route) {
  final authService = Get.find<AuthService>();
  return authService.authed.value ? null : RouteSettings(name: '/login')
}

onPageCalled

有时候,我们需要在页面创建之前调用某个额函数,比如可以使用它来更改页面的某些内容或为其提供新页面。

GetPage onPageCalled(GetPage page) {
  final authService = Get.find<AuthService>();
  return page.copyWith(title: 'Welcome ${authService.UserName}');
}

OnBindingsStart

此函数将在初始化绑定之前被调用。在这个函数中,我们可以更改页面的绑定。

List<Bindings> onBindingsStart(List<Bindings> bindings) {
  final authService = Get.find<AuthService>();
  if (authService.isAdmin) {
    bindings.add(AdminBinding());
  }
  return bindings;
}

OnPageBuildStart

此函数将在绑定初始化之后被调用。在这个函数中,我们可以在创建绑定之后和创建页面小部件之前执行一些操作。

GetPageBuilder onPageBuildStart(GetPageBuilder page) {
  print('bindings are ready');
  return page;
}

3.7 全局设置和手动配置

默认情况下,GetMaterialApp提供了一下默认的配置,但如果你想手动配置GetX,那也是可以得。

MaterialApp(
  navigatorKey: Get.key,
  navigatorObservers: [GetObserver()],
);

我们还可以将GetObserver替换成自己的中间件。

MaterialApp(
  navigatorKey: Get.key,
  navigatorObservers: [
    GetObserver(MiddleWare.observer) // Here
  ],
);

当然,我们也可以为GetX创建一个全局设置。比如在打开路由时修改默认的配置。

GetMaterialApp(
  enableLog: true,
  defaultTransition: Transition.fade,
  opaqueRoute: Get.isOpaqueRouteDefault,
  popGesture: Get.isPopGestureEnable,
  transitionDuration: Get.defaultDurationTransition,
  defaultGlobalState: Get.defaultGlobalState,
);
Get.config(
  enableLog = true,
  defaultPopGesture = true,
  defaultTransition = Transitions.cupertino
)

3.8 StateMixin

处理UI状态的另一种方法是使用StateMixin,使用前需要使用with将StateMixin添加到T模型的控制器中。

class Controller extends GetController with StateMixin&lt;User&gt;{}

mixin是Dart中一个非常重要的概念,是一种在多个类层次结构中复用类代码的方法。而change()方法是一种可以随时更改状态的方法。

change(data, status: RxStatus.success());

RxStatus支持的状态有如下一些:

RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');

然后,我们使用obx根据状态来加载不同的视图。

class OtherClass extends GetView<Controller> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: controller.obx(
        (state)=>Text(state.name),
        onLoading: CustomLoadingIndicator(),
        onEmpty: Text('No data found'),
        onError: (error)=>Text(error),
      ),
    );
}

3.9 GetxService

GetxService的作用类似于GetxController,可以共享相同的生命周期(onInit(), onReady(), onClose()),GetxService可以用来实现后台服务。例如:ApiService, StorageService, CacheService。

Future<void> main() async {
  await initServices();
  runApp(SomeApp());
}
void initServices() async {
  print('starting services ...');
  await Get.putAsync(() => DbService().init());
  await Get.putAsync(SettingsService()).init();
  print('All services started...');
}
class DbService extends GetxService {
  Future<DbService> init() async {
    print('$runtimeType delays 2 sec');
    await 2.delay();
    print('$runtimeType ready!');
    return this;
  }
}
class SettingsService extends GetxService {
  void init() async {
    print('$runtimeType delays 1 sec');
    await 1.delay();
    print('$runtimeType ready!');
  }
}

参考:Get官方文档

以上就是详解Flutter 响应式状态管理框架GetX的详细内容,更多关于Flutter响应式状态管理GetX的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter状态管理Provider的使用学习

    目录 1. provider的使用 2. 控制Widget的刷新颗粒度 3. 小结 本文主要介绍flutter中状态管理组件provider,provider: ^6.0.3主要是用于我们系统InheritedWidge的封装,用于数据的传递和管理. 1. provider的使用 网上也有很多关于provider说明,也可以看下官方的provider的 README.这里我记录一下我自己学习.我们对于简单的数据共享可以设置参数,之后子页面进行数据方法回调,从而完成数据间的通信.但是比较麻烦,下面

  • Flutter组件状态管理的3种方法

    前言 前面讲了Flutter布局,布局只是静态的.在Flutter中,组件分为StatelesslWidget和StatefulWidget. StatelesslWidget 没有状态,是一成不变的.比如图标,文字,按钮等 StatefulWidget 有状态的组件,页面数据是动态的,或者会随着用户操作变化,比如多选框,文本输入框等. 有状态组件 重点来了,如何使用实现一个有状态的组件呢? 有状态的组件一般由两个类构成,一个StatefulWidget子类和一个State子类. State类包

  • 详解如何在Flutter中用小部件创建响应式布局

    目录 前提条件 使用容器的问题 展开式小组件的介绍 灵活小组件的介绍 设置一个示例应用程序 代码执行 扩展的小部件例子 灵活部件的例子 扩大的和灵活的部件之间的区别 总结 构建响应式屏幕布局意味着编写一段代码,以响应设备布局的各种变化,因此应用程序会根据设备的屏幕尺寸和形状显示其UI. 在这篇文章中,我们将探讨Flutter中用于屏幕响应的扩展和灵活部件. 由于Flutter的跨平台.单一代码库的能力,了解屏幕管理以防止像柔性溢出错误或糟糕的用户界面设计这样的问题是至关重要的. 我们还将设计一个

  • Flutter状态管理Bloc之登录示例

    本文实例为大家分享了Flutter状态管理Bloc之登录的具体代码,供大家参考,具体内容如下 1. 依赖 dependencies:   flutter_bloc: ^2.1.1   equatable: ^1.0.1 2. UserRepository 用于管理用户数据 提供认证方法,删除Token,保存Token,是否包含Token四个方法. import 'package:flutter/material.dart';   /// 用户数据仓库 class UserRepository {

  • 详解Flutter 响应式状态管理框架GetX

    目录 一.状态管理框架对比 Provider BLoC GetX 二.基本使用 2.1 安装与引用 2.2 使用GetX改造Counter App 2.3 GetX代码插件 三.其他功能 3.1 路由管理 3.2 依赖关系管理 3.3 工具 3.4 改变主题 3.5 GetConnect 3.6 GetPage中间件 Priority Redirect onPageCalled OnBindingsStart OnPageBuildStart 3.7 全局设置和手动配置 3.8 StateMix

  • 一文详解Vue3响应式原理

    目录 回顾 vue2.x 的响应式 vue3的响应式 Reflect 回顾 vue2.x 的响应式 实现原理: 对象类型:通过object.defineProperty()对属性的读取.修改进行拦截(数据劫持) 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹) Object.defineProperty(data,'count ",{ get(){}, set(){} }) 存在问题: 新增属性.删除属性,界面不会更新 直接通过下标修改数组,界面不会自动更新 但是

  • 详解Angular2响应式表单

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将解释响应式表单并用来创建一个英雄详情编辑器. 包含内容: 响应式表单介绍 开始搭建 创建数据模型 创建响应式的表单组件 创建组建的模板文件 引入ReactiveFormsModule 显示HeroDetailComponent 添加一个FormGroup 看看表单模型 介绍FormBuilder 验证的需求 放

  • 代码详解Vuejs响应式原理

    响应式原理 > vuejs中的模型(model)和视图(view)是保持同步的,在修改数据的时候会自动更新视图,这其实依赖于Object.defineProperty方法,所以vuejs不支持IE8及以下版本,vuejs通过劫持getter/setter方法来监听数据的变化,通过getter进行依赖收集,在数据变更执行setter的时候通知视图更新. Object.defineProperty > Object.defineProperty可以定义对象的属性或修改对象的属性 > 目前可以

  • 详解VUE响应式原理

    目录 1.响应式原理基础 2.核心对象:Dep与Watcher 3.收集依赖与更新依赖 3.1 收集依赖 3.2 更新依赖 4.源码调试 4.1 测试的页面代码 1.对象说明 2.Dep与Watcher的关系 3.最终的关系结果 4.2  源码调试 1.收集依赖的入口函数:initState(页面初始化时执行); 2.初始化computed和watch时,生成Watcher实例化对象 总结 1.响应式原理基础 响应式基本原理是基于Object.defineProperty(obj, prop,

  • 详解vue3 响应式的实现原理

    目录 核心设计思想 Vue.js 2.x 响应式 Vue.js 3.x 响应式 依赖收集:get 函数 派发通知:set 函数 总结 源码参考 核心设计思想 除了组件化,Vue.js 另一个核心设计思想就是响应式.它的本质是当数据变化后会自动执行某个函数,映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染.响应式是 Vue.js 组件化更新渲染的一个核心机制. Vue.js 2.x 响应式 我们先来回顾一下 Vue.js 2.x 响应式实现的部分: 它在内部通过 Object.defi

  • 详解Flutter Widget

    目录 概述: Widget的本质: 分类: Widget StatelessWidget StatefulWidget State ParentDataWidget RenderObjectWidget 小结 概述: 所有的一切都可以被称为widget 在开发 Flutter 应用过程中,接触最多的无疑就是Widget,是『描述』 Flutter UI 的基本单元,通过Widget可以做到: 描述 UI 的层级结构 (通过Widget嵌套): 定制 UI 的具体样式 (如:font.color等

  • 详解Flutter中网络框架dio的二次封装

    其实dio框架已经封装的很好了,但是在实战项目中,为了项目可以统一管理,还是需要对dio框架进行二次封装. 整体思路:一般情况下,后台返回的数据我们可以分为两部分 1.状态数据 2.渲染数据 状态数据就是接口有没有正常返回数据相关的数据,这部分数据跟业务无关,我们可以封装起来统一管理,渲染数据就是我们渲染页面所需要的数据,这块的数据需要我们自己处理. 接下来我们就主要处理渲染数据这块的内容,我定义了两个函数,渲染数据可能为一个对象或者一个数组,我做了分别处理,定义两个函数来接受渲染数据. //

  • 详解Flutter手游操纵杆移动的原理与实现

    目录 前言 基本思路 绘制 静态效果 添加手势交互 GestureDetector 总结 前言 上一篇介绍了手势在画布上的应用,那么手势与绘制画布究竟能摩擦出怎样的火花呢,本篇文章将为你详解手游中操纵杆移动角色的的原理与实现过程. 基本思路 确定操纵杆区域,确定点击时手势响应区域,当手指滑动操纵杆时,计算出当前的手指位置与当前操纵杆圆心偏移弧度,从而确定当前角色的移动方向.接下来就一步一步实现吧. 绘制 绘制操纵杆的静态图形,玩过手游应该知道操纵杆基本构成由底部圆和手指移动圆球组成,手指移动的圆

  • 详解SQL Server数据库状态和文件状态

    数据库状态 (database states) 查询数据库的当前状态 : 1.查询所有数据库的状态 ,通过sys.databases目录视图的state_desc列 user master go select state_desc ,[name] from sys.databases go 2.查询指定数据库的状态,通过DATABASEPROPERTYEX函数的Status属性 select DATABASEPROPERTYEX('demoData','status') go 状态: ONLIN

随机推荐