GetX入门和使用

1,781 阅读16分钟

GetX介绍

image.png

pub-web.flutter-io.cn/packages?so…

image.png

关于Get

github中文文档:github.com/jonataslaw/…

  • GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。

  • GetX 有3个基本原则:

    • 性能:  GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。如果你感兴趣,这里有一个性能测试
    • 效率:  GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
    • 结构:  GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。
  • GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。

  • Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。 通过Get Server 可以在你的后端完全重用你在前端写的代码。

此外,通过Get CLI,无论是在服务器上还是在前端,整个开发过程都可以完全自动化。

当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组 件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用Flutter中的状态管理来管 理统一的状态(数据),实现不同组件之间的传值和数据共享。

现在Flutter的状态管理方案很多,redux、bloc、state、provider、Getx、riverpod、MobX。

provider是官方提供的状态管理解决方案,主要功能就是状态管理。Getx是第三方的状态管理插件, 不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、网络请 求、数据验证等功能,相比其他状态管理插件Getx 简单、功能强大并且高性能。

image.png

使用GetX

将 GetX 添加到你的 pubspec.yaml 文件中。

dependencies:
  get: ^4.3.8

在需要用到的文件中导入,它将被使用。

import 'package:get/get.dart';

image.png

Snackbar

如果想在应用程序中触发某些特定的事件后,需要弹出一则快捷消息,那么使用Snackbar则是最佳的选择

getx_snackbar.gif

Snackbar基本使用

第一步:应用程序入口设置

当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示

import 'package:flutter/material.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: Scaffold(
        appBar: AppBar(title: Text("GetX Title"),),
      ),
    );
  }
}

第二步:调用snackbar

我们可以通过Get.snackbar() 来显示 snackbar ,如下所示

import 'package:flutter/material.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: Scaffold(
        appBar: AppBar(
          title: Text("GetX Title"),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () {
                  Get.snackbar("Snackbar 标题", "欢迎使用Snackbar");
                },
                child: Text("显示 Snackbar"))
            ],
          ),
        ),
      ),
    );
  }
}

Snackbar属性和说明

总共38个属性,

字段属性描述
titleString弹出的标题文字
messageString弹出的消息文字
colorTextColortitle和message的文字颜色
durationDurationSnackbar弹出的持续时间(默认3秒)
instantInitbool当false可以把snackbar 放在initState,默认true
snackPositionSnackPosition弹出时的位置,有两个选项【TOP,BOTTOM】默认TOP
titleTextWidget弹出标题的组件,设置该属性会导致title属性失效
messageTextWidget弹出消息的组件,设置该属性会导致messageText属性失效
iconWidget弹出时图标,显示在title和message的左侧
shouldIconPulsebool弹出时图标是否闪烁,默认false
maxWidthdoubleSnackbar最大的宽度
marginEdgeInsetsSnackbar外边距,默认zero
paddingEdgeInsetsSnackbar内边距,默认EdgeInsets.all(16)
borderRadiusdouble边框圆角大小,默认15
borderColorColor边框的颜色,必须设置borderWidth,否则无效果
borderWidthdouble边框的线条宽度
backgroundColorColorSnackbar背景颜色,默认Colors.grey.withOpacity(0.2)
leftBarIndicatorColorColor左侧指示器的颜色
boxShadowsListSnackbar阴影颜色
backgroundGradientGradient背景的线性颜色
mainButtonTextButton主要按钮,一般显示发送、确认按钮
onTapOnTap点击Snackbar事件回调
isDismissiblebool是否开启Snackbar手势关闭,可配合dismissDirection使用
showProgressIndicatorbool是否显示进度条指示器,默认false
dismissDirectionSnackDismissDirectionSnackbar关闭的方向
progressIndicatorControllerAnimationController进度条指示器的动画控制器
progressIndicatorBackgroundColorColor进度条指示器的背景颜色
progressIndicatorValueColorAnimation进度条指示器的背景颜色,Animation
snackStyleSnackStyleSnackbar是否会附加到屏幕边缘
forwardAnimationCurveCurveSnackbar弹出的动画,默认Curves.easeOutCirc
reverseAnimationCurveCurveSnackbar消失的动画,默认Curves.easeOutCirc
animationDurationDurationSnackbar弹出和小时的动画时长,默认1秒
barBlurdoubleSnackbar背景的模糊度
overlayBlurdouble弹出时的毛玻璃效果值,默认0
snackbarStatusSnackbarStatusCallbackSnackbar弹出或消失时的事件回调(即将打开、已打开、即将关闭、已关闭)
overlayColorColor弹出时的毛玻璃的背景颜色
userInputFormForm用户输入表单

底部弹出:

image.png

image.png

自定义弹出内容:

image.png

image.png

Dialog

Dialog 底层其实是对AlertDialog进行了封装, 一般用于二次确认的弹出框,比如当点击某个按钮提交资料时,需要用户二次确认,以防止误操作。

Dialog使用

第一步:应用程序入口设置

当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DialogExample/DialogExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: DialogExample(),
    );
  }
}

第二步:调用Dialog

我们可以通过Get.defaultDialog() 来显示 dialog ,如下所示

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class DialogExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Get.defaultDialog();
              },
              child: Text("显示 Dialog"))
          ],
        ),
      ),
    );
  }
}

get_dialog.gif

Dialog属性和说明

总共25个属性

字段属性描述
titleString弹出的标题,默认(Alert)
titlePaddingEdgeInsetsGeometry标题的内边距,默认(EdgeInsets.all(8))
titleStyleTextStyle标题的样式
middleTextString中间内容区域显示的文字
middleTextStyleTextStyle中间内容区域显示的文字样式
contentWidget弹出的内容,该值设置后middleText将无效
contentPaddingEdgeInsetsGeometry内容的内边距,默认(EdgeInsets.all(8))
onConfirmVoidCallback确认按钮回调
onCancelVoidCallback取消按钮回调
onCustomVoidCallback自定义按钮回调
cancelTextColorColor取消按钮文字的颜色
confirmTextColorColor确认按钮文字的颜色
textConfirmString确认按钮的文字
textCancelString取消按钮的文字
textCustomString自定义按钮的文字
confirmWidget确认按钮的组件
cancelWidget取消按钮的组件
customWidget自定义按钮的组件
backgroundColorColor弹出框的背景颜色
barrierDismissiblebool是否可以通过点击背景关闭弹窗
buttonColorColor按钮的文字颜色,根据按钮类型来设定不同的位置
radiusdouble弹出框的圆角大小,默认20
actionsList增加额外的子组件
onWillPopWillPopCallback拦截关闭之前做一些操作
navigatorKeyGlobalKey用于打开对话框的key

默认的内容文字:

image.png

自定义样式:

image.png

BottomSheet

BottomSheet 是底部弹出的一个组件,常用于单选、验证码二次校验弹窗等,GetXBottomSheet底部弹出是自定义通过路由push的方法实现底部弹窗的一个效果。

第一步:应用程序入口设置

当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DialogExample/DialogExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: DialogExample(),
    );
  }
}

第二步:调用BottomSheet

