getx

无需上下文即可打开屏幕/提示/对话框/底部导航栏,轻松管理状态和注入依赖项。

getx

关于 Get

  • GetX 是一个超轻量级且功能强大的 Flutter 解决方案。它将高性能状态管理、智能依赖注入和路由管理快速而实用地结合在一起。

  • GetX 有 3 个基本原则。这意味着这些是库中所有资源的优先级:生产力、性能和组织。

    • 性能: GetX 专注于性能和最小的资源消耗。GetX 不使用 Streams 或 ChangeNotifier。

    • 生产力: GetX 使用简单愉快的语法。无论您想做什么,总有更简单的方法可以使用 GetX。它将节省数小时的开发时间,并提供您的应用程序可以提供的最高性能。

      通常,开发人员应该关注将控制器从内存中移除。有了 GetX,这就不必了,因为资源在使用后会自动从内存中移除。如果您想将其保留在内存中,则必须在依赖项中明确声明“permanent: true”。这样,除了节省时间,您因内存中存在不必要的依赖项的风险也会降低。依赖项加载默认也是延迟的。

    • 组织: GetX 允许完全解耦视图、演示逻辑、业务逻辑、依赖注入和导航。您无需上下文即可在路由之间导航,因此您不依赖于小部件树(可视化)进行导航。您无需使用 inheritedWidget 通过上下文访问控制器/blocs,从而完全将演示逻辑和业务逻辑与可视化层解耦。您无需通过 MultiProvider 将您的 Controllers/Models/Blocs 类注入到小部件树中。为此,GetX 使用其自身的依赖注入功能,完全将 DI 与其视图解耦。

      有了 GetX,您就知道在哪里可以找到应用程序的每个功能,默认情况下代码干净整洁。除了易于维护外,这使得模块共享在 Flutter 中以前是不可想象的,现在变得完全可能。
      BLoC 是组织 Flutter 代码的一个起点,它将业务逻辑与可视化分开。GetX 是这一演进的自然产物,不仅分离了业务逻辑,还分离了演示逻辑。此外,依赖注入和路由也是解耦的,数据层则完全独立。您知道一切都在哪里,而且这一切都比构建一个“Hello World”更简单。
      GetX 是使用 Flutter SDK 构建高性能应用程序最简单、最实用、可扩展的方式。它围绕它拥有一个庞大的生态系统,可以完美地协同工作,对初学者来说很容易,对专家来说也很准确。它安全、稳定、最新,并提供 Flutter SDK 中不存在的大量内置 API。

  • GetX 并不臃肿。它拥有大量功能,让您可以开始编程而无需担心任何事情,但这些功能都包含在单独的容器中,并且仅在使用后启动。如果您只使用状态管理,则只会编译状态管理。如果您只使用路由,则状态管理的所有内容都不会被编译。

  • GetX 拥有庞大的生态系统、庞大的社区、大量的贡献者,并且只要 Flutter 存在,它就会得到维护。GetX 还可以与 Android、iOS、Web、Mac、Linux、Windows 以及您的服务器上的代码相同地运行。
    您可以使用 Get Server 完全重用前端代码与后端。.

此外,使用 Get CLI 可以完全自动化服务器和前端的整个开发过程。.

此外,为了进一步提高您的生产力,我们有
VSCode 的扩展和 Android Studio/Intellij 的扩展

安装

将 Get 添加到您的 pubspec.yaml 文件中

dependencies:
  get:

在将使用的文件中导入 get

import 'package:get/get.dart';

带有 GetX 的计数器应用

Flutter 新项目默认创建的“counter”项目有 100 多行(包括注释)。为了展示 Get 的强大功能,我将演示如何创建一个“counter”,在每次点击时更改状态,在页面之间切换,并在屏幕之间共享状态,所有这些都以一种有组织的方式,将业务逻辑与视图分离,仅需 26 行代码(包括注释)。

  • 步骤 1
    在 MaterialApp 前面加上“Get”,将其转换为 GetMaterialApp
