Flutter中GetX状态管理的终极指南

状态管理是Flutter中一个复杂的讨论话题。然而,许多状态管理库,如Provider,是大多数开发者推荐的。

但是...

今天,我们将讨论一个用于Flutter应用开发的简化状态管理解决方案,它的大部分功能不需要上下文,称为GetX。

目录

什么是GetX?

GetX的三大支柱

GetX的增值功能

让我们开始使用GetX状态管理

第1步:创建一个新的应用程序

第2步:添加所需的依赖项

第三步:更新MaterialApp 小工具

第4步:添加GetX控制器

第5步:依赖性注入

第6步:实例化控制器

第7步:Obx 小工具(观察者)

其他GetX功能

路线管理

增值功能

从浅色主题切换到深色主题,反之亦然

结语

GitHub上的源代码链接

网络应用的链接


什么是GetX?

GetX 不仅是一个状态管理库,而且是一个与路由管理和依赖注入相结合的微框架。它旨在为Flutter提供顶级的开发体验,是一个额外的轻量级但强大的解决方案。GetX有三个基本原则,它是在此基础上建立的。

  1. 性能:注重内存和资源的最小消耗
  2. 生产力:直观和高效的工具,结合简单和直接的语法,最终节省开发时间
  3. 组织性:将业务逻辑与视图和表现逻辑解耦,没有比这更好的了。你不需要上下文在路由之间导航,也不需要有状态的小工具

GetX的三大支柱

  1. 状态管理。GetX有两个状态管理器。一个是与GetBuilder 函数一起使用的简单状态管理器,另一个是与Getx 或Obx 一起使用的反应式状态管理器。我们将在下文中详细论述
  2. 路线管理:无论是在屏幕之间导航、显示SnackBars 、弹出对话框,还是在不使用context 的情况下添加底层表单,GetX都能满足你的要求。我不会写关于路线管理的细节,因为它超出了本文的范围,但确实有几个例子可以让人了解GetX语法的简洁性是如何工作的
  3. 依赖性管理。GetX有一个简单而强大的解决方案,使用控制器进行依赖性管理。只需一行代码,就可以从视图中访问它,而无需使用继承的部件或上下文。通常情况下,你会在一个类中实例化一个类,但使用GetX,你是用Get 实例进行实例化,它将在整个应用程序中可用。

GetX的增值功能

GetX有一些开箱即用的伟大功能,使得在Flutter中开发移动应用程序更加容易,而无需任何模板代码。

  1. 国际化:用键值地图进行翻译,支持各种语言,使用单数、复数和参数的翻译。在整个应用程序中只使用Get 字来改变应用程序的地域性
  2. 验证:电子邮件和密码验证也被GetX覆盖。现在你不需要安装一个单独的验证包。
  3. 存储。GetX还提供了快速和超轻的同步键值数据备份,完全用Dart编写,很容易与GetX核心包集成。
  4. 主题:GetX使浅色和深色主题之间的切换变得简单。
  5. 响应式视图:如果你正在为不同的屏幕尺寸构建一个应用程序,你只需要用GetView ,就可以快速开发你的用户界面,这将是对桌面、平板电脑、手机和手表的响应。

让我们开始使用GetX状态管理

我将一步一步地进行,这是我一直喜欢做的,我将尽量描述并尽可能详细地解释这个过程。

第1步:创建一个新的应用程序

在你喜欢的IDE中创建一个全新的应用程序。首先,通过选择编辑菜单中的查找和替换选项来删除所有的启动器注释,然后输入:\/\/.* 。这将在启动代码中选择Flutter的注释,你可以直接点击删除按钮。

第2步:添加所需的依赖项

在你的pubspec.yaml 文件中添加这些依赖项。

get: ^4.6.1           //YAML
get_storage: ^2.0.3  //YAML

运行这个命令。

flutter pub get  //YAML

在进入第三步之前,让我解释一下我们在这里做什么。我已经创建了一个小的应用程序,演示GetX的核心功能。该应用程序是关于一个商店,用户可以。

  1. 改变商店的名称
  2. 添加关注者的名字
  3. 添加关注者数量
  4. 将商店的状态从开放改为关闭,反之亦然
  5. 给商店添加评论
  6. 将商店的主题从浅色改为深色