我们可以通过Get.bottomSheet() 来显示 BottomSheet ,如下所示

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class BottomSheetExample extends StatelessWidget {
  GlobalKey<NavigatorState> _navKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(onPressed: () {
              Get.bottomSheet(
                Container(
                  child: Wrap(
                    children: [
                      ListTile(
                        leading: Icon(Icons.wb_sunny_outlined),
                        title: Text("白天模式"),
                        onTap: () {
                          Get.changeTheme(ThemeData.light());
                        },
                      ),
                      ListTile(
                        leading: Icon(Icons.wb_sunny),
                        title: Text("黑夜模式"),
                        onTap: () {
                          Get.changeTheme(ThemeData.dark());
                        },
                      )
                    ],
                  ),
                )
              );
            }, child: Text("Bottom Sheet"))
          ],
        ),
      ),
    );
  }
}

切换白天主题颜色: Get.changeTheme(ThemeData.light());

切换黑夜主题颜色: Get.changeTheme(ThemeData.dark());

get_bottomsheet1.gif

BottomSheet属性和说明

字段属性描述
bottomsheetWidget弹出的Widget组件
backgroundColorColorbottomsheet的背景颜色
elevationdoublebottomsheet的阴影
persistentbool是否添加到路由中
shapeShapeBorder边框形状,一般用于圆角效果
clipBehaviorClip裁剪的方式
barrierColorColor弹出层的背景颜色
ignoreSafeAreabool是否忽略安全适配
isScrollControlledbool是否支持全屏弹出,默认false
useRootNavigatorbool是否使用根导航
isDismissiblebool点击背景是否可关闭,默认ture
enableDragbool是否可以拖动关闭,默认true
settingsRouteSettings路由设置
enterBottomSheetDurationDurationbottomsheet进入时的动画时间
exitBottomSheetDurationDurationbottomsheet退出时的动画时间

image.png

Navigation路由管理

使用GetX 进行路由跳转非常的简单,只需要调用Get.to()即可进行路由跳转,而系统的路由跳转需要写八行代码,这是不能忍受的事情,而且涉及到跳转动画设置 、动画时长定义动画曲线 等设置那就更加的复杂,而GetX为我们封装了Navigation,无需context可进行跳转,并且能很方便的使用跳转动画等。

应用程序入口设置:

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/NavigationForNamedExample/NavigationForNamedExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      initialRoute: "/",
      defaultTransition: Transition.zoom,
      getPages: [
        GetPage(name: "/", page: () => MyApp()),
        GetPage(name: "/home", page: () => Home()),
        GetPage(name: "/my", page: () => My(), transition: Transition.rightToLeft)
      ],
      home: NavigationForNamedExample(),
    );
  }
}

image.png

通过 Get.to方法进行路由跳转:

image.png

通过 Get.toNamed方法进行路由跳转:

image.png

以前:

Navigator.pushNamed(context, "/login");

通过 Get.back方法进行路由跳转:

image.png

以前:

Navigator.of(context).pop();

通过 Get.agruments获取路由传参

image.png

getx_navigation_to.gif

Get.offAll(); 返回到根页面

以前:

Navigator.of(context).pushAndRemoveUntil(
    MaterialPageRoute(builder: (BuildContext context) {
    return const Tabs(index: 4);
}), (route) => false);

使用Getx后:

Get.offAll( const Tabs(index: 4));

Get.off(NextScreen());

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

Get.off(NextScreen());

路由中间件 路由拦截

新建shopMiddleware.dart:

import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
class ShopMiddleWare extends GetMiddleware {
    @override
    // 优先级越低越先执行
    int? get priority => 1;
    
   // 当被调用路由的页面被搜索时,这个函数将被调用。它将RouteSettings作为重定向的结果。或者给它null,就没有重定向了。
    @override
    RouteSettings redirect(String ? route){
        final authService = Get.find<AuthService>();
        return authService.authed.value ? null : RouteSettings(name: '/login')
    }
    
    // 在调用页面时,创建任何东西之前,这个函数会先被调用。 您可以使用它来更改页面的某些内容或给它一个新页面。
    @override
    GetPage onPageCalled(GetPage page) {
      final authService = Get.find<AuthService>();
      return page.copyWith(title: 'Welcome ${authService.UserName}');
    }
    
    // 这个函数将在绑定初始化之前被调用。 在这里,您可以更改此页面的绑定。
    @override
    List<Bindings> onBindingsStart(List<Bindings> bindings) {
      final authService = Get.find<AuthService>();
      if (authService.isAdmin) {
        bindings.add(AdminBinding());
      }
      return bindings;
    }
    
    // 这个函数将在绑定初始化之后被调用。 在这里,您可以在创建绑定之后和创建页面widget之前执行一些操作。
    @override
    GetPageBuilder onPageBuildStart(GetPageBuilder page) {
      print('bindings are ready');
      return page;
    }
    
    // OnPageBuilt
    // 这个函数将在GetPage.page调用后被调用,并给出函数的结果,并获取将要显示的widget。

    // OnPageDispose
    // 这个函数将在处理完页面的所有相关对象(Controllers, views, ...)之后被调用。
}

GetPage配置路由

return GetMaterialApp(
    ...
    initialRoute: "/",
    defaultTransition: Transition.rightToLeftWithFade,
    getPages: [
    GetPage(name: "/", page: () => const Tabs()),
    ...
    GetPage(
        name: "/shop",
        page: () => const ShopPage(),
        middlewares: [ShopMiddleWare()]
        ),
    ],
);

flutter状态管理的底层原理

  1. InheritedWidget

  2. Notification

  3. StreamBuilder

InheritedWidget

专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发。

Provider就是对InheritedWidget的包装,只是让InheritedWidget用起来更加简单且高可复用。我们也知道它有一些缺点

  • 容易造成不必要的刷新,使用selector进行优化
  • 不支持跨页面(route)的状态,意思是跨树,如果不在一个树中,我们无法获取
  • 数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用

image.png

InheritedWidget和React中的context功能类似,可以实现跨组件数据的传递。

定义一个共享数据的InheritedWidget,需要继承自InheritedWidget

  • 这里定义了一个of方法,该方法通过context开始去查找祖先的HYDataWidget(可以查看源码查找过程)
  • updateShouldNotify方法是对比新旧HYDataWidget,是否需要对更新相关依赖的Widget
class HYDataWidget extends InheritedWidget {
  finalint counter;

  HYDataWidget({this.counter, Widget child}): super(child: child);

  static HYDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType();
  }

  @override
  bool updateShouldNotify(HYDataWidget oldWidget) {
    returnthis.counter != oldWidget.counter;
  }
}

创建HYDataWidget,并且传入数据(这里点击按钮会修改数据,并且重新build)

class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {
  int data = 100;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedWidget"),
      ),
      body: HYDataWidget(
        counter: data,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              HYShowData(showdata:data)
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            data++;
          });
        },
      ),
    );
  }
}

在某个Widget中使用共享的数据,并且监听

Provider的基本使用

Provider是目前官方推荐的全局状态管理工具,由社区作者Remi Rousselet 和 Flutter Team共同编写。

引入依赖:

image.png

创建一个ChangeNotifier来保存我们的状态

  • 这里我们可以使用继承自ChangeNotifier,也可以使用混入,这取决于概率是否需要继承自其它的类
  • 我们使用一个私有的_counter,并且提供了getter和setter
  • 在setter中我们监听到_counter的改变,就调用notifyListeners方法,通知所有的Consumer进行更新