void main() => runApp(GetMaterialApp(home: Home()));
  • 注意:这不会修改 Flutter 的 MaterialApp,GetMaterialApp 不是修改后的 MaterialApp,它只是一个预配置的小部件,它将默认 MaterialApp 作为子项。您可以手动配置它,但绝对没有必要。GetMaterialApp 将创建路由、注入它们、注入翻译、注入您路由导航所需的一切。如果您仅将 Get 用于状态管理或依赖项管理,则无需使用 GetMaterialApp。GetMaterialApp 对于路由、提示、国际化、底部导航栏、对话框以及与路由和无上下文相关的高级 API 是必需的。

  • 注意²:只有当您要使用路由管理(Get.to()Get.back() 等)时,才需要此步骤。如果您不使用它,则无需执行步骤 1。

  • 步骤 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) {

    // Instantiate your class using Get.put() to make it available for all "child" routes there.
    final Controller c = Get.put(Controller());

    return Scaffold(
      // Use Obx(()=> to update Text() whenever count is changed.
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

      // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context
      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 {
  // You can ask Get to find a Controller that is being used by another page and redirect you to it.
  final Controller c = Get.find();

  @override
  Widget build(context){
     // Access the updated count variable
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}

结果

这是一个简单的项目,但它已经清楚地展示了 Get 的强大功能。随着您的项目的发展,这种差异将变得更加明显。

Get 的设计是为了与团队合作,但它也简化了个人开发者的工作。

提高您的截止日期,按时交付所有内容而不会牺牲性能。Get 不是给所有人用的,但如果您认同这句话,那么 Get 就是为您准备的!

三大支柱

状态管理

Get 有两种不同的状态管理器:简单状态管理器(我们称之为 GetBuilder)和响应式状态管理器(GetX/Obx)。

响应式状态管理器

响应式编程可能会让许多人望而却步,因为人们认为它很复杂。GetX 将响应式编程变得非常简单。

  • 您无需创建 StreamControllers。
  • 您无需为每个变量创建 StreamBuilder。
  • 您无需为每个状态创建一个类。
  • 您无需创建 get 来获取初始值。
  • 您无需使用代码生成器。

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

假设您有一个 name 变量,并希望每次更改它时,所有使用它的控件都会自动更改。

这是您的 count 变量。

var name = 'Jonatas Borges';

要使其可观察,只需在其末尾添加“.obs”。

var name = 'Jonatas Borges'.obs;

在 UI 中,当您想显示该值并在值更改时更新屏幕时,只需这样做。

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

就是这样。就是这么简单。

有关状态管理的更多详细信息

在此处 查看有关状态管理的更深入的解释。您将在其中看到更多示例以及简单状态管理器和响应式状态管理器之间的区别。

您将很好地了解 GetX 的强大功能。

路由管理

如果您要无上下文地使用路由/提示/对话框/底部导航栏,GetX 对您来说也非常出色,只需看看。

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

GetMaterialApp( // Before: MaterialApp(
  home: MyHome(),
)

导航到新屏幕


Get.to(NextScreen());

使用名称导航到新屏幕。在此处 查看有关命名路由的更多详细信息


Get.toNamed('/details');

关闭提示、对话框、底部导航栏或任何您通常使用 Navigator.pop(context) 关闭的内容。

Get.back();

前往下一屏幕,不提供返回上一屏幕的选项(用于启动屏幕、登录屏幕等)。

Get.off(NextScreen());

前往下一屏幕并取消所有之前的路由(适用于购物车、民意调查和测试)。

Get.offAll(NextScreen());

您注意到您不必使用上下文来执行任何这些操作吗?这是使用 Get 路由管理的最大优势之一。通过此,您可以从控制器类中执行所有这些方法,无需担心。

有关路由管理的更多详细信息

Get 支持命名路由,并且还提供对路由的更低级控制!在此处 有详细的文档

依赖管理

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

Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
  • 注意:如果您正在使用 Get 的状态管理器,请更加关注 bindings API,这将使您的视图与控制器连接更容易。

您不是在正在使用的类中实例化您的类,而是在 Get 实例中实例化它,这将使其在您的整个应用程序中可用。
这样您就可以正常使用您的控制器(或 Bloc 类)。

提示: Get 的依赖管理与其他包部分是解耦的,所以如果例如,您的应用程序已经在使用状态管理器(任何一种,无关紧要),您无需全部重写,您可以毫无问题地使用此依赖注入。

controller.fetchApi();

想象一下,您已经通过许多路由进行了导航,并且需要控制器中遗留的数据,您将需要一个状态管理器结合 Provider 或 Get_it,对吗?使用 Get 则不必。您只需要求 Get“查找”您的控制器,无需任何其他依赖项。

Controller controller = Get.find();
//Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller.

然后您将能够恢复您之前获取的控制器数据。

Text(controller.textFromApi);

有关依赖项管理的更多详细信息

在此处 查看有关依赖项管理的更深入的解释

Utils

国际化

翻译

翻译保留为简单的键值字典映射。
要添加自定义翻译,请创建一个类并扩展 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 追加到指定的键,它就会被翻译,使用 Get.localeGet.fallbackLocale 的当前值。

Text('title'.tr);

使用带单数和复数的翻译

var products = [];
Text('singularKey'.trPlural('pluralKey', products.length, Args));

使用带参数的翻译

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': '[email protected]'
  }));

