了解用于状态管理的 Flutter GetX 生态系统

180 阅读14分钟

在靠近用户的地方部署容器

本工程教育(EngEd)计划由科支持。

在全球范围内即时部署容器。Section是经济实惠、简单而强大的。

免费入门

了解用于状态管理的Flutter GetX生态系统

9月21日, 2021

状态管理使你能够将数据从一个用户界面传递到另一个界面。当你的应用程序的状态改变时,系统会重建用户界面。

Flutter传统上使用Stateful Widget来管理状态。然而,这在一个复杂的应用程序中是相当难以实现的。

有状态的小部件通过子小部件的构造器传递数据。虽然这个功能很有用,但它会导致数据被传递给不需要它的部件。

另一个缺点是,业务逻辑与用户界面紧密相连。这可能会导致混乱。

目标

本文教你如何使用GetX状态管理包来解决Flutter中的状态管理问题。

在本教程中,我们将建立一个购物移动应用,允许用户查看产品、喜欢的物品、将产品添加到购物车,以及下订单。

下单成功后,用户仍然可以访问其他产品。通过这个应用程序,我们将展示GetX软件包的强大功能。

主要收获

  • 如何设置Flutter项目和配置依赖性。
  • 如何使用GetX作为一个状态管理工具。
  • 使用Obx来最大化反应式编程的力量。
  • 探索Getx的导航功能。
  • 如何使用GetBuilder子生态系统来管理状态。
  • GetX生态系统的可重复使用的组件。

先决条件

要跟上进度,你应该具备以下条件

在Android Studio中创建一个Flutter应用程序

在这个项目中,我们将使用Android Studio。

为了开始,启动Android studio并创建一个新的Flutter项目。确保你将类型设置为Flutter application

确保你选择了你的Flutter SDK所在的路径,然后点击下一步。

creating a new Flutter project

接下来,填写以下项目细节,以完整地设置该项目。

  • 由于我们正在建立一个网上商店,我们可以将项目命名为shopping_getx
  • 选择一个您希望保存项目的目录。
  • 添加一个项目描述。
  • 选择一个合适的软件包名称。
  • 将默认的Android和iOS语言分别保留为Kotlin和Swift。

Adding project details

这将在main.dart 文件中生成默认的Flutter计数器项目。

整合GetX生态系统

什么是GetX?

GetX是一个简单而强大的Flutter包。GetX包的主要支柱是高性能状态管理、智能依赖注入和路由管理。

GetX通过简单愉快的语法帮助开发者实现高水平的生产力,而不牺牲应用程序的性能。

它支持用户界面、表现逻辑、业务逻辑、依赖注入和导航的解耦。这有助于在默认情况下产生干净的代码。

GetX Documentation

要将GetX集成到应用程序中。请访问GetX文档,复制get: ^4.3.8 ,并将其添加到项目pubspec.yaml 文件中,在dependencies section ,然后运行pub get 命令。

这将把GetX生态系统安装到你的项目中。

intl: ^0.17.0 添加到dependencies section ,以安装intl package ,然后运行pub get ,以安装该依赖关系。

Pubspec file

用以下内容替换main.dart 文件中生成的代码。

import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:shopping_app/screens/product_overview_screen.dart';


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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return  GetMaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.purple,
          accentColor: Colors.deepOrange,
          fontFamily: "Lato",
        ),
        home: ProductOverviewPage(),

    );
  }
}

在上面的代码中。

我们已经创建了main 方法,它是Dart VM 执行程序的入口点。main 方法运行MyApp 类。

MyApp class 扩展了无状态部件,并重写了构建方法。它还返回一个 。GetMaterialApp

GetMaterialApp:构建方法是返回GetMaterialApp而不是通常的MaterialApp。这是因为我们可以使用GetX进行导航。GetMaterialApp 是来自Getx 包的一个类。

homepage ,我们正在渲染ProductOverviewPage

项目结构

我们将对项目进行如下结构。

lib folder ,我们将创建以下文件夹。

  • screens (放置我们的用户界面)。
  • controller (业务逻辑)。
  • models (要存储的数据的对象表示)。

让我们从models 开始。