image.png

在Widget Tree中插入ChangeNotifierProvider

我们需要在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以获取到数据:

  • 将ChangeNotifierProvider放到了顶层,这样方便在整个应用的任何地方可以使用CounterProvider

image.png

在首页中使用Consumer引入和修改状态

  • 引入位置一:在body中使用Consumer,Consumer需要传入一个builder回调函数,当数据发生变化时,就会通知依赖数据的Consumer重新调用builder方法来构建;
  • 引入位置二:在floatingActionButton中使用Consumer,当点击按钮时,修改CounterNotifier中的counter数据;

image.png

通过Provider.of的方式来使用

image.png

MultiProvider使用多个状态管理数据

image.png

使用Selector进行优化,shouldRebuild返回false不进行rebuild

image.png

Notification

与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但两者都在自己的Widget树中传递,无法跨越树传递。

它是Flutter中跨层数据共享的一种机制,它不是widget,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知

image.png

  • 定义TestNotification通知的实现
  • WidgetNotification 负责通知结果,通过RaisedButton的点击事件,将数据a传递出去,通过Notification提供的dispatch方法向上传递
  • WidgetListener通过Widget NotificationListener来监听数据变化,最终通过setState变更数据
  • WidgetNotification 实例化了两次,一次在NotificationListener的树内部,一个在NotificationListener的外部,经过测试发现,在外部的WidgetNotification并不能通知到内容变化。
class TestNotification extends Notification {
  final int test;
 
  TestNotification(this.test);
}
 
var a = 0;
 
// ignore: must_be_immutable
class WidgetNotification extends StatelessWidget {
 
  final String btnText;
 
  WidgetNotification({Key key, this.btnText}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Container(
      child: RaisedButton(
        child: Text(btnText),
        onPressed: () {
          var b = ++a;
          debugPrint(b.toString());
          TestNotification(b).dispatch(context);
        },
      ),
    );
  }
}
 
class WidgetListener extends StatefulWidget {
  @override
  _WidgetListenerState createState() => _WidgetListenerState();
}
 
class _WidgetListenerState extends State {
  int _test = 1;
 
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          NotificationListener(
            child: Column(
              children: [
                Text("监听$_test"),
                WidgetNotification(btnText: "子Widget",)
              ],
            ),
            onNotification: (TestNotification notification) {
              setState(() {
                _test = notification.test;
              });
              return true;
            },
          ),
          WidgetNotification(btnText: "非子Widget",)
        ],
      ),
    );
  }
}

StreamBuilder

Stream就是个事件流,有点类似于安卓开发中的RxJava,它允许我们从一端发射一个事件,从另外一端去监听事件的变化,通过Stream我们可以在Flutter上设计出基于事件流驱动的响应式代码逻辑。

StreamDart 中的一个核心部分,它用于接收异步事件序列,这些事件可以包括数据消息或错误。
StreamBuilder 则是一个 widget,它能够根据最新的 Stream快照 (snapshot) 来重建自己。

StreamBuild从字面意思来讲是数据流构建,是一种基于数据流的订阅管理。Stream可以接受任何类型的数据,值、事件、对象、集合、映射、错误、甚至是另一个Stream,通过StreamController中的sink作为入口,往Stream中插入数据,然后通过你的自定义监听StreamSubscription对象,接受数据变化的通知。如果你需要对输出数据进行处理,可以使用StreamTransformer,它可以对输出数据进行过滤、重组、修改、将数据注入其他流等等任何类型的数据操作。

Stream有两种类型:单订阅Stream和广播Stream。单订阅Stream只允许在该Stream的整个生命周期内使用单个监听器,即使第一个subscription被取消了,你也没法在这个流上监听到第二次事件;而广播Stream允许任意个数的subscription,你可以随时随地给它添加subscription,只要新的监听开始工作流,它就能收到新的事件。

image.png

单订阅类型实例

import 'dart:async';

void main() {
  // 初始化一个单订阅的Stream controller
  final StreamController ctrl = StreamController();
  
  // 初始化一个监听
  final StreamSubscription subscription = ctrl.stream.listen((data) => print('$data'));

  // 往Stream中添加数据
  ctrl.sink.add('my name');
  ctrl.sink.add(1234);
  ctrl.sink.add({'a': 'element A', 'b': 'element B'});
  ctrl.sink.add(123.45);
  
  // StreamController用完后需要释放
  ctrl.close();
}

广播类stream

import 'dart:async';

void main() {
  // 初始化一个int类型的广播Stream controller
  final StreamController<int> ctrl = StreamController<int>.broadcast();
  
  // 初始化一个监听,同时通过transform对数据进行简单处理
  final StreamSubscription subscription = ctrl.stream
                          .where((value) => (value % 2 == 0))
                          .listen((value) => print('$value'));

  // 往Stream中添加数据
  for(int i=1; i<11; i++){
    ctrl.sink.add(i);
  }
  
  // StreamController用完后需要释放
  ctrl.close();
}

使用streamBuild

  StreamBuilder<T>(
    key: ...可选...
    stream: ...需要监听的stream...
    initialData: ...初始数据,尽量不要填null...
    builder: (BuildContext context, AsyncSnapshot<T> snapshot){
        if (snapshot.hasData){
            return ...基于snapshot.hasData返回的控件
        }
        return ...没有数据的时候返回的控件
    },
)

下面是一个模仿官方自带demo“计数器”的一个例子,使用了StreamBuilder,而不需要任何setState:

import 'dart:async';
import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;
  //步骤1:初始化一个StreamController<任何数据> 简单的可以扔一个int,string,开发中经常扔一个网络请求的model进去,具体看你使用场景了。
  final StreamController<int> _streamController = StreamController<int>();

  @override
  void dispose(){
  //步骤2.关流,不管流会消耗资源,同时会引起内存泄漏
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream version of the Counter App')),
      body: Center(
      //步骤3.使用StreamBuilder构造器
        child: StreamBuilder<int>(  // 监听Stream,每次值改变的时候,更新Text中的内容
          stream: _streamController.stream,
          initialData: _counter,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot){
            return Text('You hit me: ${snapshot.data} times');
          }
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          // 每次点击按钮,更加_counter的值,同时通过Sink将它发送给Stream;
          // 每注入一个值,都会引起StreamBuilder的监听,StreamBuilder重建并刷新counter
          //步骤4.往StreamBuilder里添加流,数据变了,就用通知小部件
          _streamController.sink.add(++_counter);
        },
      ),
    );
  }
}

image.png

点击“关注”变为“已关注”
1.这个item是StatefulWidget,点击“关注”,然后setstate(){}
2.使用其他的状态管理去实现。 3.使用 StreamBuild 实现。

import 'dart:async';
import 'package:easy_alert/easy_alert.dart';
import 'package:flutter/material.dart';
import 'package:hongka_flutter/app/Manager/IO/hk_request.dart';
import 'package:hongka_flutter/app/Manager/api/ConfigApi.dart';
import 'package:hongka_flutter/app/Modules/basemodel/focuseItemModel.dart';
import 'package:hongka_flutter/app/Modules/home/info_organization.dart';
//我会省略部分代码,并且注释使用步骤
//步骤一:使用 StatefulWidget,为何要用StatefulWidget?待会解释
class FollowsItem extends StatefulWidget {
  FocuseItemModel focusItemModel;
  String focusType; //不为空就是关注界面,隐藏关注按钮
  int index;
  String studentId;