区域设置

将参数传递给 GetMaterialApp 以定义区域设置和翻译。

return GetMaterialApp(
    translations: Messages(), // your translations
    locale: Locale('en', 'US'), // translations will be displayed in that locale
    fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected.
);

更改区域设置

调用 Get.updateLocale(locale) 来更新区域设置。翻译然后会自动使用新的区域设置。

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

系统区域设置

要读取系统区域设置,您可以使用 Get.deviceLocale

return GetMaterialApp(
    locale: Get.deviceLocale,
);

更改主题

为了更新它,请不要使用高于 GetMaterialApp 的任何更高层小部件。这可能会触发重复的键。许多人习惯于创建一个“ThemeProvider”小部件来更改应用程序主题的史前方法,而这对于 GetX™ 绝对是没必要的。

您可以创建自定义主题,只需将其添加到 Get.changeTheme 中,无需任何样板代码。

Get.changeTheme(ThemeData.light());

如果您想创建一个在 onTap 中更改主题的按钮,您可以将两个 GetX™ API 结合起来。

  • 用于检查是否正在使用暗 Theme 的 API。
  • Theme 更改 API,您可以将其放在 onPressed 中。
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());

当激活 .darkmode 时,它将切换到浅色主题,当浅色主题变为活动状态时,它将切换到深色主题

GetConnect

GetConnect 是一个简单的方法,可以通过 http 或 websockets 从后端与前端通信。

默认配置

您只需扩展 GetConnect 并使用 GET/POST/PUT/DELETE/SOCKET 方法即可与您的 Rest API 或 websockets 通信。

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、响应修改器、请求修改器、身份验证器,甚至是尝试身份验证的次数,还可以定义一个标准解码器,将所有请求转换为您的模型,而无需任何额外配置。

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

GetPage 中间件

GetPage 现在有一个新属性,它接受 GetMiddleWare 列表并按特定顺序运行它们。

注意:当 GetPage 具有中间件时,该页面所有子页面将自动具有相同的中间件。

优先级

中间件的运行顺序可以通过 GetMiddleware 中的优先级来设置。

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

这些中间件将按此顺序运行 -8 => 2 => 4 => 5

重定向

当调用路由的页面正在被搜索时,将调用此函数。它将 RouteSettings 作为结果返回以重定向。或将其设置为 null,则不会重定向。

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

在初始化 Bindings 之前立即调用此函数。
您可以在此处更改此页面的 Bindings。

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

OnPageBuildStart

在初始化 Bindings 之后立即调用此函数。
您可以在此处在创建绑定并创建页面小部件之前执行一些操作。

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

OnPageBuilt

在调用 GetPage.page 函数后立即调用此函数,并将提供函数的结果。并获取将要显示的窗口小部件。

OnPageDispose

在处置完页面所有相关对象(Controllers、views 等)后立即调用此函数。

其他高级 API

// give the current args from currentScreen
Get.arguments

// give name of previous route
Get.previousRoute

// give the raw route to access for example, rawRoute.isFirst()
Get.rawRoute

// give access to Routing API from GetObserver
Get.routing

// check if snackbar is open
Get.isSnackbarOpen

// check if dialog is open
Get.isDialogOpen

// check if bottomsheet is open
Get.isBottomSheetOpen

// remove one route.
Get.removeRoute()

