Flutter - 应用程序状态

应用程序状态 - scoped_model

Flutter 提供了一种使用 scoped_model 包管理应用程序状态的简单方法。Flutter 包只是可重用功能库。我们将在接下来的章节中详细了解 Flutter 包。

scoped_model 提供了三个主要类,用于在应用程序中实现强大的状态管理,这里将详细讨论 −

模型

模型封装了应用程序的状态。我们可以根据需要使用尽可能多的模型(通过继承模型类)来维护应用程序状态。它有一个方法,notifyListeners,每当模型状态发生变化时都需要调用该方法。notifyListeners 将执行必要的操作来更新 UI。

class Product extends Model { 
   final String name; 
   final String description; 
   final int price;
   final String image; 
   int rating; 
   
   Product(this.name, this.description, this.price, this.image, this.rating); 
   factory Product.fromMap(Map<String, dynamic> json) { 
      return Product( 
         json['name'], 
         json['description'], 
         json['price'], 
         json['image'], 
         json['rating'], 
      ); 
   } 
   void updateRating(int myRating) { 
      rating = myRating; notifyListeners(); 
   }
}

ScopedModel

ScopedModel 是一个小部件,它保存给定的模型,然后根据需要将其传递给所有后代小部件。如果需要多个模型,则需要嵌套 ScopedModel。

  • 单一模型

ScopedModel<Product>(
   model: item, child: AnyWidget() 
)
  • 多种模型

ScopedModel<Product>( 
   model: item1, 
   child: ScopedModel<Product>( 
      model: item2, child: AnyWidget(),
   ),
)

ScopedModel.of 是一种用于获取 ScopedModel 底层模型的方法。当模型将要更改但不需要更改 UI 时,可以使用该方法。以下操作不会更改产品的 UI(评级)。

ScopedModel.of<Product>(context).updateRating(2);

ScopedModelDescendant

ScopedModelDescendant 是一个窗口小部件,它从上层窗口小部件 ScopedModel 获取模型,并在模型更改时构建其用户界面。

ScopedModelDescendant 有两个属性 - builder 和 child。child 是不会更改的 UI 部分,将传递给 builder。builder 接受一个带有三个参数的函数 −

  • content − ScopedModelDescendant 传递应用程序的上下文。

  • child − UI 的一部分,不会根据模型而改变。

  • model − 该实例的实际模型。

return ScopedModelDescendant<ProductModel>( 
   builder: (context, child, cart) => { ... Actual UI ... }, 
   child: PartOfTheUI(), 
);

让我们将之前的示例更改为使用 scoped_model 而不是 StatefulWidget

  • 在 Android Studio 中创建一个新的 Flutter 应用程序,product_scoped_model_app

  • 用我们的 product_state_app 代码替换默认启动代码 (main.dart)

  • 将 assets 文件夹从 product_nav_app 复制到 product_rest_app 并在 pubspec.yaml 文件中添加 assets

flutter: 

assets: 
   - assets/appimages/floppy.png 
   - assets/appimages/iphone.png 
   - assets/appimages/laptop.png 
   - assets/appimages/pendrive.png 
   - assets/appimages/pixel.png 
   - assets/appimages/tablet.png
  • 在pubspec.yaml文件中配置scoped_model包,如下所示:−

dependencies: scoped_model: ^1.0.1

这里,你应该使用最新版本的http包

  • Android Studio将提醒pubspec.yaml已更新。

PubSpec YAML
  • 单击获取依赖项选项。Android Studio将从Internet获取包并为应用程序正确配置它。

  • 用我们的启动代码替换默认启动代码(main.dart)。

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp()); 

class MyApp extends StatelessWidget { 
   // 此小部件是您的应用程序的根。
   @override 
   Widget build(BuildContext context) { 
      return MaterialApp( 
         title: 'Flutter Demo', 
         theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title: 'Product state demo home page'), 
      ); 
   }
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   
   @override 
   Widget build(BuildContext context) { 
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title), 
         ),
         body: Center(
            child: Text( 'Hello World', )
         ), 
      );
   }
}
  • 在 main.dart 文件中导入 scoped_model 包。