上述所有内容将解释状态管理、依赖性管理、路径管理、存储和主题。

我们在这里更关注状态和依赖性管理。路由、存储和主题只是为了应用程序的美感。

你可以通过这个链接来阅读和测试这个应用程序

第三步:更新MaterialApp 小工具

在添加了依赖性之后,你需要做的第一件事是在你的main.dart 文件中把MaterialApp 小工具改为GetMaterialApp 。这样就可以访问整个应用程序的所有GetX属性。

第4步:添加GetX控制器

我们已经确定,GetX将用户界面与业务逻辑分开。这就是GetX控制器发挥作用的地方。

你总是可以在你的应用程序中创建一个以上的控制器。GetX控制器类控制UI的状态,当你用它的Observer ,以便它只在该特定的小部件的状态发生变化时才重建。

我们正在添加一个新的Dart文件来创建我们的控制器类,StoreController ,它扩展了GetxController 。

class StoreController extends GetxController {}

接下来,我们添加一些变量,并用默认值初始化它们。

通常情况下,我们会像下面这样添加这些变量。

final storeName = 'Thick Shake';

但是,在使用GetX时,我们必须通过在值的末尾添加以下内容使变量可被观察到 **obs**在值的末尾加上然后,当变量发生变化时,应用程序中依赖它的其他部分将得到通知。所以现在,我们的初始化值将看起来像这样。

final storeName = 'Thick Shake'.obs;

其余的变量在下面给出。

// String for changing the Store Name
final storeName = 'Thick Shake'.obs;
// int for increasing the Follower count
final followerCount = 0.obs;
// bool for showing the status of the Store open or close
final storeStatus = true.obs;
// List for names of Store Followers
final followerList = [].obs;
// Map for Names and their Reviews for the Store
final reviews = <StoreReviews>[].obs;
// text editing controllers
final storeNameEditingController  = TextEditingController();
final reviewEditingController = TextEditingController();
final followerController = TextEditingController();
final reviewNameController = TextEditingController();

接下来,我们创建三个方法来改变名字,增加追随者数量,以及改变商店状态。

updateStoreName(String name) {
 storeName(name);
}

updateFollowerCount() {
 followerCount(followerCount.value + 1);
}

void storeStatusOpen(bool isOpen) {
 storeStatus(isOpen);
}

第5步:依赖性注入

通俗地说,我们将刚刚创建的controller 类添加到我们的view 类中。有三种方式来进行实例化。

  1. GetView 来扩展整个view 类,用它来注入我们的StoreController :

    class Home extends GetView<StoreController>{}
    
  2. 像这样将storeController 实例化:

    final storeController = Get.put(StoreContoller())
    
  3. 对于方案三,首先创建一个新的StoreBinding 类并实现Bindings 。lazyPut 在其默认的依赖关系中,你需要通过使用Get.lazyPut() ,将StoreController 。其次,你需要在GetMaterialWidget 中的initialBinding 属性内添加绑定类。

最后,代替上面提到的Get.Put ,现在你可以使用Get.find ,当你在任何一个类中实例化时,GetX将为你找到你的控制器。