// back repeatedly until the predicate returns true.
Get.until()

// go to next route and remove all the previous routes until the predicate returns true.
Get.offUntil()

// go to next named route and remove all the previous routes until the predicate returns true.
Get.offNamedUntil()

//Check in what platform the app is running
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia

//Check the device type
GetPlatform.isMobile
GetPlatform.isDesktop
//All platforms are supported independently in web!
//You can tell if you are running inside a browser
//on Windows, iOS, OSX, Android, etc.
GetPlatform.isWeb


// Equivalent to : MediaQuery.of(context).size.height,
// but immutable.
Get.height
Get.width

// Gives the current context of the Navigator.
Get.context

// Gives the context of the snackbar/dialog/bottomsheet in the foreground, anywhere in your code.
Get.contextOverlay

// Note: the following methods are extensions on context. Since you
// have access to context in any place of your UI, you can use it anywhere in the UI code

// If you need a changeable height/width (like Desktop or browser windows that can be scaled) you will need to use context.
context.width
context.height

// Gives you the power to define half the screen, a third of it and so on.
// Useful for responsive applications.
// param dividedBy (double) optional - default: 1
// param reducedBy (double) optional - default: 0
context.heightTransformer()
context.widthTransformer()

/// Similar to MediaQuery.of(context).size
context.mediaQuerySize()

/// Similar to MediaQuery.of(context).padding
context.mediaQueryPadding()

/// Similar to MediaQuery.of(context).viewPadding
context.mediaQueryViewPadding()

/// Similar to MediaQuery.of(context).viewInsets;
context.mediaQueryViewInsets()

/// Similar to MediaQuery.of(context).orientation;
context.orientation()

/// Check if device is on landscape mode
context.isLandscape()

/// Check if device is on portrait mode
context.isPortrait()

/// Similar to MediaQuery.of(context).devicePixelRatio;
context.devicePixelRatio()

/// Similar to MediaQuery.of(context).textScaleFactor;
context.textScaleFactor()

/// Get the shortestSide from screen
context.mediaQueryShortestSide()

/// True if width be larger than 800
context.showNavbar()

/// True if the shortestSide is smaller than 600p
context.isPhone()

/// True if the shortestSide is largest than 600p
context.isSmallTablet()

/// True if the shortestSide is largest than 720p
context.isLargeTablet()

/// True if the current device is Tablet
context.isTablet()

/// Returns a value<T> according to the screen size
/// can give value for:
/// watch: if the shortestSide is smaller than 300
/// mobile: if the shortestSide is smaller than 600
/// tablet: if the shortestSide is smaller than 1200
/// desktop: if width is largest than 1200
context.responsiveValue<T>()

可选的全局设置和手动配置

GetMaterialApp 会为您配置所有内容,但如果您想手动配置 Get。

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

您也可以在 GetObserver 中使用自己的中间件,这不会影响任何内容。

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

您可以创建 全局设置Get。只需在推送任何路由之前在代码中添加 Get.config
或者直接在您的 GetMaterialApp 中进行。

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
)

您可以选择重定向 Get 的所有日志消息。
如果您想使用自己喜欢的日志记录包,
并想捕获那里的日志。

GetMaterialApp(
  enableLog: true,
  logWriterCallback: localLogWriter,
);

void localLogWriter(String text, {bool isError = false}) {
  // pass the message to your favourite logging package here
  // please note that even if enableLog: false log messages will be pushed in this callback
  // you get check the flag if you want through GetConfig.isLogEnable
}

本地状态小部件

这些小部件允许您管理单个值,并将状态保留为临时且本地的。
我们有响应式和简单两种口味。
例如,您可以使用它们来切换 TextField 中的 obscureText,或许创建一个自定义的
可展开面板,或者在更改内容的同时修改 BottomNavigationBar 中的当前索引。
Scaffold 的主体。

ValueBuilder

StatefulWidget 的简化版本,它带有一个 .setState 回调,该回调接受更新后的值。

ValueBuilder<bool>(
  initialValue: false,
  builder: (value, updateFn) => Switch(
    value: value,
    onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue )
  ),
  // if you need to call something outside the builder method.
  onUpdate: (value) => print("Value updated: $value"),
  onDispose: () => print("Widget unmounted"),
),

