Flutter - 應用狀態



應用程式狀態 - scoped_model

Flutter 提供了一種簡單的方法來使用 scoped_model 包管理應用程式的狀態。Flutter 包僅僅是可重用功能的庫。我們將在接下來的章節中詳細瞭解 Flutter 包。

scoped_model 提供了三個主要的類來支援應用程式中強大的狀態管理,這裡將詳細討論它們:

模型

模型封裝了應用程式的狀態。我們可以根據需要使用任意多個模型(透過繼承 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 是一個 Widget,它持有給定的模型,然後在請求時將其傳遞給所有後代 Widget。如果需要多個模型,則需要巢狀 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 是一個 Widget,它從上層 Widget 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

  • 將預設的啟動程式碼 (main.dart) 替換為我們的 product_state_app 程式碼

  • 將 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 將從網際網路獲取包併為應用程式正確配置它。

  • 將預設的啟動程式碼 (main.dart) 替換為我們的啟動程式碼。

import 'package:flutter/material.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; 
   
   @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';
  • 讓我們更改新的 Widget 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 Widget 以與 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 Widget 包含在 ScopedModel 和 ScopedModelDescendant 中。

  • 更改 MyHomePage Widget 以使用我們的 ProductBox Widget。

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.htm
廣告

© . All rights reserved.