模型

models 文件夹内,创建一个名为product.dart 的dart文件,如下所示。

产品模型

class Product{
  final int   id;
  final String productTitle;
  final String imageUrl;
  final String description;
  final double price;
   bool isFavourite;

  Product(
      {required this.id,
      required this.productTitle,
      required this.imageUrl,
      required this.description,
      required this.price,
      this.isFavourite = false});

}

这只是一个包含产品字段的Dart类。

我们添加了一个构造函数来初始化所有字段。isFavourite 变量被设置为false

CartItem模型

class CartItem {
  final String id;
  final String productTitle;
  final int productQuantity;
  final double productPrice;

  CartItem(
      {required this.id,
      required this.productTitle,
      required this.productQuantity,
      required this.productPrice});
}

我们创建了一个Dart类来存储CartItem 字段。

然后构造函数初始化了所需的字段。

订单类

import 'package:shopping_app/models/cart_item.dart';

class Order {
  final String orderId;
  final double amount;
  final List<CartItem> products;
  final DateTime dateTime;

  Order(
      {required this.orderId,
      required this.amount,
      required this.products,
      required this.dateTime});
}

这只是一个Dart类,包含了将要下的订单的字段。

控制器

我们所有的业务逻辑都将在控制器上。这将使我们很容易跟踪不同的问题或错误。

controller folder ,创建一个名为product_controller 的文件,添加以下代码。

产品控制器

import 'package:get/get.dart';
import 'package:shopping_app/models/product.dart';