import 'package:scoped_model/scoped_model.dart';
  • 让我们创建一个 Product 类 Product.dart 来组织产品信息。

import 'package:scoped_model/scoped_model.dart'; 
class Product extends Model { 
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   int rating;

   Product(this.name, this.description, this.price, this.image, this.rating); 
   factory Product.fromMap(Map<String, dynamic> json) { 
      return Product( 
         json['name'], 
         json['description'], 
         json['price'], 
         json['image'], 
         json['rating'], 
      ); 
   } 
   void updateRating(int myRating) {
      rating = myRating; 
      notifyListeners(); 
   }
}

在这里,我们使用了notifyListeners来在评级发生变化时更改UI。

  • 让我们在Product类中编写一个方法getProducts来生成我们的虚拟产品记录。

static List<Product> getProducts() { 
   List<Product> items = <Product>[]; 
   
   items.add(
      Product(
         "Pixel",
         "Pixel is the most feature-full phone ever", 800,
         "pixel.png", 0
      )
   ); 
   items.add(
      Product(
         "Laptop", "Laptop is most productive development tool", 2000, 
         "laptop.png", 0
      )
   );
   items.add(
      Product(
         "Tablet", 
         "Tablet is the most useful device ever for meeting", 1500, 
         "tablet.png", 0
      )
   );
   items.add(
      Product(
         "Pendrive", 
         "Pendrive is useful storage medium", 
         100, "pendrive.png", 0
      )
   );
   items.add(
      Product(
         "Floppy Drive", 
         "Floppy drive is useful rescue storage medium", 20, 
         "floppy.png", 0
      )
   );
   return items; 
}
import product.dart in main.dart
import 'Product.dart';
  • 让我们改变我们的新小部件,RatingBox 以支持 scoped_model 概念。

class RatingBox extends StatelessWidget {
   RatingBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      double _size = 20; 
      print(item.rating); 
      return Row(
         mainAxisAlignment: MainAxisAlignment.end, 
         crossAxisAlignment: CrossAxisAlignment.end, 
         mainAxisSize: MainAxisSize.max, 
         children: <Widget>[ 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (
                     item.rating >= 1 
                     ? Icon( Icons.star, size: _size, )
                     : Icon( Icons.star_border, size: _size, )
                  ), color: Colors.red[500], 
                  onPressed: () => this.item.updateRating(1), 
                  iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (item.rating >= 2
                     ? Icon(
                        Icons.star,
                        size: _size,
                     ) : Icon(
                        Icons.star_border,
                        size: _size,
                     )
                  ), 
                  color: Colors.red[500],
                  onPressed: () => this.item.updateRating(2),
                  iconSize: _size,
               ),
            ),
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     item.rating >= 3? Icon(
                        Icons.star,
                        size: _size,
                     )
                     : Icon(
                        Icons.star_border,
                        size: _size,
                     )
                  ), 
                  color: Colors.red[500], 
                  onPressed: () => this.item.updateRating(3), 
                  iconSize: _size, 
               ), 
            ), 
         ], 
      ); 
   }
}

在这里,我们从 StatelessWidget 而不是 StatefulWidget 扩展了 RatingBox。此外,我们使用了 Product 模型的 updateRating 方法来设置评级。

  • 让我们修改我们的 ProductBox 小部件以与 Product、ScopedModel 和 ScopedModelDescendant 类一起使用。

class ProductBox extends StatelessWidget { 
   ProductBox({Key key, this.item}) : super(key: key);
   final Product item; 

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[  
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5),
                        child: ScopedModel<Product>(
                           model: this.item,
                           child: Column(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                              children: <Widget>[
                                 Text(this.item.name, 
                                    style: TextStyle(fontWeight: FontWeight.bold)), 
                                 Text(this.item.description), 
                                    Text("Price: " + 
                                 this.item.price.toString()), 
                                 ScopedModelDescendant<Product>(
                                    builder: (context, child, item) 
                                    { return RatingBox(item: item); }
                                 ) 
                              ], 
                           )
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   } 
}