class StoreBinding implements Bindings {
// default dependency
 @override
 void dependencies() {
   Get.lazyPut(() => StoreController();
 }
}
@override
Widget build(BuildContext context) {
 return GetMaterialApp(
   debugShowCheckedModeBanner: false,
   title: 'GetX Store',
   initialBinding: StoreBinding(),
}
class UpdateStoreName extends StatelessWidget {
 UpdateStoreName({Key? key}) : super(key: key);
//Getx will find your controller.
 final storeController = Get.find<StoreController>();

这个项目中有很多代码和Dart文件。我只写了上面提到的三个方法。其余的代码将在Git上提供。链接将在本文的最后提供。其次,你也可以通过网络链接试用该应用程序

第6步:实例化控制器

由于我们已经用GetView 扩展了我们的Home 视图,并创建了一个绑定类,以在其中创建我们的控制器lazyPut ,现在我们将使用Get.find 来在我们的类中实例化我们的控制器。

首先,我们添加一个新的无状态widget,UpdateStoreName 。像这样实例化我们的controller 类。

final storeController = Get.find<StoreController>();
RoundedInput(
 hintText: "Store Name",
 controller: storeController.storeNameEditingController,
),
const SizedBox(height: 20),
ElevatedButton(
 onPressed: () {
   storeController.updateStoreName(
       storeController.storeNameEditingController.text);
   Get.snackbar(
       'Updated',
       'Store name has been updated ton '
           '${storeController.storeNameEditingController.text}',
       snackPosition: SnackPosition.BOTTOM);
 },
 child: const Padding(
   padding: EdgeInsets.all(10.0),
   child: Text(
     'Update',
     style: TextStyle(fontSize: 20.0),
   ),
 ),
),

让我解释一下上面的代码。RoundedInput 只是一个自定义的TextField ,而我们正在使用我们的storeController ,为TextField ,添加一个TextEditingController 。我们还以同样的方式在ElevatedButton 的onPressed 内调用updateStoreName() 方法。然后我们显示一个SnackBar ,以确认商店名称已被更新。

下面是AddFollowerCount 和StoreStatus 的代码。同样都是无状态的widget,实现storeController 和调用我们的控制器的方法是相似的。

class AddFollowerCount extends StatelessWidget {
 AddFollowerCount({Key? key}) : super(key: key);
 final storeController = Get.find<StoreController>();

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: const Text("Add Follower Count")),
     floatingActionButton: FloatingActionButton(
       onPressed: () {storeController.updateFollowerCount();
       },
       child: const Icon(Icons.add),
     ),
     body: Container(
       padding: const EdgeInsets.all(24),
       child: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             const Text(
               'You have add these many followers to your store',
               textAlign: TextAlign.center,
               style: TextStyle(fontSize: 28),
             ),
             const SizedBox(
               height: 40.0,
             ),
             Obx(
               () => Text(
                 storeController.followerCount.value.toString(),
                 style: const TextStyle(fontSize: 48),
               ),
             )
           ],
         ),
       ),
     ),
   );
 }
}
class StoreStatus extends StatelessWidget {
 StoreStatus({Key? key}) : super(key: key);
 //final storeController = Get.put(StoreController());
 final storeController = Get.find<StoreController>();

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: const Text("Test Status Toggle")),
     body: Container(
       padding: const EdgeInsets.all(24),
       child: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             const Text(
               "Is the Store open?",
               style: TextStyle(fontSize: 22),
             ),
             const SizedBox(height: 16),
             Obx(
               () => Switch(
                 onChanged: (value) => storeController.storeStatus(value),
                 activeColor: Colors.green,
                 value: storeController.storeStatus.value,
               ),
             )
           ],
         ),
       ),
     ),
   );
 }
}

第7步:Obx 小工具(观察者)

现在,让我们进入到使用我们的storeController 来显示我们商店名称的输入值,增加的关注者数量,以及商店状态的部分。

我们的Home 视图被扩展为GetView<StoreController> ,所以我们不需要在这里实例化我们的storeController 。相反,我们可以直接使用GetX的默认控制器。请看下面给出的代码,以获得一个清晰的画面,并理解步骤6和步骤7之间的区别。

你一定注意到,Text widget里面的Flexible widget被一个Obx widget包裹着,在这里我们也调用了我们的controller 。还记得我们是如何将(.obs) 添加到我们的变量中的吗?现在,当我们想看到那个可观察变量的变化时,我们必须用Obx ,也被称为Observer ,类似于你一定在上面的代码中注意到的那样。

Obx 包裹widget,当状态发生变化时,只会重建那个特定的widget,而不是整个类。这就是它的简单之处。