class ProductController extends GetxController {
  List<Product> _items = [
    Product(
      id: 1,
      productTitle: 'Sport Shoe',
      description: 'Made for you Check it out!',
      price: 7000.00,
      imageUrl:
         'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Cavalier-Black-1.jpg?v=1589391819',
    ),
    Product(
        id: 2,
        productTitle: 'Legend',
        description:
            'Built to last forever, StormKing™ lug rubber outsoles and a flexible elastic goring, this can only be for the Legends and i bet you, you have not seen it anywhere.',
        price: 63000.00,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-Legend-BlackMatte-3.4_672x672.jpg?v=1600886623'),
    Product(
        id: 3,
        productTitle: 'The Chelsea',
        description: 'Functional and Fashionable.',
        price: 49.00,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Cavalier-Black-1.jpg?v=1589391819'),
    Product(
        id: 4,
        title: 'Men\'s Sneakers',
        productTitle: 'Clean & Comfortable Sneakers made with classic Leathers.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-PremierLowTop-Black-3.4.jpg?v=1600270679'),
    Product(
        id: 5,
        productTitle: 'The Chelsea',
        description:
            'Comfortable as you\'d expect.This can only be found at Resilient collection.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Captain-Natural-3.jpg?v=1584114360'),
    Product(
        id: 6,
        productTitle: 'Men\'s Sneakers',
        description: 'Clean & Comfortable Sneakers made with classic Leathers.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-PremierLowTop-Black-3.4.jpg?v=1600270679'),
    Product(
        id: 7,
        productTitle: 'The Chelsea',
        description:
            'Made for the men who understand what classic means, every bit was carefully selected so you can go the extra mile with confidence and alacrity.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-Cavalier-Toffee-210402-2.jpg?v=1618424894'),
    Product(
        id: 8,
        productTitle: 'Men\'s Sneakers',
        description: 'Clean & Comfortable Sneakers made with classic Leathers.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-Cavalier-Toffee-210402-3.jpg?v=1618424894'),
    Product(
        id: 9,
        productTitle: 'The Chelsea',
        description: 'Functional and Fashionable.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Cavalier-Black-1.jpg?v=1589391819'),
    Product(
        id: 10,
        productTitle: 'Men\'s Sneakers',
        description: 'Clean & Comfortable Sneakers made with classic Leathers.',
        price: 49.99,
        imageUrl:
            'https://cdn.shopify.com/s/files/1/0419/1525/products/1024x1024-Men-PremierLowTop-Black-3.4.jpg?v=1600270679'),
  ];

  List<Product> get items {
    return [..._items];
  }

  List<Product> get favouriteItems {
    return _items.where((productItem) => productItem.isFavourite).toList();
  }

  Product findProductById(int id) {
    return _items.firstWhere((element) => element.id == id);
  }


  void toggleFavouriteStatus(int id) {
    items[id].isFavourite = !items[id].isFavourite;
    update();
  }
}

我们刚刚创建的ProductController 类是对GetxController 类的扩展,它是对DisposableInterface 的一个抽象类。

通过扩展DisposableInterface ,GetX帮助我们减少内存的消耗,在使用它的部件从导航栈中删除后,立即从内存中删除我们的控制器。

我们在控制器中需要的是我们想要绑定到用户界面的元素。在这种情况下,一个列表Products ,因此我们创建了一个字段_items ,其中包含所有产品的列表。

_items 中的下划线使其成为私有。我们是硬编码的,但我们同样可以从后端服务器检索产品。

接下来,我们创建了两个返回所有产品的getters,包括那些被标记为最喜欢的产品。

findProductById 方法接收一个ID 作为参数,并返回具有该特定ID 的产品。

toggleFavouriteStatus 方法接收一个ID ,并将带有该ID 的产品标记为收藏夹。

我们从Getx 中调用更新方法,以便在点击时改变用户界面。update方法监听toggleFavouriteStatus 方法中的变化,并更新相应的用户界面。

如果你熟悉Provider包,update 方法的功能与notifyListeners

购物车控制器

CartController 将包含关于如何从购物车中添加和删除物品的业务逻辑。

它还将包含返回购物车中所有物品的方法,以及购物车中所有物品的总金额。

controllers 文件夹中创建一个名为cart_controller.dart 的dart文件,然后添加以下代码。

import 'dart:core';
import 'package:get/get.dart';
import 'package:shopping_app/models/cart_item.dart';

class CartController extends GetxController {
  Map<int, CartItem> _items = {};

  Map<int, CartItem> get items {
    return {..._items};
  }

  int get itemCount {
    // return  _items?.length?? 0;
    return _items.length;
  }

  double get totalAmount {
    var total = 0.0;
    _items.forEach((key, cartItem) {
      total += cartItem.price * cartItem.quantity;
    });
    return total;
  }

  void addItem(int productId, double price, String title, int quantity) {
    if (_items.containsKey(productId)) {
      _items.update(
          productId,
          (existingCartItem) => CartItem(
              id: existingCartItem.id,
              title: existingCartItem.title,
              quantity: existingCartItem.quantity + 1,
              price: existingCartItem.price));
    } else {
      _items.putIfAbsent(
        productId,
        () => CartItem(
          id: DateTime.now().toString(),
          title: title,
          price: price,
          quantity: 1,
        ),
      );
    }
    update();
  }

  void removeitem(int productId) {
    _items.remove(productId);
    update();
  }

  void clear() {
    _items = {};
    update();
  }
}

我们已经创建了一个CartContoller 类,它扩展了GetXController 。记得从包中导入GetXController

我们还创建了一个map ,用来存放CartItem 对象。

我们包括两个getters,分别返回购物车中的所有物品和物品的数量。

接下来,我们创建了一个totalAmount 方法,计算并返回购物车中所有产品的总金额。

addItem 方法将产品添加到购物车中。首先,我们检查产品是否已经存在于购物车中,如果是,我们更新数量,否则,我们将其添加到购物车中。

removeitem 方法接收一个productId ,并从购物车中删除一个带有该ID 的产品。

一旦成功下了订单,clear 方法将清除购物车。

注意,我们在所有创建的方法中都调用了GetX ,以监听变化,并在需要这些数据的地方更新相应的用户界面。

OrderController

OrderController 将包含下订单的方法。

controller folder 创建一个名为order_controller.dart 的dart文件,并添加以下代码。

import 'package:get/get.dart';
import 'package:shopping_app/models/cart_item.dart';
import 'package:shopping_app/models/order.dart';

class OrderController extends GetxController {
  List<Order> _orders = [];

  List<Order> get orders {
    return [..._orders];
  }

  void addOrder(List<CartItem> cartProducts, double total) {
    _orders.insert(
        0,
        Order(
            id: DateTime.now().toString(),
            products: cartProducts,
            amount: total,
            dateTime: DateTime.now()));
    update();
  }
}

我们创建了一个保存所有订单的列表。

接下来,我们创建了一个名为orders 的getter,以返回所有下的订单。

addOrder 方法接收了一个列表CartItem ,这是已经添加到购物车的产品,以及一个类型为doubletotal ,这是添加到购物车的所有产品的总和,并下了一个订单。

我们再次调用update 方法来监听变化并更新用户界面。

用户界面

screens 文件夹中,创建一个dart文件product_overview_screen.dart ,并添加以下代码。

ProductOverviewPage

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/cart_controller.dart';
import 'package:shopping_app/screens/cart_screen.dart';
import 'package:shopping_app/widgets/app_drawer.dart';
import 'package:shopping_app/widgets/badge.dart';
import 'package:shopping_app/widgets/productgrid.dart';

class ProductOverviewPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cartController = Get.put(CartController());

    return Scaffold(
      appBar: AppBar(
        title: Text("My Shop"),
        actions: <Widget>[
          GetBuilder<CartController>(
              init: CartController(),
              builder: (contex) {
                return Badge(
                  child: IconButton(
                      icon: Icon(
                        Icons.shopping_cart,
                      ),
                      onPressed: () {
                        Get.to(() => CartScreen());
                      }),
                  value: cartController.itemCount.toString(),
                  color: Theme.of(context).accentColor,
                );
              })
        ],
      ),
      drawer: AppDrawer(),
      body: ProductsGrid(),
    );
  }
}