在这里,我们将 RatingBox 小部件包装在 ScopedModel 和 ScopedModelDecendant 中。

  • 将 MyHomePage 小部件更改为使用我们的 ProductBox 小部件。

class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   final items = Product.getProducts(); 

   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")),
         body: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
               return ProductBox(item: items[index]);
            }, 
         )
      ); 
   }
}

这里我们使用 ListView.builder 来动态构建我们的产品列表。

  • 该应用程序的完整代码如下 −

Product.dart
import 'package:scoped_model/scoped_model.dart'; 
class Product extends Model {
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   int rating; 
   
   Product(this.name, this.description, this.price, this.image, this.rating); 
   factory Product.fromMap(Map<String, dynamic> json) {
      return Product(
         json['name'], 
         json['description'], 
         json['price'], 
         json['image'], 
         json['rating'], 
      );n 
   } void cn "Laptop is most productive development tool", 2000, "laptop.png", 0));
   items.add(
      Product(
         "Tablet"cnvn, 
         "Tablet is the most useful device ever for meeting", 1500, 
         "tablet.png", 0
      )
   ); 
   items.add(
      Product(
         "Pendrive", 
         "Pendrive is useful storage medium", 100, 
         "pendrive.png", 0
      )
   ); 
   items.add(
      Product( 
         "Floppy Drive", 
         "Floppy drive is useful rescue storage medium", 20, 
         "floppy.png", 0
      )
   )
   ; return items; 
}
main.dart
import 'package:flutter/material.dart'; 
import 'package:scoped_model/scoped_model.dart'; 
import 'Product.dart'; 

void main() => runApp(MyApp()); 
class MyApp extends StatelessWidget {
   // This widget is the root of your application

   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo', 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title: 'Product state demo home page'), 
      ); 
   } 
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   final items = Product.getProducts(); 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")), 
         body: ListView.builder(
            itemCount: items.length, 
            itemBuilder: (context, index) { 
               return ProductBox(item: items[index]); 
            }, 
         )
      ); 
   } 
}
class RatingBox extends StatelessWidget {
   RatingBox({Key key, this.item}) : super(key: key);
   final Product item;
   Widget build(BuildContext context) {
      double _size = 20; 
      print(item.rating); 
      return Row(
         mainAxisAlignment: MainAxisAlignment.end,
         crossAxisAlignment: CrossAxisAlignment.end,
         mainAxisSize: MainAxisSize.max,
         children: <Widget>[
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     item.rating >= 1? Icon( Icons.star, size: _size, )
                     : Icon( Icons.star_border, size: _size, )
                  ), 
                  color: Colors.red[500], 
                  onPressed: () => this.item.updateRating(1), 
                  iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (item.rating >= 2 
                     ? Icon( 
                        Icons.star, 
                        size: _size, 
                     ) 
                     : Icon( 
                        Icons.star_border, 
                        size: _size, 
                     )
                  ), 
                  color: Colors.red[500], 
                  onPressed: () => this.item.updateRating(2), 
                  iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     item.rating >= 3 ? 
                     Icon( Icons.star, size: _size, )
                     : Icon( Icons.star_border, size: _size, )
                  ), 
                  color: Colors.red[500], 
                  onPressed: () => this.item.updateRating(3), 
                  iconSize: _size, 
               ), 
            ), 
         ], 
      ); 
   } 
}
class ProductBox extends StatelessWidget { 
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2),
         height: 140,
         child: Card( 
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + this.item.image),
                  Expanded(
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: ScopedModel<Product>(
                           model: this.item, child: Column(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: <Widget>[
                                 Text(
                                    this.item.name, style: TextStyle(
                                       fontWeight: FontWeight.bold
                                    )
                                 ), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 ScopedModelDescendant<Product>(
                                    builder: (context, child, item) {
                                       return RatingBox(item: item); 
                                    }
                                 )
                              ],
                           )
                        )
                     )
                  )
               ]
            ), 
         )
      );
   }
}

最后,编译并运行应用程序以查看其结果。它的工作原理与前面的示例类似,只是应用程序使用了 scoped_model 概念。

flutter_state_management.html