Flutter-application-state

提供:Dev Guides
移動先:案内検索

フラッター-アプリケーションの状態

アプリケーションの状態-scoped_model

Flutterは、scoped_modelパッケージを使用してアプリケーションの状態を管理する簡単な方法を提供します。 Flutterパッケージは、単に再利用可能な機能のライブラリです。 Flutterパッケージについては、今後の章で詳しく説明します。

scoped_modelは、アプリケーションで堅牢な状態管理を可能にする3つの主要なクラスを提供します。詳細については、ここで説明します-

モデル

モデルは、アプリケーションの状態をカプセル化します。 (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には、ビルダーと子という2つのプロパティがあります。 childは変更されず、ビルダーに渡されるUIパーツです。 ビルダーは、3つの引数を持つ関数を受け入れます-

  • content -ScopedModelDescendantはアプリケーションのコンテキストを渡します。
  • -モデルに基づいて変更されないUIの一部。
  • model -そのインスタンスの実際のモデル。
return ScopedModelDescendant<ProductModel>(
   builder: (context, child, cart) => { ... Actual UI ... },
   child: PartOfTheUI(),
);

StatefulWidgetの代わりにscoped_modelを使用するように前のサンプルを変更しましょう

  • Androidスタジオでproduct_scoped_model_appに新しいFlutterアプリケーションを作成します
  • デフォルトの起動コード(main.dart)をproduct_state_appコードに置き換えます
  • アセットフォルダーをproduct_nav_appからproduct_rest_appにコピーし、pubspec.yamlファイル内にアセットを追加します
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スタジオは、pubspec.yamlが更新されたことを警告します。

PubSpec YAML

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • デフォルトのスタートアップコード(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.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';
  • scoped_modelコンセプトをサポートするために、新しいウィジェットRatingBoxを変更しましょう。
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,
               ),
            ),
         ],
      );
   }
}

ここでは、StatefulWidgetではなくStatelessWidgetからRatingBoxを拡張しました。 また、製品モデルのupdateRatingメソッドを使用して評価を設定しました。

  • Product、ScopedModel、ScopedModelDescendantクラスで動作するようにProductBoxウィジェットを変更しましょう。
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); }
                                 )
                              ],
                           )
                        )
                     )
                  )
               ]
            ),
         )
      );
   }
}

ここでは、ScopedModelおよびScopedModelDecendant内にRatingBoxウィジェットをラップしました。

  • 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コンセプトを使用することを除いて、前の例と同様に機能します。