依赖性注入允许将一个类的实例注入另一个类中。

为了定义什么是依赖,如果C类使用D类的功能,那么D就是C的依赖。

Getx 允许你只用一行代码就可以进行依赖性注入。

final cartController = Get.put(CartController());

我们已经将cartController 注入我们的用户界面,这样我们就可以访问控制器上的数据。

GetBuilder 在任何一个widget上都包裹着一个 "依赖",使其与控制器的方法和变量进行交互。

无论哪个widget被包装成GetBuilder ,Getx都会对其应用setState 。有了这个,我们就能在CartController 类中调用itemCount 函数。

  • 我们使用Getx navigation manager ,即使在按下shopping_cart icon ,也能导航到CartScreen 页面。

在这个类的主体中,我们调用了ProductsGrid 类来返回一个显示所有产品的网格。

小工具

我们已经分解了我们的用户界面,以保持其简单和可重复使用。

创建一个名为widget 的文件夹。在widget folder ,创建一个名为productgrid.dart 的dart文件。

产品网格类

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/cart_controller.dart';
import 'package:shopping_app/controllers/product_controller.dart';
import 'package:shopping_app/screens/product_details_screen.dart';

class ProductsGrid extends StatelessWidget {
  final controller = Get.put(ProductController());
  final cartController = Get.put(CartController());

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(10),
      itemCount: controller.items.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10),
      itemBuilder: (context, index) {
        return GetBuilder(
          init: ProductController(),
          builder: (value) => ClipRRect(
            borderRadius: BorderRadius.circular(10),
            child: GridTile(
              child: GestureDetector(
                onTap: () {
                  Get.to(
                    ProductDetailsScreen(
                      controller.items[index].title,
                      controller.items[index].price,
                      controller.items[index].imageUrl,
                      controller.items[index].description,
                    ),
                  );
                },
                child: Image.network(
                  controller.items[index].imageUrl,
                  fit: BoxFit.cover,
                ),
              ),
              footer: GridTileBar(
                backgroundColor: Colors.black87,
                leading: IconButton(
                  icon: Icon(
                    controller.items[index].isFavourite == true
                        ? Icons.favorite
                        : Icons.favorite_border,
                    color: Theme.of(context).accentColor,
                  ),
                  onPressed: () {
                    controller.toggleFavouriteStatus(index);
                  },
                ),
                title: Text(
                  controller.items[index].title,
                  textAlign: TextAlign.center,
                ),
                trailing: GetBuilder<CartController>(
                    init: CartController(),
                    builder: (cont) {
                      return IconButton(
                        icon: Icon(Icons.shopping_cart),
                        onPressed: () {
                          cartController.addItem(
                              controller.items[index].id,
                              controller.items[index].price,
                              controller.items[index].title,
                              1);
                        },
                        color: Theme.of(context).accentColor,
                      );
                    }),
              ),
            ),
          ),
        );
      },
    );
  }
}

