Flutter-application-state
フラッター-アプリケーションの状態
アプリケーションの状態-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が更新されたことを警告します。
- [依存関係の取得]オプションをクリックします。 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コンセプトを使用することを除いて、前の例と同様に機能します。