ObxValue

类似于 ValueBuilder,但这是响应式版本,您传递一个 Rx 实例(还记得神奇的 .obs 吗?)和
自动更新...是不是很棒?

ObxValue((data) => Switch(
        value: data.value,
        onChanged: data, // Rx has a _callable_ function! You could use (flag) => data.value = flag,
    ),
    false.obs,
),

有用的提示

.observables(也称为Rx 类型)具有多种内部方法和运算符。

人们普遍认为具有 .obs 的属性就是实际值...但请不要弄错!
我们避免变量的类型声明,因为 Dart 的编译器足够智能,代码
看起来更干净,但是

var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');

即使 message打印的是实际的 String 值,其类型也是RxString

所以,您不能这样做 message.substring( 0, 4 )
您必须访问可观察对象中的实际value
最“常用”的方法是 .value,但是,您知道您还可以使用……

final name = 'GetX'.obs;
// only "updates" the stream, if the value is different from the current one.
name.value = 'Hey';

// All Rx properties are "callable" and returns the new value.
// but this approach does not accepts `null`, the UI will not rebuild.
name('Hello');

// is like a getter, prints 'Hello'.
name() ;

/// numbers:

final count = 0.obs;

// You can use all non mutable operations from num primitives!
count + 1;

// Watch out! this is only valid if `count` is not final, but var
count += 1;

// You can also compare against values:
count > 2;

/// booleans:

final flag = false.obs;

// switches the value between true/false
flag.toggle();


/// all types:

// Sets the `value` to null.
flag.nil();

// All toString(), toJson() operations are passed down to the `value`
print( count ); // calls `toString()` inside  for RxInt

final abc = [0,1,2].obs;
// Converts the value to a json Array, prints RxList
// Json is supported by all Rx types!
print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}');

// RxMap, RxList and RxSet are special Rx types, that extends their native types.
// but you can work with a List as a regular list, although is reactive!
abc.add(12); // pushes 12 to the list, and UPDATES the stream.
abc[3]; // like Lists, reads the index 3.


// equality works with the Rx and the value, but hashCode is always taken from the value
final number = 12.obs;
print( number == 12 ); // prints > true

/// Custom Rx Models:

// toJson(), toString() are deferred to the child, so you can implement override on them, and print() the observable directly.

class User {
    String name, last;
    int age;
    User({this.name, this.last, this.age});

    @override
    String toString() => '$name $last, $age years old';
}

final user = User(name: 'John', last: 'Doe', age: 33).obs;

// `user` is "reactive", but the properties inside ARE NOT!
// So, if we change some variable inside of it...
user.value.name = 'Roi';
// The widget will not rebuild!,
// `Rx` don't have any clue when you change something inside user.
// So, for custom classes, we need to manually "notify" the change.
user.refresh();

// or we can use the `update()` method!
user.update((value){
  value.name='Roi';
});

print( user );

StateMixin

处理 UI 状态的另一种方法是使用 StateMixin<T>
要实现它,请使用 withStateMixin<T> 添加
到您的控制器,该控制器允许一个 T 模型。

class Controller extends GetController with StateMixin<User>{}

change() 方法在我们想要的时候更改状态。
只需这样传递数据和状态。

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

RxStatus 允许这些状态。

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

要在 UI 中表示它,请使用。

class OtherClass extends GetView<Controller> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(

      body: controller.obx(
        (state)=>Text(state.name),
        
        // here you can put your custom loading indicator, but
        // by default would be Center(child:CircularProgressIndicator())
        onLoading: CustomLoadingIndicator(),
        onEmpty: Text('No data found'),

        // here also you can set your own error widget, but by
        // default will be an Center(child:Text(error))
        onError: (error)=>Text(error),
      ),
    );
}

GetView

我喜欢这个小部件,它如此简单,却又如此有用!

它是一个 const Stateless 小部件,它有一个已注册 Controllercontroller getter,仅此而已。

 class AwesomeController extends GetController {
   final String title = 'My Awesome View';
 }

  // ALWAYS remember to pass the `Type` you used to register your controller!
 class AwesomeView extends GetView<AwesomeController> {
   @override
   Widget build(BuildContext context) {
     return Container(
       padding: EdgeInsets.all(20),
       child: Text(controller.title), // just call `controller.something`
     );
   }
 }