我们将ProductControllerCartController 注入到ProductGrid 类中,以便能够访问其中定义的功能。

我们用一个GetBuilder 来包装ClipRRect widget,以便在状态改变时更新它。通过注入的ProductController ,我们显示了产品,显示它们的titleimage

我们还使用了导航器管理器,当某个产品被点击时,通过简单地调用Get.to() ,并传入产品的titlepriceimagedescription ,来路由到ProductDetailsScreen

当最喜欢的图标被点击时,我们调用产品控制器来访问toggleFavouriteStatus 函数,并适当地改变图标的颜色。

shopping_cart 图标已经被包裹在Getbuilder 中,所以每当它被点击时,我们通过调用CartController 中的addItem 函数将产品添加到购物车中。

ProductDetails屏幕

screens 文件夹中创建一个名为product_details_screen.dart 的dart文件,并添加以下代码。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/cart_controller.dart';
import 'package:shopping_app/controllers/product_controller.dart';

class ProductDetailsScreen extends StatelessWidget {

  final String title;
  final double price;
  final String image;
  final String description;

  ProductDetailsScreen(this.title, this.price, this.image, this.description);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(this.title),
      ),
      body: SingleChildScrollView(
        child: Container(
          color: Color(0xffF6F6F6),
          child: Column(
            children: [
              Container(
                child: ClipRRect(
                  borderRadius: BorderRadius.only(
                      bottomLeft: Radius.circular(25),
                      bottomRight: Radius.circular(25)),
                  child: Image.network(
                    this.image,
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(10.0),
                child: Column(
                  children: [
                        Chip(
                          label: Text(
                            "Price: " + "₦" + this.price.toString(),
                            textAlign: TextAlign.center,
                            style: TextStyle(
                                color: Colors.white,
                                fontSize: 24,
                                fontWeight: FontWeight.bold
                            ),
                          ),
                          backgroundColor: Theme.of(context).primaryColor,
                        ),
                    SizedBox(height: 15),
                    Text(
                      "" + this.description,
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        color: Color(0xff403B58),
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

每当人们点击一个产品时,我们使用构造函数从productGrid 类中传入详细信息。

CartScreen

screens folder ,创建一个名为cart_screen.dart 的dart文件,并编写以下代码。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/cart_controller.dart';
import 'package:shopping_app/controllers/order_controller.dart';
import 'package:shopping_app/widgets/cart_items.dart';

class CartScreen extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    var cartController = Get.put(CartController());
    var orderController = Get.put(OrderController());

    return Scaffold(
      appBar: AppBar(
        title: Text("Your cart"),
      ),
      body: GetBuilder<CartController>(
        init: CartController(),
        builder: (cont) => Column(
          children: <Widget>[
            Card(
              margin: EdgeInsets.all(15),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text(
                      "Total",
                      style: TextStyle(
                        fontSize: 20,
                      ),
                    ),
                    Spacer(),
                    Chip(
                      label: Text(
                        '₦${cartController.totalAmount.toStringAsFixed(2)}',
                        style: TextStyle(
                          color: Colors.white,
                        ),
                      ),
                      backgroundColor: Theme.of(context).primaryColor,
                    ),
                    GetBuilder<OrderController>(
                        init: OrderController(),
                        builder: (context) {
                          return TextButton(
                              onPressed: () {
                                orderController.addOrder(
                                    cartController.items.values.toList(),
                                    cartController.totalAmount);
                                cartController.clear();
                                Get.snackbar(
                                  "Orders",
                                  "Orders placed successfully",
                                  backgroundColor: Colors.green,
                                  snackPosition: SnackPosition.BOTTOM
                                );
                              },
                              child: Text('ORDER NOW'));
                        })
                  ],
                ),
              ),
            ),
            SizedBox(
              height: 10,
            ),
            Expanded(
              child: ListView.builder(
                  itemCount: cartController.items.length,
                  itemBuilder: (context, index) => CartItem(
                        cartController.items.values.toList()[index].id,
                        cartController.items.values.toList()[index].price,
                        cartController.items.values.toList()[index].quantity,
                        cartController.items.values.toList()[index].title,
                        cartController.items.keys.toList()[index],
                      )),
            ),
          ],
        ),
      ),
    );
  }
}

我们已经把OrderControllerCartController 注入到CartScreen类中,以访问它们的功能。

我们用GetBuilder 来更新那些需要在状态变化时重建的小部件。总金额通过CartController 进行相应的更新。

Listview.builder widget被用来渲染所有添加到购物车的产品列表,也就是Cartitem 类。

如前所述,我们正在显示购物车中产品的title,amount,quantity, 和price

我们使用注入的OrderController 的实例来调用addOrder 函数,这样,只要点击TextButton 小部件,就会有一个订单被下。

如果订单成功下达,我们使用GetX的snackbar ,向用户显示订单已成功下达的信息,如下图所示。

Get.snackbar(
            "Orders",
            "Orders placed successfully",
              backgroundColor: Colors.green,
              snackPosition: SnackPosition.BOTTOM
                  );

一旦下单成功,我们就从cartController ,调用clear 方法来清除购物车。

购物车项目

widget folder 中创建一个cart_items.dart 文件,代码如下。

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/cart_controller.dart';

class CartItem extends StatelessWidget {
  final String id;
  final int productId;
  final double price;
  final int quantity;
  final String title;

  CartItem(this.id, this.price, this.quantity, this.title, this.productId);

  @override
  Widget build(BuildContext context) {
    var cartController = Get.put(CartController());
    return Dismissible(
      key: ValueKey(id),
      background: Container(
        color: Theme.of(context).errorColor,
        child: Icon(Icons.delete, color: Colors.white,size: 40,

        ),
        alignment: Alignment.centerRight,
        padding: EdgeInsets.only(right: 20),
        margin:  EdgeInsets.symmetric(horizontal: 15, vertical: 4),

      ),
      direction: DismissDirection.endToStart,
      onDismissed: (direction){
        cartController.removeitem(productId);

      },
      child: Card(
        margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: ListTile(
            leading: Chip(
              label: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text('₦$price'),
              ),
              backgroundColor: Theme.of(context).primaryColor,
            ),
            title: Text(title),
            subtitle: Text('Total: ₦${(price * quantity)}'),
            trailing: Text('$quantity X'),
          ),
        ),
      ),
    );
  }
}

我们已经创建了CartItem 类,它扩展了无状态部件。字段包括id,productId,price,quantity, 和title 。构造函数被用来初始化这些字段。

我们使用注入的CartController 来访问removeitem 方法,从而在Dismissible widget被刷的时候从购物车中删除一个产品。

订单屏幕

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/controllers/order_controller.dart';
import 'package:shopping_app/widgets/app_drawer.dart';
import 'package:shopping_app/widgets/order_item.dart';

class OrderScreen extends StatelessWidget {
  var orderController = Get.put(OrderController());
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Yours Orders"),
      ),
      drawer: AppDrawer(),
      body: ListView.builder(
          itemCount: orderController.orders.length,
          itemBuilder: (context, index) =>
              OrderItem(orderController.orders[index])),
    );
  }
}

