Flutter GetX使用详解

一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希望未来技术之巅上有你们也有我。

About Get

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

GetX 有 3 个基本原则。 这意味着图书馆中所有资源的首要任务是:生产力、绩效和组织。

性能:GetX 注重性能和最小资源消耗。 GetX 不使用 Streams 或 ChangeNotifier。

生产力:GetX 使用简单且令人愉快的语法。 无论您想做什么,GetX 总有一种更简单的方法。 它将节省开发时间,并为您的应用程序提供最大的性能。

一般来说,开发人员应该关心从内存中删除控制器。 对于 GetX,这是不必要的,因为默认情况下不使用资源时会从内存中删除资源。 如果您想将其保留在内存中,则必须在依赖项中显式声明“permanent: true”。 这样,除了节省时间之外,您还可以减少对内存不必要的依赖的风险。 默认情况下,依赖项加载也是惰性加载的。

组织:GetX 允许视图、表示逻辑、业务逻辑、依赖注入和导航的完全解耦。 您不需要上下文来在路线之间导航,因此您不依赖于小部件树(可视化)。 您不需要上下文来通过继承的Widget 访问控制器/块,因此您可以将表示逻辑和业务逻辑与可视化层完全解耦。 您不需要通过 MultiProvider 将 Controllers/Models/Blocs 类注入到您的 widget 树中。 为此,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 扩展

Installing

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

dependencies:
  get:

导入将要使用的文件:

import 'package:get/get.dart';

Counter App with GetX

使用 GetX 的计数器应用程序

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

步骤1:在MaterialApp前添加“Get”,将其变成GetMaterialApp

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

注意:这不会修改Flutter的MaterialApp,GetMaterialApp不是修改后的MaterialApp,它只是一个预先配置的Widget,它有默认的MaterialApp作为子项。 您可以手动配置它,但这绝对不是必要的。 GetMaterialApp 将创建路线、注入路线、注入翻译、注入路线导航所需的一切。 如果您仅使用 Get 进行状态管理或依赖管理,则没有必要使用 GetMaterialApp。 GetMaterialApp 对于路线、snackbars、国际化、bottomSheets、对话框以及与路线和缺乏上下文相关的高级 API 是必需的。

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

第 2 步:创建业务逻辑类并将所有变量、方法和控制器放入其中。 您可以使用简单的“.obs”使任何变量可观察。

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

第三步:创建你的View,使用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 就适合您!

三大支柱

状态管理

​查询详情
780 / 5,000
翻译结果
翻译结果
Get 有两种不同的状态管理器:简单状态管理器(我们称之为 GetBuilder)和反应式状态管理器(GetX/Obx)

反应式状态管理器
反应式编程可能会疏远很多人,因为据说它很复杂。 GetX 将响应式编程变得非常简单:

您不需要创建 StreamController。
您不需要为每个变量创建 StreamBuilder
您不需要为每个状态创建一个类。
您不需要创建初始值的获取。
您不需要使用代码生成器
使用 Get 进行响应式编程与使用 setState 一样简单。

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

这是您的计数变量:

var name = 'Jonatas Borges';

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

var name = 'Jonatas Borges'.obs;

在 UI 中,当您想要显示该值并在值发生变化时更新屏幕时,只需执行以下操作:

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

就这样。 就是这么简单。

有关状态管理的更多详细信息
请在此处查看状态管理的更深入说明。 在那里您将看到更多示例以及简单状态管理器和反应式状态管理器之间的区别

您将会对 GetX 的强大功能有一个很好的了解。

路由管理

如果您要在没有上下文的情况下使用路由/snackbars/dialogs/bottomsheets,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 有一个简单而强大的依赖管理器,允许您仅用 1 行代码检索与 Bloc 或 Controller 相同的类,没有 Provider 上下文,没有继承的Widget:

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

注意:如果您使用 Get 的状态管理器,请多注意绑定 API,这将使您的视图连接到控制器更加容易。

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

提示:获取依赖管理与包的其他部分解耦,因此,例如,如果您的应用程序已经在使用状态管理器(任何一个,都没关系),则不需要全部重写,您可以 使用这种依赖注入没有任何问题

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

有关依赖管理的更多详细信息
请在此处查看依赖管理的更深入说明

实用程序

国际化

翻译

翻译保留为简单的键值字典映射。 要添加自定义翻译,请创建一个类并扩展 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.locale Get.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 中Theme的按钮之类的内容,您可以为此组合两个 GetX™ API:

检查是否正在使用深色Theme的 api。
对于主题更改 API,您可以将其放在 onPressed 中:

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

.darkmode激活时,它将切换到浅色主题,当浅色主题激活时,它将更改为深色主题。

获取连接

GetConnect 是一种使用 httpwebsockets 从后到前进行通信的简单方法

默认配置

您可以简单地扩展 GetConnect 并使用 GET/POST/PUT/DELETE/SOCKET 方法与您的 Rest APIwebsockets 进行通信。

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

页面调用