GetResponsiveView

扩展此小部件以构建响应式视图。
此小部件包含 screen 属性,该属性具有所有
关于屏幕尺寸和类型的信息。

如何使用它

您有两种选择来构建它。

  • 使用 builder 方法,您将返回要构建的小部件。
  • 使用 desktoptabletphonewatch 方法。特定的
    当屏幕类型匹配方法时,将构建方法。
    当屏幕为 [ScreenType.Tablet] 时,tablet 方法
    将被执行,依此类推。
    注意: 如果您使用此方法,请将 alwaysUseBuilder 属性设置为 false

使用 settings 属性,您可以为屏幕类型设置宽度限制。

example
此屏幕的代码。
code

GetWidget

大多数人对此小部件一无所知,或者完全混淆了它的用法。
用例非常罕见,但非常具体:它缓存一个 Controller。
由于缓存,它不能是 const Stateless

那么,您何时需要“缓存” Controller?

如果您使用 GetX 中另一个“不那么常见”的功能:Get.create()

Get.create(()=>Controller()) 将在您每次调用时生成一个新的 Controller
Get.find<Controller>(),

这就是 GetWidget 发光的地方……因为您可以使用它,例如,
来维护待办事项列表。因此,如果小部件被“重建”,它将保持相同的控制器实例。

GetxService

此类类似于 GetxController,它共享相同的生命周期(onInit()onReady()onClose())。
但内部没有“逻辑”。它只是通知 GetX 依赖注入系统,该子类
不能 从内存中删除。

因此,它对于保持您的“服务”始终可访问和活动非常有用,通过 Get.find()。例如
ApiServiceStorageServiceCacheService

Future<void> main() async {
  await initServices(); /// AWAIT SERVICES INITIALIZATION.
  runApp(SomeApp());
}

/// Is a smart move to make your Services intiialize before you run the Flutter app.
/// as you can control the execution flow (maybe you need to load some Theme configuration,
/// apiKey, language defined by the User... so load SettingService before running ApiService.
/// so GetMaterialApp() doesnt have to rebuild, and takes the values directly.
void initServices() async {
  print('starting services ...');
  /// Here is where you put get_storage, hive, shared_pref initialization.
  /// or moor connection, or whatever that's async.
  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!');
  }
}

删除 GetxService 的唯一方法是使用 Get.reset(),这就像一个
“热重启”您的应用程序。所以请记住,如果您需要在应用程序生命周期中
绝对持久化类实例,请使用 GetxService

测试

您可以像测试其他类一样测试您的控制器,包括它们的生命周期。

class Controller extends GetxController {
  @override
  void onInit() {
    super.onInit();
    //Change value to name2
    name.value = 'name2';
  }

  @override
  void onClose() {
    name.value = '';
    super.onClose();
  }

  final name = 'name1'.obs;

  void changeName() => name.value = 'name3';
}

void main() {
  test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
      () {
    /// You can test the controller without the lifecycle,
    /// but it's not recommended unless you're not using
    ///  GetX dependency injection
    final controller = Controller();
    expect(controller.name.value, 'name1');

    /// If you are using it, you can test everything,
    /// including the state of the application after each lifecycle.
    Get.put(controller); // onInit was called
    expect(controller.name.value, 'name2');

    /// Test your functions
    controller.changeName();
    expect(controller.name.value, 'name3');

    /// onClose was called
    Get.delete<Controller>();

    expect(controller.name.value, '');
  });
}

技巧

Mockito 或 mocktail

如果您需要模拟您的 GetxController/GetxService,您应该扩展 GetxController,并将其与 Mock 混合,这样。

class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
使用 Get.reset()

如果您正在测试小部件或测试组,请在测试结束时或 tearDown 中使用 Get.reset 来重置您之前测试的所有设置。

Get.testMode

如果您在控制器中使用导航,请在 main 的开头使用 Get.testMode = true

2.0 的重大更改

1- Rx 类型

之前 之后
StringX RxString
IntX RxInt
MapX RxMap
ListX RxList
NumX RxNum
DoubleX RxDouble