在上面的文件中。

OrderScreen 类渲染了OrderItem widget。

我们注入了OrderController 来访问orders ,其中包含了所有订单的列表。

我们渲染了AppDrawer ,以显示OrdersShops ,这取决于所选的订单。

订单项

import 'package:flutter/material.dart';
import 'package:shopping_app/models/order.dart';
import 'package:intl/intl.dart';
import 'dart:math';

class OrderItem extends StatefulWidget {
  final Order order;

  OrderItem(this.order);

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

class _OrderItemState extends State<OrderItem> {
  var _isExpanded = false;
  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          ListTile(
            title: Text('${widget.order.amount.toStringAsFixed(2)}'),
            subtitle: Text(
                DateFormat('dd/MM/yyyy hh:mm').format(widget.order.dateTime)),
            trailing: IconButton(
              icon: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more),
              onPressed: () {
                setState(() {
                  _isExpanded = !_isExpanded;
                });
              },
            ),
          ),
          if (_isExpanded)
            Container(
              padding: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
              height: min(widget.order.products.length * 20 + 10, 180),
              child: ListView(
                children: widget.order.products
                    .map(
                      (product) => Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: <Widget>[
                          Text(
                            product.title,
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 18,
                            ),
                          ),
                          Text(
                            '${product.quantity}X ₦${product.price}',
                            style: TextStyle(
                              fontSize: 18,
                              color: Colors.grey,
                            ),
                          ),
                        ],
                      ),
                    )
                    .toList(),
              ),
            )
        ],
      ),
    );
  }
}