class Home extends GetView<StoreController> {
 Home({Key? key}) : super(key: key);
 
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     backgroundColor: AppColors.spaceCadet,
     appBar: AppBar(
       title: const Text("GetX Store"),),
     drawer: const SideDrawer(),
     body: Container(
       padding: const EdgeInsets.all(10),
       child: SingleChildScrollView(
         child: Column(
           children: [
             MainCard(
               title: "Store Info",
               body: Column(
                 crossAxisAlignment: CrossAxisAlignment.stretch,
                 children: [
                   Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                     children: [
                       const Flexible(
                         child: Text('Store Name:',
                           style: TextStyle(fontSize: 20),),
                         fit: FlexFit.tight,),
                       const SizedBox(width: 20.0),
                   // Wrapped with Obx to observe changes to the storeName
                   // variable when called using the StoreController.
                       Obx(
                         () => Flexible(
                           child: Text(
                             controller.storeName.value.toString(),
                             style: const TextStyle(
                             fontSize: 22, fontWeight: FontWeight.bold) ),
                           fit: FlexFit.tight,
                         ),),
                     ],),
                   const SizedBox(height: 20.0),
                   Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                     children: [
                       const Flexible(
                         child: Text('Store Followers:',
                           style: TextStyle(fontSize: 20),),
                         fit: FlexFit.tight, ),
                       const SizedBox(width: 20.0),
               // Wrapped with Obx to observe changes to the followerCount
               // variable when called using the StoreController.
                       Obx(
                         () => Flexible(
                           child: Text(
                             controller.followerCount.value.toString(),
                             textAlign: TextAlign.start,
                             style: const TextStyle(
                             fontSize: 22, fontWeight: FontWeight.bold),
                           ),
                           fit: FlexFit.tight,),), ],
                   ),
                   const SizedBox(height: 20.0),
                   Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                     children: [
                       const Flexible(
                         child: Text('Status:',
                           style: TextStyle(fontSize: 20),),
                         fit: FlexFit.tight,),
                       const SizedBox(width: 20.0),
                 // Wrapped with Obx to observe changes to the storeStatus
                 // variable when called using the StoreController.
                       Obx(
                         () => Flexible(
                           child: Text(
                        controller.storeStatus.value ? 'Open' : 'Closed',
                             textAlign: TextAlign.start,
                             style: TextStyle(
                                 color: controller.storeStatus.value
                                     ? Colors.green.shade700
                                     : Colors.red,
                                 fontSize: 22,
                                 fontWeight: FontWeight.bold),),
                           fit: FlexFit.tight,
                         ),  ),  ], ), ], ), ),

我特意强调了controllers 和Obx ,以了解Flutter提供的默认有状态的widget和使用GetX管理视图或整个应用程序的状态之间的区别。

如果我们使用一个有状态的widget,我们就必须在每次想看到变化时使用setState() 方法。我们还必须手动处理controllers 。因此,我们避免了所有的模板代码,而只是用Obx 来包装我们的widget,其余的都被处理了。

如果我们要总结以上所有的内容,只需要两个步骤就可以完成。

  1. 在你的变量中添加obs
  2. 将你的小部件用Obx

另一种方法

好吧,这并不是唯一的方法。例如,如果你让你的变量是可观察的,你也可以直接用GetX<StoreController> ,而不是Obx 来包装小部件。然而,功能仍然是一样的。这样一来,你就不需要在调用storeController 之前进行实例化。请看下面的代码。

// Wrapped with GetX<StoreController> to observe changes to the
//storeStatus variable when called using the StoreController.
GetX<StoreController>(
 builder: (sController) => Flexible(
   child: Text(
     sController.storeStatus.value ? 'Open' : 'Closed',
     textAlign: TextAlign.start,
     style: TextStyle(
         color: sController.storeStatus.value
             ? Colors.green.shade700
             : Colors.red,
         fontSize: 22,
         fontWeight: FontWeight.bold), ),
   fit: FlexFit.tight, ),),

注意,我已经把storeStatus 从Obx 改成了GetX<StoreController> ,它使用了building 函数中的sController 。

Obx 或GetX 包裹小部件被称为反应式状态管理。

简单的状态管理

让我们看一个简单状态管理的例子。首先,使用简单状态管理的好处是,你不需要把你的MaterialWidget 改为GetMaterialWidget 。其次,你可以将其他状态管理库与简单状态管理结合起来。

注意,如果你不把你的MaterialWidget GetMaterialWidget ,你将不能使用其他GetX功能,如路由管理。

对于简单的状态管理。

  1. 你需要使用GetBuilder 函数
  2. 你不需要observable 变量
  3. 你必须在你的方法中调用update() 函数。

我已经在我们的StoreController 中创建了一个新的变量。但是这一次,我没有在变量的末尾添加(obs) 。这意味着现在它是不可观察的。

但是我仍然需要我的视图在商店数量增加时得到更新,所以我必须在我新创建的方法中调用update() 函数。请看下面的代码。

// variable is not observable
int storeFollowerCount = 0;

void incrementStoreFollowers() {
 storeFollowerCount++;
//update function needs to be called
 update();
}

现在,在我们的主页视图中,我已经将Obx 改为GetBuilder 到Text widget,它显示了follower count。

GetBuilder<StoreController>(
 builder: (newController) => Flexible(
   child: Text(
     newController.storeFollowerCount.toString(),
     textAlign: TextAlign.start,
     style: const TextStyle(
         fontSize: 22, fontWeight: FontWeight.bold),
   ),
   fit: FlexFit.tight, ),),

由于我们在主视图中用GetBuilder 来包装我们的关注者数量,我们也必须对AddFollowerCount Dart文件进行修改。

  1. Fab 按钮的onPressed 函数内添加这个:

    storeController.incrementStoreFollowers();
    
    1. Text 小组件与GetBuilder 包装在一起,这样它就可以显示关注者数量:

      GetBuilder<StoreController>(
       builder: (newController) => Text(
         'With GetBuilder: ${newController.storeFollowerCount.toString()}',
         textAlign: TextAlign.start,
         style: const TextStyle(
             fontSize: 22, fontWeight: FontWeight.bold), ),),
      

在使用Obx 或GetX 和使用GetBuilder 之间还有一个区别。当使用Obx 或GetX 时,你需要在使用StoreController调用你的方法后添加值。但是当使用GetBuilder ,你不需要为它添加一个值参数。请看下面的区别。

// value parameter to be added with Obx or GetX
controller.storeName.value.toString(),

// value parameter is not needed with GetBuilder
newController.storeFollowerCount.toString(),

这都是针对GetX提供的不同状态管理的。此外,按照承诺,我将写一点关于路由管理和GetX包的其他功能。因此,需要写一篇新的文章来详细介绍这一切。

其他GetX功能

路线管理

传统上,当用户想通过点击一个按钮从一个屏幕到另一个屏幕时,代码会是这样的。

Navigator.push(context, 
    MaterialPageRoute(builder: (context)=> Home()));

但是,有了GetX,简直就只有两个字。

Get.to(Home());

当你想导航回前一个屏幕时。

Navigator.pop(context);

当你使用GetX时,完全没有必要考虑上下文。

Get.back();

如果您打开了一个对话框或抽屉,并想在关闭抽屉或对话框的同时导航到另一个屏幕,用默认的Flutter导航有两种方法可以做到这一点。

  1. 关闭抽屉或对话框,然后像这样导航:

    Navigator.pop(context);
    Navigator.push(context, 
        MaterialPageRoute(builder: (context)=> SecondScreen()));
    
  2. 如果你已经生成了命名路线:

    Navigator.popAndPushNamed(context, '/second');
    

有了GetX,在关闭任何打开的对话框或抽屉的同时,生成命名的路线和在屏幕之间导航就变得简单多了。

// for named routes
Get.toNamed('/second'),
// to close, then navigate to named route
Get.offAndToNamed('/second'),

增值功能

  1. 黑板

    Get.snackbar(
       'title',
       'message',
       snackPosition: SnackPosition.BOTTOM,
    colorText: Colors.white,
    backgroundColor: Colors.black,
    borderColor: Colors.white);
    
  2. 对话框

    Get.defaultDialog(
       radius: 10.0,
       contentPadding: const EdgeInsets.all(20.0),
       title: 'title',
       middleText: 'content',
       textConfirm: 'Okay',
       confirm: OutlinedButton.icon(
         onPressed: () => Get.back(),
         icon: const Icon(
           Icons.check,
           color: Colors.blue,     ),
         label: const Text('Okay',
           style: TextStyle(color: Colors.blue),
         ),   ),
     cancel: OutlinedButton.icon(
         onPressed: (){},
         icon: Icon(),
         label: Text(),),);
    
  3. 底层表单

    Get.bottomSheet(
       Container(
     height: 150,
     color: AppColors.spaceBlue,
     child: Center(
         child: Text(
       'Count has reached ${obxCount.value.toString()}',
       style: const TextStyle(fontSize: 28.0, color: Colors.white),
     )),
    ));
    

看了上面的代码,你可以很容易地理解显示和定制黑板、对话框和底层表单是多么简单。

嗯,这只是冰山一角。用GetX库可以做的事情还有很多。在我的文章结束之前,最后一个例子是在浅色和深色主题之间切换。

从浅色主题切换到深色主题,反之亦然

首先,我创建了一个类似于我们的ThemeController StoreController 。在我的控制器中,我使用GetStorage 函数来保存切换后的主题。

class ThemeController extends GetxController {
  final _box = GetStorage();
  final _key = 'isDarkMode';

  ThemeMode get theme => _loadTheme() ? ThemeMode.dark : ThemeMode.light;
  bool _loadTheme() => _box.read(_key) ?? false;

  void saveTheme(bool isDarkMode) => _box.write(_key, isDarkMode);
  void changeTheme(ThemeData theme) => Get.changeTheme(theme);
  void changeThemeMode(ThemeMode themeMode) => Get.changeThemeMode(themeMode);
}

GetMaterialApp widget里面,我为theme 和darkTheme 添加了属性,同时初始化了themeController ,并为themeMode 添加了同样的属性。

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  final themeController = Get.put(ThemeController());

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'GetX Store',
      initialBinding: StoreBinding(),
      theme: Themes.lightTheme,
      darkTheme: Themes.darkTheme,
      themeMode: themeController.theme,
}
}