RxController 和 GetBuilder 现在已合并,您不再需要记住要使用哪个控制器,只需使用 GetxController,它适用于简单状态管理和响应式状态管理。

2- 命名路由
之前

GetMaterialApp(
  namedRoutes: {
    '/': GetRoute(page: Home()),
  }
)

现在

GetMaterialApp(
  getPages: [
    GetPage(name: '/', page: () => Home()),
  ]
)

为什么进行此更改?
通常,有必要根据参数或登录令牌决定显示哪个页面,以前的方法不灵活,因为它不允许这样做。
将页面插入函数极大地减少了 RAM 消耗,因为路由不会在应用程序启动时分配到内存中,它还允许执行此类方法。


GetStorage box = GetStorage();

GetMaterialApp(
  getPages: [
    GetPage(name: '/', page:(){
      return box.hasData('token') ? Home() : Login();
    })
  ]
)

为什么选择 Getx?

1- 许多时候,在 Flutter 更新后,您的许多包都会损坏。有时会出现编译错误,经常出现没有答案的错误,开发人员需要知道错误来自哪里,跟踪错误,然后尝试在相应的存储库中打开一个问题,并看到他的问题得到解决。Get 集中了开发的主要资源(状态、依赖项和路由管理),允许您在 pubspec 中添加一个包,然后开始工作。在 Flutter 更新后,您需要做的就是更新 Get 依赖项,然后开始工作。Get 还可以解决兼容性问题。一个包的版本与另一个不兼容的情况有多少次,因为一个版本使用了某个版本的依赖项,而另一个版本使用了另一个版本的依赖项?使用 Get 也不会成为问题,因为所有内容都在同一个包中并且完全兼容。

2- Flutter 简单易学,令人惊叹,但 Flutter 仍然存在一些开发人员可能不希望的样板代码,例如 Navigator.of(context).push (context, builder [...]。Get 简化了开发。您无需编写 8 行代码来调用路由,只需执行:Get.to(Home()) 即可完成,您将转到下一个页面。动态 Web URL 对 Flutter 来说是一件非常痛苦的事情,而使用 GetX 却异常简单。管理 Flutter 中的状态和依赖项也是一个会引起很多讨论的事情,因为有数百种模式可供选择。但是,没有什么比在变量末尾添加“.obs”并将小部件放在 Obx 内更简单的了,仅此而已,该变量的所有更新都会自动在屏幕上更新。

3- 轻松而不担心性能。Flutter 的性能已经很棒了,但想象一下您使用状态管理器和定位器来分发您的 bloc/stores/controllers/ etc. 类。您需要手动调用排除该依赖项,当您不再需要它时。但是您是否曾想过简单地使用您的控制器,当它不再被任何人使用时,它就会自动从内存中删除?这就是 GetX 所做的。通过 SmartManagement,所有未使用的内容都会从内存中删除,而您无需担心任何事情,只需编程即可。您将确保您消耗的资源最少,而无需为此创建任何逻辑。

4- 真正的解耦。您可能听说过“将视图与业务逻辑分离”的概念。这并不是 BLoC、MVC、MVVM 的特例,市场上的任何标准都有这个概念。然而,由于上下文的使用,这个概念在 Flutter 中经常被缓解。
如果您需要上下文来查找 InheritedWidget,则需要在视图中,或者通过参数传递上下文。我个人认为这个解决方案非常糟糕,并且为了在团队中工作,我们将始终依赖于视图的业务逻辑。Getx 对标准方法不那么正统,虽然它不完全禁止使用 StatefulWidgets、InitState 等,但它总有一个类似的、可能更干净的方法。控制器具有生命周期,当您需要发出 APIREST 请求时,您不依赖于视图中的任何内容。您可以使用 onInit 来启动 http 调用,当数据到达时,变量将被填充。由于 GetX 是完全响应式的(真的,并且在流上工作),一旦项目被填充,使用该变量的所有小部件都会在视图中自动更新。这允许具有 UI 专业知识的人员仅使用小部件,而不必将任何内容发送到业务逻辑(除了用户事件,如单击按钮),而从事业务逻辑的人员可以自由地单独创建和测试业务逻辑。

此库将始终更新并实现新功能。欢迎提供 PRs 并为此做出贡献。

GitHub

https://github.com/jonataslaw/getx