上面的类渲染了所下订单的列表。

AppDrawer

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shopping_app/screens/order_screen.dart';
import 'package:shopping_app/screens/product_overview_screen.dart';

class AppDrawer extends StatelessWidget {
  const AppDrawer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        children: <Widget>[
          AppBar(
            title: Text("Hello Friend"),
            automaticallyImplyLeading: false,
          ),
          Divider(),
          ListTile(
            leading: Icon(Icons.shop),
            title: Text("Shop"),
            onTap: () {
              Get.to(() => ProductOverviewPage());
            },
          ),
          Divider(),
          ListTile(
            leading: Icon(Icons.payment),
            title: Text("Orders"),
            onTap: () {
              Get.to(() => OrderScreen());
            },
          ),
        ],
      ),
    );
  }
}

这个类返回一个带有Drawer widget的Column,所以用户可以选择导航到显示所有产品的ProductOverviewPage

他们也可以导航到order 页面,该页面有已下订单的列表。

OBX

虽然GetBuilder速度快,内存占用少,但它不是反应式的。

Obx是GetX生态系统中的一个反应式状态管理器。GetX将反应式编程范式变成相当简单的东西。

  • 没有必要再为每个变量创建StreamControllers和StreamBuilder。
  • OBX为你省去了为每个状态创建一个类的压力,以及使用代码生成器。

本教程的重点是GetBuilder,然而,如果我们要使用反应式流(OBX),OrderController 类会是这样的。

import 'package:get/get.dart';
import 'package:shopping_app/models/cart_item.dart';
import 'package:shopping_app/models/order.dart';

class OrderController extends GetxController {
  var _orders = [].obs;

  List<Order> get orders {
    return [..._orders];
  }

  void addOrder(List<CartItem> cartProducts, double total) {
    _orders.insert(
        0,
        Order(
            id: DateTime.now().toString(),
            products: cartProducts,
            amount: total,
            dateTime: DateTime.now()));
  }
}

在上面的代码中。

我们声明了一个变量,该变量将保存所有订单的列表。我们通过使用点符号将其改为obs,使其成为可观察的。

每当订单发生变化时,所有使用它的小部件都会自动改变。

addOrder 方法中,我们不需要手动调用update 方法来更新被绑定的UI。Obx 智能地观察并进行相应的更新。

要使用Obx ,将控制器绑定到视图上,如下图所示,包住widget。

    body: Obx(() => ListView.builder(
            itemCount: orderController.orders.length,
            itemBuilder: (context, index) =>
                OrderItem(orderController.orders[index])),
      ),

总结

在本教程中,你已经学会了如何用Flutter构建一个购物应用程序,以及使用GetX进行状态管理、导航和渲染部件。

源代码可以在这个Github资源库中找到。


同行评审贡献者:。Wanja Mike

类似文章

[

How to Create a Reusable React Form component Hero Image

语言

如何创建一个可重复使用的React表单组件

阅读更多

](www.section.io/engineering…

Building a payroll system with next.js Hero Image

语言, Node.js

用Next.js构建一个薪资系统

阅读更多

](www.section.io/engineering…

Creating and Utilizing Decorators in Django example image

架构

在Django中创建和使用装饰器

阅读更多

](www.section.io/engineering…)