在创建任何内容之前调用此页面时,将调用此函数,您可以使用它来更改页面的某些内容或为其提供新页面

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

页面构建开始

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

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

页面内置

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

页面处理

该函数将在处理完页面的所有相关对象(控制器、视图等)后立即调用。

其他高级 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 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 中的模糊文本,创建自定义可扩展面板,或者在更改 Scaffold 中正文内容的同时修改 BottomNavigationBar 中的当前索引。

价值创造者

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"),
),

观测值

ValueBuilder 类似,但这是 Reactive 版本,您传递一个 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 的财产就是实际价值……但不要误会! 我们避免变量的 Type 声明,因为 Dart 的编译器足够智能,并且代码看起来更干净,但是:

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

即使消息打印实际的字符串值,类型也是RxString

所以,你不能执行 message.substring( 0, 4 )。 您必须访问可观察对象内的真实值:最“常用的方式”是 .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 );

状态混合

处理 UI 状态的另一种方法是使用 StateMixin 。 要实现它,请使用 withStateMixin 添加到允许 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),
      ),
    );
}

获取视图

我喜欢这个小部件,非常简单,但是非常有用!
是一个 const Stateless Widget,它有一个用于注册 Controller 的 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`
     );
   }
 }

获取响应式视图

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

如何使用它
您有两种选择来构建它。

使用builder器方法,您可以返回要构建的小部件。

具有desktop, tablet,phone, watch的方法。 当屏幕类型匹配时会构建具体的方法,当屏幕为[ScreenType.Tablet]时,tablet方法会被渗出等等。 注意:如果使用此方法请将属性alwaysUseBuilder设置为false
通过设置属性,您可以设置屏幕类型的宽度限制。

通过设置属性,您可以设置屏幕类型的宽度限制。

此屏幕代码的示例代码

获取小工具

大多数人对这个Widget一无所知,或者完全混淆了它的用法。 该用例非常罕见,但非常具体:它缓存一个控制器。 由于cache的原因,不能是const Stateless

那么,什么时候需要“缓存”控制器呢?

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

Get.create(()=>Controller()) 每次调用 Get.find() 时都会生成一个新的 Controller

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

获取服务

这个类就像一个 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, '');
  });
}

Tips

Mockito or mocktail(无酒精鸡尾酒或无酒精鸡尾酒)

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

class NotificationServiceMock extends GetxService with Mock implements NotificationService {}

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

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

2.0 的重大变化

Flutter GetX使用详解_第1张图片
RxController 和 GetBuilder 现在已经合并,您不再需要记住要使用哪个控制器,只需使用 GetxController,它将适用于简单的状态管理和反应式。

2.0 之前的命名路由:

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 令人难以置信,但是 Flutter 仍然有一些大多数开发人员可能不需要的样板,例如 Navigator.of(context).push(context, builder […]。Get 简化了开发。而不是 写8行代码来调用一个路由,你可以这样做: Get.to(Home()) 就完成了,你将进入下一页。动态网址是一件非常痛苦的事情 目前使用 Flutter,而使用 GetX 则非常简单。在 Flutter 中管理状态和管理依赖关系也引起了很多讨论,因为 pub 中有数百种模式。但是没有什么比添加“”更简单的了 .obs”放在变量的末尾,并将您的小部件放入 Obx 中,就这样,对该变量的所有更新都将在屏幕上自动更新。

3- 轻松使用,无需担心性能。 Flutter 的性能已经令人惊叹,但想象一下您使用状态管理器和定位器来分发块/存储/控制器/等类。 当您不需要该依赖项时,您必须手动调用排除它。 但是您是否想过简单地使用您的控制器,当它不再被任何人使用时,它就会从内存中删除? GetX 就是这么做的。 借助 SmartManagement,所有未使用的内容都会从内存中删除,除了编程之外,您不必担心任何事情。 您将确信自己正在消耗最少的必要资源,甚至无需为此创建逻辑。

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

该库将始终更新并实现新功能。 请随意提供 PR 并为其做出贡献。

社区

社区频道

GetX 拥有一个非常活跃且乐于助人的社区。 如果您有疑问,或者需要有关该框架使用的任何帮助,请加入我们的社区频道,您的问题将得到更快的解答,这将是最合适的地方。 该存储库专用于打开问题和请求资源,但请随意成为 GetX 社区的一部分。
Flutter GetX使用详解_第2张图片

如何贡献

想为该项目做出贡献吗? 我们将很自豪地强调您是我们的合作者之一。 您可以在以下几点做出贡献,让 Get(和 Flutter)变得更好。

帮助将自述文件翻译成其他语言。
在自述文件中添加文档(许多 Get 的函数尚未记录)。
撰写文章或制作视频来教授如何使用 Get(它们将插入自述文件中,并且将来会插入我们的 Wiki 中)。
提供代码/测试的 PR。
包括新功能。
欢迎任何贡献!

文章和视频

Flutter GetX使用详解_第3张图片

你可能感兴趣的:(Flutter,flutter,javascript,开发语言)