  FollowsItem(
      {Key key,
      this.focusItemModel,
      this.focusType,
      this.index,
      this.studentId});

  @override
  _FollowsItemState createState() => _FollowsItemState();
}

class _FollowsItemState extends State<FollowsItem> {
  FocuseItemModel focusItemModel;
  String focusType; //不为空就是关注界面,隐藏关注按钮
  String headerImage;
  double screenWidth, marginLeft = 15.0, marginRight = 15.0, marginAll = 30.0;
  int index;
  String studentId;
  //步骤二:声明StreamController
  StreamController<FocuseItemModel> _streamController;

  @override
  void initState() {
    super.initState();
    this.focusItemModel = this.widget.focusItemModel;
    this.focusType = this.widget.focusType;
    this.index = this.widget.index;
    this.studentId = this.widget.studentId;
    //步骤三实现 StreamController<FocuseItemModel>,FocuseItemModel是我的实体类
    _streamController = StreamController<FocuseItemModel>.broadcast();
    //步骤四将数据添加到 _streamController
    _streamController.sink.add(focusItemModel);
  }

  @override
  void dispose() {
  //步骤五:关流
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        _clickItem(context);
      },
      child: Container(
        .....省略无关UI代码
        child: Stack(
          children: <Widget>[
            .....省略无关UI代码
            Positioned(
              right: 0,
              top: 0,
              child: Offstage(
                  offstage: !(focusType == null),
                  child: GestureDetector(
                    onTap: () {
                    //已关注return
                      if (focusItemModel.focusStatus == 1) {
                        return;
                      }
                      //未关注进行网络请求
                      focusOrganizationRequest(focusItemModel.positionId);
                    },
                    //步骤五:使用StreamBuilder构建
                    child: StreamBuilder<FocuseItemModel>(
                        stream: _streamController.stream,//数据流
                        initialData: focusItemModel,//初始值
                        builder: (BuildContext context,
                            AsyncSnapshot<FocuseItemModel> snapshot) {
                          return Image.asset(
                            (snapshot?.data?.focusStatus ?? 0) == 1
                                ? 'images/view/recommend_flowered.png'
                                : 'images/view/recommend_flower.png',
                            width: 54,
                            height: 40,
                          );
                        }),
                  )),
            )
          ],
        ),
      ),
    );
  }

  ///关注红广号
  Future focusOrganizationRequest(String positionId) async {
    String url = 'xxxx';
    var data = {
      'studentId': studentId,
      'positionId': positionId,
    };
    var options = await HK_Request().getRequestOptions();
    var response = await HK_Request()
        .post(url, context, isShowLoading: true, data: data, options: options);
    print(response.toString());
    if (response['status'] == 200) {
    //步骤六:改变model值,并将model 扔到_streamController里,此时数据改变,通知小部件,重新构建
      focusItemModel.focusStatus = 1;
      _streamController.sink.add(focusItemModel);
      Alert.toast(context, '关注成功!',
          position: ToastPosition.center, duration: ToastDuration.short);
      print('关注红广号成功' + response.toString());
    
    } else {
      print('关注红广号错误');
      Alert.toast(context, response['msg'],
          position: ToastPosition.center, duration: ToastDuration.short);
    }
  }

GetX Obx响应式状态管理

介绍

响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。

  • 你不需要创建StreamControllers.
  • 你不需要为每个变量创建一个StreamBuilder。
  • 你不需要为每个状态创建一个类。
  • 你不需要为一个初始值创建一个get。

使用 Get 的响应式编程就像使用 setState 一样简单。

定义Obx变量的三种方式

// 第一种 使用 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>({});

// 第二种是使用 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>();

// 第三种更实用、更简单、更可取的方法,只需添加 .obs 作为value的属性。
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
inal items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 自定义类 - 可以是任何类
final user = User().obs;

计数器案例

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCountExample/ObxCountExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: ObxCountExample(),
    );
  }
}

第二步:声明Rx变量以及改变计数器的方法

RxInt count = RxInt(0);
// var count = Rx<double>(0);
// var count = 0.obs;

void increment() {
  count++;
}

第三步:使用Obx监听值的改变

Obx(() => Text(
  "count的值为: $count",
  style: TextStyle(color: Colors.red, fontSize: 30),
)),

完整代码

import 'dart:ffi';

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class ObxCountExample extends StatelessWidget {
  RxInt count = RxInt(0);
  // var count = Rx<double>(0);
  // var count = 0.obs;

  void increment() {
    count++;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Obx---计数器"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Obx(() => Text(
              "count的值为: $count",
              style: TextStyle(color: Colors.red, fontSize: 30),
            )),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () {
                increment();
              },
              child: Text("点我加1"))
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-a481f6988083d389.webp

自定义类案例

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCustomClassExample/ObxCustomClassExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: ObxCustomClassExample(),
    );
  }
}

第二步:创建Teacher类

import 'package:get/get.dart';

class Teacher {

  // rx 变量
  var name = "Jimi".obs;
  var age = 18.obs;

  // 构造函数创建
  // var name;
  // var age;
  // Teacher({this.name, this.age});
}

第三步:监听Teacher状态改变


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';

class ObxCustomClassExample extends StatelessWidget {

  var teacher = Teacher();

  // final teacher = Teacher(name: "Jimi", age: 18).obs;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Obx---自定义类"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Obx(() => Text(
              "我的名字是 ${teacher.name.value}",
              style: TextStyle(color: Colors.red, fontSize: 30),
            )),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () {
                teacher.name.value = teacher.name.value.toUpperCase();

                // teacher.update((val) {
                //   teacher.value.name = teacher.value.name.toString().toUpperCase();
                // });
              },
              child: Text("转换为大写"))
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-007376acc6d7702b.webp

GetxController依赖注入状态管理

在实际的项目开发过程中,我们不可能把UI代码、业务逻辑都放在一起处理,这样对项目的架构、代码的可读性、后期的维护将会是致命的,在GetX为我们提供了GetxController,GetxController主要的作用是用于UI代码与业务逻辑分离开来。

这里主要讲解使用GetxController动态获取数据的三种方式以及更新数据的方式。

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/GetXControllerExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: GetXControllerExample(),
    );
  }
}

第二步:定义控制器继承自GetxController

import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';

class MyController extends GetxController {

  // 第一种
  // var teacher = Teacher();
  //
  // void convertToUpperCase() {
  //   teacher.name.value = teacher.name.value.toUpperCase();
  // }

  // 第二种
  // var teacher =  Teacher(name: "Jimi", age: 18).obs;
  // void convertToUpperCase() {
  //   teacher.update((val) {
  //     teacher.value.name = teacher.value.name.toString().toUpperCase();
  //   });
  // }

  // 第三种
  var teacher = Teacher();

  void convertToUpperCase() {
    teacher.name.value = teacher.name.value.toUpperCase();
    update();
  }
}

第三步:实例化控制器并使用


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/MyController.dart';
import 'package:get/get.dart';