接下来,在我们的屏幕上的appBar ,我已经添加了一个图标,在浅色和深色之间切换主题。请看下面的代码。

class Home extends GetView<StoreController> {
 Home({Key? key}) : super(key: key);
 final themeController = Get.find<ThemeController>();

 @override
 Widget build(BuildContext context) {
   return Scaffold(backgroundColor: AppColors.spaceCadet,
     appBar: AppBar(title: const Text("GetX Store"),
       actions: [IconButton(
           onPressed: () {
             if (Get.isDarkMode) {
               themeController.changeTheme(Themes.lightTheme);
               themeController.saveTheme(false);
             } else {
               themeController.changeTheme(Themes.darkTheme);
               themeController.saveTheme(true); }},
           icon: Get.isDarkMode
               ? const Icon(Icons.light_mode_outlined)
               : const Icon(Icons.dark_mode_outlined),),], ),

就这样了。现在你可以轻松地在浅色和深色主题之间切换。

结语

读完整篇文章后,你是否想知道为什么这个库的创建者给它取名为GetX?以我的愚见,人们经常下命令说:"把它做好!"或 "让我们把它做好!"

然而,x是一个未知的变量,但在这种情况下,它真的是任何东西和一切。

你可以用GetX来完成一切。

现在就说到这里,感谢您的阅读。如果你有任何建议、更正或反馈,请在下面留言。

我在下面留下链接,以获取我在本文中解释的应用程序的源代码和一个额外的计数器应用程序的基础知识。请随意克隆Git仓库,并自己尝试使用代码。还有一些PWA的链接,可以在不安装的情况下试用该应用程序。

GitHub上的源代码链接

GetX商店链接:github.com/timelessfus…

GetX计数器链接:github.com/timelessfus…

网络应用的链接

GetX商店链接:getx-store.web.app/#/

GetX计数器应用:https://getx-counter.web.app/#/

The post Theultimate guide to GetX state management in Flutterappeared first onLogRocket Blog

 

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/126695702