class GetXControllerExample extends StatelessWidget {

  // 第一种
  MyController myController = Get.put(MyController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Obx---GetXController"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            // 第一种
            // Obx(() => Text(
            //   "我的名字是 ${myController.teacher.name}",
            //   style: TextStyle(color: Colors.red, fontSize: 30),
            // )),
            // 第二种
            // GetX<MyController>(
            //   init: MyController(),
            //   builder: (controller) {
            //     return Text(
            //       "我的名字是 ${controller.teacher.name}",
            //       style: TextStyle(color: Colors.green, fontSize: 30),
            //     );
            //   },
            // ),
            // 第三种
            GetBuilder<MyController>(
              init: myController,
              builder: (controller) {
                return Text(
                  "我的名字是 ${controller.teacher.name}",
                  style: TextStyle(color: Colors.green, fontSize: 30),
                );
              },
            ),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () {
                // 第一种
                myController.convertToUpperCase();

                // 第二种
                // Get.find<MyController>().convertToUpperCase();

              },
              child: Text("转换为大写"))
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-bc758d2fdc04b710.webp

GetxController事件监听

这里主要讲解GetxController的事件监听,包括监听单个值多个值等。

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/GetXControllerWorkersExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: GetXControllerWorkersExample(),
    );
  }
}

第二步:定义控制器继承自GetxController

import 'package:get/get.dart';

class WorkersController extends GetxController {
  var count = 0.obs;

  void increment() {
    count++;
  }
}

第三步:重写onInit并监听事件

@override
void onInit() {
  // TODO: implement onInit

  // 监听count的值,当它发生改变的时候调用
  ever(count, (callback) => print("ever----$count"));

  // 监听多个值,当它们发生改变的时候调用
  everAll([count], (callback) => print("everAll----$count"));

  // count值改变时调用,只执行一次
  once(count, (callback) => print("once----$count"));

  // 用户停止打字时1秒后调用,主要是防DDos
  debounce(count, (callback) => print("debounce----$count"));

  // 忽略3秒内的所有变动
  interval(count, (callback) => print("interval----$count"));

  super.onInit();
}

第四步:实例化控制器并使用


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/WorkersConroller.dart';
import 'package:get/get.dart';

class GetXControllerWorkersExample extends StatelessWidget {

  WorkersController workersController = Get.put(WorkersController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetXWorkersController"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
                onPressed: () => workersController.increment(),
                child: Text("增加")
            ),
            Padding(
              padding: EdgeInsets.all(16),
              child: TextField(
                onChanged: (val) {
                  workersController.increment();
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-18868f057624e393.webp

控制台输出结果

flutter: ever----1
flutter: everAll----1
flutter: once----1
[GETX] Worker [once] called
flutter: debounce----1
flutter: interval----1
flutter: ever----2
flutter: everAll----2
flutter: debounce----2
flutter: interval----2
flutter: ever----3
flutter: everAll----3
flutter: ever----4
flutter: everAll----4
flutter: ever----5
flutter: everAll----5
flutter: interval----5
flutter: debounce----5

GetxController生命周期

这里主要讲解GetxController的生命周期,包括初始化加载完成销毁等。

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerLifecycleMethodsExample/GetXControllerLifecycleMethodExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: GetXControllerLifecycleMethodExample(),
    );
  }
}

第二步:定义控制器继承自GetxController

import 'package:get/get.dart';

class MyLifecycleController extends GetxController {

  var count = 0;

  void increment() async {
    await Future.delayed(Duration(milliseconds: 3000));
    count++;
    update();
  }

  void cleanTask() {
    print("清除了任务");
  }
}

第三步:重写GetxController生命周期方法

@override
void onInit() {
  // TODO: implement onInit
  print("初始化");
  super.onInit();
}

@override
void onReady() {
  // TODO: implement onReady
  print("加载完成");
  super.onReady();
}

@override
void onClose() {
  // TODO: implement onClose
  print("控制器被释放");
  super.onClose();
}

第四步:实例化控制器并使用


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerLifecycleMethodsExample/MyLifecycleController.dart';
import 'package:get/get.dart';

class GetXControllerLifecycleMethodExample extends StatelessWidget {

  MyLifecycleController myLifecycleController = Get.put(MyLifecycleController());

  @override
  Widget build(BuildContext context) {
    print("build");

    return Scaffold(
      appBar: AppBar(
        title: Text("GetXControllerLifecycleMethod"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            GetBuilder<MyLifecycleController>(
              initState: (data) => myLifecycleController.increment(),
              dispose: (_) => myLifecycleController.cleanTask(),
              builder: (controller) {
                return Text(
                  "计数器值为: ${myLifecycleController.count}",
                  style: TextStyle(color: Colors.green, fontSize: 30),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-94dcf2a9b649e63d.webp

控制台输出结果

flutter: 初始化
[GETX] Instance "MyLifecycleController" has been created
[GETX] Instance "MyLifecycleController" has been initialized
[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized
flutter: build
flutter: 加载完成
flutter: build
flutter: build

GetxController UniqueID

我们在开发的过程中会碰到一种情况,就是多个地方引用了同一个属性,但我只想单独更新某一个地方,那么就可以用UniqueID来进行区分。

第一步:应用程序入口设置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerUniqueIDExample/GetXControllerUniqueIDExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: GetXControllerUniqueIDExample(),
    );
  }
}

第二步:定义控制器继承自GetxController,并且定义uniqueID

import 'package:get/get.dart';

class CountController extends GetxController {
  var count = 0;

  void increment() {
    count++;
    update(['jimi_count']);
  }
}

第三步:实例化控制器并使用


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerUniqueIDExample/CountConroller.dart';
import 'package:get/get.dart';

class GetXControllerUniqueIDExample extends StatelessWidget {

  CountController countController = Get.put(CountController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Obx---GetXController"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            GetBuilder<CountController>(
              builder: (controller) {
                return Text(
                  "计数器值为: ${controller.count}",
                  style: TextStyle(color: Colors.red, fontSize: 30),
                );
              },
            ),
            GetBuilder<CountController>(
              id: 'jimi_count',
              builder: (controller) {
                return Text(
                  "计数器值为: ${controller.count}",
                  style: TextStyle(color: Colors.green, fontSize: 30),
                );
              },
            ),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () => countController.increment(),
              child: Text("增加"))
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-4869f9aea252ce1d.webp

GetX还提供很多创建实例的方法,可根据不同的业务来进行创建

  • Get.put(): 不使用控制器实例也会被创建
  • Get.lazyPut(): 懒加载方式创建实例,只有在使用时才创建
  • Get.putAsync(): Get.put()的异步版版本
  • Get.create(): 每次使用都会创建一个新的实例

我们来看一下代码演示

第一步:应用程序入口配置

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DependecyInjectionExample/DependecyInjectionExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: DependecyInjectionExample(),
    );
  }
}

第二步:创建控制器

import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';

class MyController extends GetxController {
  var teacher = Teacher();
  
  void convertToUpperCase() {
     teacher.name.value = teacher.name.value.toUpperCase();
  }
}

第三步:实例化控制器并使用


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/MyController.dart';
import 'package:get/get.dart';

class DependecyInjectionExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    // 即使不使用控制器实例也会被创建
    // tag将用于查找具有标签名称的实例
    // 控制器在不使用时被处理,但如果永久为真,则实例将在整个应用程序中保持活动状态
    MyController myController = Get.put(MyController(), permanent: true);
    // MyController myController = Get.put(MyController(), tag: "instancel", permanent: true);

    // 实例将在使用时创建
    // 它类似于'permanent',区别在于实例在不被使用时被丢弃
    // 但是当它再次需要使用时,get 将重新创建实例
    // Get.lazyPut(()=> MyController());
    // Get.lazyPut(()=> MyController(), tag: "instancel");

    // Get.put 异步版本
    // Get.putAsync<MyController>(() async  => await MyController());

    // 每次都将返回一个新的实例
    // Get.create<MyController>(() => MyController());

    return Scaffold(
      appBar: AppBar(
        title: Text("GetXController"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                // 实例使用的tag创建
                // Get.find<MyController>(tag: 'instancel');

                Get.find<MyController>();
              },
              child: Text("别点我"))
          ],
        ),
      ),
    );
  }
}

Get Service

这个类就像一个 GetxController,它共享相同的生命周期onInit()onReady()onClose()。 但里面没有 "逻辑"。它只是通知GetX的依赖注入系统,这个子类不能从内存中删除。所以如果你需要在你的应用程序的生命周期内对一个类实例进行绝对的持久化,那么就可以使用GetxService

image.png

第一步:创建Service

import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';

class Service extends GetxService {
    
  Future<void> getCounter() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    int count = (prefs.getInt("counter") ?? 0) + 1;
    print("count 的值为: $count");
    await prefs.setInt("counter", count);
  }
}

第二步:初始化Service

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXServiceExample/GetXServiceExample.dart';
import 'package:flutter_getx_example/GetXServiceExample/Service.dart';
import 'package:get/get.dart';

/// 初始化服务
Future<void> main() async {
  await initServices();
  runApp(MyApp());
}

Future<void> initServices() async {
  print("初始化服务");
  await Get.putAsync(() async => await Service());
  print("所有服务启动");
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: "GetX",
      home: GetXServiceExample(),
    );
  }
}

第三步:调用Service


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXServiceExample/Service.dart';
import 'package:get/get.dart';

class GetXServiceExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Service"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Get.find<Service>().getCounter();
              },
              child: Text("点我加1"))
          ],
        ),
      ),
    );
  }
}

GetX Binding

在我们使用GetX状态管理器的时候,往往每次都是用需要手动实例化一个控制器,这样的话基本页面都需要实例化一次,这样就太麻烦了,而Binding 能解决上述问题,可以在项目初始化时把所有需要进行状态管理的控制器进行统一初始化,接下来看代码演示:

第一步:声明需要进行的绑定控制器类


import 'package:flutter_getx_example/GetXBindingExample/controller/BindingHomeController.dart';
import 'package:flutter_getx_example/GetXBindingExample/controller/BindingMyController.dart';
import 'package:get/get.dart';

class AllControllerBinding implements Bindings {
  
  @override
  void dependencies() {
    // TODO: implement dependencies
    Get.lazyPut<BindingMyController>(() => BindingMyController());
    Get.lazyPut<BindingHomeController>(() => BindingHomeController());
  }
}



import 'package:get/get.dart';

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


import 'package:get/get.dart';

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

第二步:在项目启动时进行初始化绑定

绑定的方式有多种,在不同的情况下有不同的使用方式,这里介绍一种,如果需要更加详细的介绍,观看视频将会是最佳的选择。

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXBindingExample/binding/AllControllerBinding.dart';
import 'package:flutter_getx_example/GetXBindingExample/GetXBindingExample.dart';
import 'package:get/get.dart';

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


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// GetX Binding
    return GetMaterialApp(
      title: "GetX",
      initialBinding: AllControllerBinding(),
      home: GetXBindingExample(),
    );
  }
}

第三步:在页面中使用状态管理器


import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXBindingExample/BHome.dart';
import 'package:flutter_getx_example/GetXBindingExample/binding/BHomeControllerBinding.dart';
import 'package:flutter_getx_example/GetXBindingExample/controller/BindingMyController.dart';
import 'package:get/get.dart';

class GetXBindingExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetXBinding"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Obx(() => Text(
              "计数器的值为 ${Get.find<BindingMyController>().count}",
              style: TextStyle(color: Colors.red, fontSize: 30),
            )),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () {
                Get.find<BindingMyController>().increment();
              },
              child: Text("点击加1")
            ),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () {
                Get.to(BHome());

                // Get.toNamed("/bHome");

                // Get.to(BHome(), binding: BHomeControllerBinding());
              },
              child: Text("跳转去首页")
            ),
          ],
        ),
      ),
    );
  }
}

通过路由配置为单独的页面状态管理数据:

image.png

效果展示

image.png

新闻案例

网络数据请求模型处理GetXController,用MVC模型来实现新闻案例

效果展示

2225852-31f7857cb1c0fced.webp

请求网络数据

我们新建一个ApiService.dart用于请求网络数据,该数据是一个新闻列表的数据。


import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Models/MovieModel.dart';

class ApiService {

  static Future<List<MovieModel>> fetchMovie() async {
    var response = await Dio().get("http://apis.juhe.cn/fapig/douyin/billboard?type=hot_video&size=50&key=9eb8ac7020d9bea6048db1f4c6b6d028");
    if (response.statusCode == 200) {
      var jsonString = response.data['result'];
      return movieModelFromJson(json.encode(response.data["result"]));
    }
  }

}

定义模型类

我们新建一个MovieModel.dart用来对网络请求回来的数据进行模型转换。

// To parse this JSON data, do
//
//     final movieModel = movieModelFromJson(jsonString);

import 'dart:convert';

List<MovieModel> movieModelFromJson(String str) => List<MovieModel>.from(json.decode(str).map((x) => MovieModel.fromJson(x)));

String movieModelToJson(List<MovieModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class MovieModel {
  MovieModel({
    this.title,
    this.shareUrl,
    this.author,
    this.itemCover,
    this.hotValue,
    this.hotWords,
    this.playCount,
    this.diggCount,
    this.commentCount,
  });

  String title;
  String shareUrl;
  String author;
  String itemCover;
  int hotValue;
  String hotWords;
  int playCount;
  int diggCount;
  int commentCount;

  factory MovieModel.fromJson(Map<String, dynamic> json) => MovieModel(
    title: json["title"],
    shareUrl: json["share_url"],
    author: json["author"],
    itemCover: json["item_cover"],
    hotValue: json["hot_value"],
    hotWords: json["hot_words"],
    playCount: json["play_count"],
    diggCount: json["digg_count"],
    commentCount: json["comment_count"],
  );

  Map<String, dynamic> toJson() => {
    "title": title,
    "share_url": shareUrl,
    "author": author,
    "item_cover": itemCover,
    "hot_value": hotValue,
    "hot_words": hotWords,
    "play_count": playCount,
    "digg_count": diggCount,
    "comment_count": commentCount,
  };
}

使用控制器对数据进行处理

我们对请求回来的网络数据转化为Model,并在请求前增加一个loading,当网络数据请求回来的时候关闭loading,我们来看一下代码


import 'package:flutter_getx_example/GetXApiDataExample/ApiModule/ApiService.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Models/MovieModel.dart';
import 'package:get/get.dart';

class MovieController extends GetxController {

  var isLoading = true.obs;
  // ignore: deprecated_member_use
  var movieList = List<MovieModel>().obs;

  @override
  void onInit() {
    // TODO: implement onInit
    fetchMovie();
    super.onInit();
  }

  void fetchMovie() async {
    try {
      isLoading(true);
      var movie = await ApiService.fetchMovie();
      if (movie != null) {
        movieList.assignAll(movie);
      }
    } finally {
      isLoading(false);
    }
  }
}

在视图层展示列表数据

前面我们对网络请求、模型、数据处理进行了处理,我们最终的目的是需要把数据展示到页面上,那我们接着来看一下视图的代码

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Controller/MovieController.dart';
import 'package:get/get.dart';

class MovieListView extends StatelessWidget {

  final MovieController movieController = Get.put(MovieController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Movie"),
      ),
      body: Obx(() {
        if (movieController.isLoading.value) {
          return Center(
            child: CircularProgressIndicator(),
          );
        } else {
          return ListView.builder(
            itemCount: movieController.movieList.length,
            itemBuilder: (context, index) {
              return Column(
                children: [
                  Row(
                    children: [
                      Container(
                        width: 100,
                        height: 120,
                        margin: EdgeInsets.all(10),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(6),
                          child: Image.network(
                            movieController.movieList[index].itemCover,
                            width: 150,
                            height: 100,
                            fit: BoxFit.fill,
                            // color: Colors.orange,
                            // colorBlendMode: BlendMode.color,
                          ),
                        ),
                      ),
                      Flexible(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.start,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              movieController.movieList[index].author,
                              style: TextStyle(
                                color: Colors.black45,
                                fontSize: 16
                              ),
                            )
                          ],
                        ),
                      ),
                    ],
                  ),
                  Container(
                    color: Colors.grey.shade300,
                    height: 2,
                  )
                ],
              );
            },
          );
        }
      }),
    );
  }

国际化配置

在我们使用系统自带MaterialApp来实现国际化配置,需要进行很多配置,而且还需要手动去依赖第三方组件,而使用GetX来实现国际化配置,你只需要一行代码即可实现切换,接下来我们看一下具体实现。

第一步:应用程序入口配置

  • translations: 国际化配置文件
  • locale: 设置默认语言,不设置的话为系统当前语言
  • fallbackLocale: 配置错误的情况下,使用的语言
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/InternationalizationExample/InternationalizationExample.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// 国际化配置
    return GetMaterialApp(
      title: "GetX",
      translations: Messages(),
      locale: Locale('zh', 'CN'), //设置默认语言
      fallbackLocale: Locale("zh", "CN"), // 在配置错误的情况下,使用的语言
      home: InternationalizationExample(),
    );
  }
}

第二步:创建国际化类

需要继承自Translations并重写keys方法。

import 'package:get/get.dart';

class Messages extends Translations {

  @override
  // TODO: implement keys
  Map<String, Map<String, String>> get keys => {
    'zh_CN': {
      'hello': "你好, 世界"
    },
    'en_US': {
      'hello': 'hello world'
    }
  };
}

第三步:创建控制器类,用于切换语言

import 'dart:ui';
import 'package:get/get.dart';

class MessagesController extends GetxController {

  void changeLanguage(String languageCode, String  countryCode) {
    var locale = Locale(languageCode, countryCode);
    Get.updateLocale(locale);
  }
}

第四步:实例化控制器并使用

import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/WorkersConroller.dart';
import 'package:flutter_getx_example/InternationalizationExample/MessagesCnotroller.dart';
import 'package:get/get.dart';

class InternationalizationExample extends StatelessWidget {

  MessagesController messagesController = Get.put(MessagesController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Internationalization"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text('hello'.tr, style: TextStyle(color: Colors.pink, fontSize: 30)),
            ElevatedButton(
              onPressed: () => messagesController.changeLanguage('zh', "CN"),
              child: Text("切换到中文")
            ),
            SizedBox(height: 20,),
            ElevatedButton(
              onPressed: () => messagesController.changeLanguage('en', "US"),
              child: Text("切换到英文")
            ),
          ],
        ),
      ),
    );
  }
}

效果展示

2225852-c97a6de028c6b06b.webp

GetUtils

GetUtils 是getx为我们提供一些常用的工具类库,在我们的项目开发中提供了很多便捷的方法。包括值是否为空是否是数字是否是视频、图片、音频、PPT、Word、APK邮箱、手机号码、日期、MD5、SHA1等等。

image.png image.png

判断是否是邮箱、手机号、IPV4地址为例

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class GetXUtilsExample extends StatelessWidget {

  var textFieldController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetX Utils"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Padding(
              padding: EdgeInsets.all(20),
              child: TextField(
                controller: textFieldController,
              ),
            ),
            SizedBox(height: 10,),
            Padding(
              padding: EdgeInsets.all(10),
              child: ElevatedButton(
                child: Text("判断是否是邮箱"),
                onPressed: () async {
                  if (GetUtils.isEmail(textFieldController.text)) {
                    Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
                  } else {
                    Get.snackbar(
                        "邮箱错误",
                        "请输入正确的邮箱",
                        backgroundColor: Colors.pink
                    );
                  }
                },
              ),
            ),
            Padding(
              padding: EdgeInsets.all(10),
              child: ElevatedButton(
                child: Text("判断是否是手机号"),
                onPressed: () async {
                  if (GetUtils.isPhoneNumber(textFieldController.text)) {

                    Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
                  } else {
                    Get.snackbar(
                        "手机号错误",
                        "请输入正确的手机号",
                        backgroundColor: Colors.pink
                    );
                  }
                },
              ),
            ),
            Padding(
              padding: EdgeInsets.all(10),
              child: ElevatedButton(
                child: Text("判断是否是IPv4"),
                onPressed: () async {
                  if (GetUtils.isIPv4(textFieldController.text)) {

                    Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
                  } else {
                    Get.snackbar(
                        "地址错误",
                        "请输入正确的IPv4地址",
                        backgroundColor: Colors.pink
                    );
                  }
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

判断是否是邮箱效果展示

2225852-e68a390ebd6dd575.webp

判断是否是手机号码效果展示

2225852-22311f574d47c961.webp

判断是否是IPV4地址效果展示

2225852-2cc9c3ebafeffa55.webp

GetStorage

创建一个 GetStorage 实例来开始保存和检索数据。可以在代码开始时初始化或在需要存储或提取数据时进行初始化。 您可以根据需要创建多个实例。

final box = GetStorage();

现在,我们可以使用 GetStorage 实例来存储数据。 对于存储操作,GetStorage 提供了 write() 和 writeIfNull() 方法。

write(key, value) // 存储值是“value”的键值对到本地。
writeIfNull(key, value) //当该键不存在时,存储一个键为“key”,值为“value”的键值对到本地。   

以下是示例代码: (例如在登录后存储用户数据以进行后续身份验证)

// 在用户登录成功后存储用户数据
box.write('username', 'johndoe');
box.write('email', 'johndoe@example.com');    

同样,检索数据也很容易。 GetStorage 提供了 read() 和 readIfNull() 方法,以检索存储的值。

read(key)//检索给定键的值。如果键不存在,则返回null。
readIfNull(key, value) //与 read() 方法类似,但在不存在键时返回的是“value”。    

以下是示例代码:

final username = box.read('username');
final email = box.readIfNull('email', 'no email available');    

GetX 其他高级API

// 给出当前页面的args。
Get.arguments

//给出以前的路由名称
Get.previousRoute

// 给出要访问的原始路由,例如,rawRoute.isFirst()
Get.rawRoute

// 允许从GetObserver访问Rounting API。
Get.routing

// 检查 snackbar 是否打开
Get.isSnackbarOpen

// 检查 dialog 是否打开
Get.isDialogOpen

// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen

// 删除一个路由。
Get.removeRoute()

//反复返回,直到表达式返回真。
Get.until()

// 转到下一条路由,并删除所有之前的路由,直到表达式返回true。
Get.offUntil()

// 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。
Get.offNamedUntil()

//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid

GetPlatform.isIOS

GetPlatform.isMacOS

GetPlatform.isWindows

GetPlatform.isLinux

GetPlatform.isFuchsia

//检查设备类型
GetPlatform.isMobile

GetPlatform.isDesktop

//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb

// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height

Get.width

// 提供当前上下文。
Get.context

// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay

// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。
// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width

context.height

// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值:1
// 参数: reducedBy (double) 可选 - 默认值:0。

context.heightTransformer()

context.widthTransformer()

/// 类似于 MediaQuery.of(context).size。
context.mediaQuerySize()

/// 类似于 MediaQuery.of(context).padding。
context.mediaQueryPadding()

/// 类似于 MediaQuery.of(context).viewPadding。
context.mediaQueryViewPadding()

/// 类似于 MediaQuery.of(context).viewInsets。
context.mediaQueryViewInsets()

/// 类似于 MediaQuery.of(context).orientation;
context.orientation()

///检查设备是否处于横向模式
context.isLandscape()

///检查设备是否处于纵向模式。
context.isPortrait()

///类似于MediaQuery.of(context).devicePixelRatio。
context.devicePixelRatio()

///类似于MediaQuery.of(context).textScaleFactor。
context.textScaleFactor()

///查询设备最短边。
context.mediaQueryShortestSide()

///如果宽度大于800,则为真。
context.showNavbar()

///如果最短边小于600p,则为真。
context.isPhone()

///如果最短边大于600p,则为真。
context.isSmallTablet()

///如果最短边大于720p,则为真。
context.isLargeTablet()

///如果当前设备是平板电脑,则为真
context.isTablet()
///根据页面大小返回一个值<T>
///可以给值为:
///watch:如果最短边小于300

///mobile:如果最短边小于600

///tablet:如果最短边(shortestSide)小于1200

///desktop:如果宽度大于1200

context.responsiveValue<T>()

GetX Cli

安装Cli脚手架

我们通过命令flutter pub global activate get_cli 进行脚手架的全局安装

设置环境变量

下面路径添加到Path环境变量里面

F:\flutter_windows\flutter_windows_3.3.0-stable\flutter.pub-cache\bin F:\flutter_windows\flutter_windows_3.0.5\flutter\bin\cache\dart-sdk\bin

image.png

通过get命令校验是否成功安装

image.png

我们可以通过get create project来进行创建工程

image.png

输入工程名称公司域名选择iOS语言选择Android语言是否空安全是否校验,选完开始创建工程

image.png

image.png

通过get create page:login来快速创建一个页面,这个页面有controllerviewbindingroutes等配置,结构是Getx_pattern

image.png

get install安装依赖

第一种:直接安装最新版本

JMdeMacBook-Pro:getx_example jm$ get install dio

Installing package "dio" …

✓  'Package: dio installed!

Running `flutter pub get` …

$ flutter pub get
Running "flutter pub get" in getx_example...                     2,656ms

Time: 5815 Milliseconds

第二种:同时安装多个包

JMdeMacBook-Pro:getx_example jm$ get install path dio

Installing package "path" …

✓  'Package: path installed!

Installing package "dio" …

✓  'Package: dio installed!

Running `flutter pub get` …

$ flutter pub get
Running "flutter pub get" in getx_example...                       732ms

Time: 7146 Milliseconds

第三种:安装自定版本的包

JMdeMacBook-Pro:getx_example jm$ get install prodiver:5.0.0

Installing package "prodiver" …

✓  'Package: prodiver installed!

我们可以通过get install flutter_launcher_icons --dev安装开发时所依赖的包

JMdeMacBook-Pro:getx_example jm$ get install flutter_launcher_icons --dev


The [--dev] is not necessary


Installing package "flutter_launcher_icons" …

✓  'Package: flutter_launcher_icons installed!

Cli卸载包

第一种:卸载某个安装包

JMdeMacBook-Pro:getx_example jm$ get remove http

Removing package: "http"


Package: http is not installed in this application


Running `flutter pub get` …

$ flutter pub get
Running "flutter pub get" in getx_example...                       772ms

Time: 2641 Milliseconds

第二种:同时卸载多个包

JMdeMacBook-Pro:getx_example jm$ get remove dio path

Removing package: "dio"

✓  Package: dio removed!

Removing package: "path"

✓  Package: path removed!

Running `flutter pub get` …

$ flutter pub get
Running "flutter pub get" in getx_example...                     1,085ms

Time: 3142 Milliseconds

Cli更新脚手架

我们可以通过get update对脚手架进行更新

JMdeMacBook-Pro:getx_example jm$ get update

Latest version of get_cli already installed


Time: 3315 Milliseconds

Cli查看版本号

我们可以通过get -v 查看当前脚手架的版本号

JMdeMacBook-Pro:getx_example jm$ get -v
░██████╗░███████╗████████╗   ░█████╗░██╗░░░░░░██╗
██╔════╝░██╔════╝╚══██╔══╝   ██╔══██╗██║░░░░░░██║
██║░░██╗░█████╗░░░░░██║░░░   ██║░░╚═╝██║░░░░░░██║
██║░░╚██╗██╔══╝░░░░░██║░░░   ██║░░██╗██║░░░░░░██║
╚██████╔╝███████╗░░░██║░░░   ╚█████╔╝███████╗ ██║
░╚═════╝░╚══════╝░░░╚═╝░░░   ░╚════╝░╚══════╝ ╚═╝

Version: 1.6.0

Time: 148 Milliseconds

Cli帮助

当我们忘记了命令的使用方式,我们可以通过get help进行查看帮助。

JMdeMacBook-Pro:getx_example jm$ get help

List available commands:

  create:  
    controller:  Generate controller
    page:  Use to generate pages
    project:  Use to generate new project
    provider:  Create a new Provider
    screen:  Generate new screen
    view:  Generate view
  generate:  
    locales:  Generate translation file from json files
    model:  generate Class model from json
  help:  Show this help
  init:  generate the chosen structure on an existing project:
  install:  Use to install a package in your project (dependencies):
  remove:  Use to remove a package in your project (dependencies):
  sort:  Sort imports and format dart files
  update:  To update GET_CLI
  --version:  Shows the current CLI version'

Time: 94 Milliseconds