宣言型ユーザーインターフェイスプログラミングと、エフェメラル状態とアプリケーション状態の違い について理解したので、アプリケーション状態を簡単に管理する方法を学習する準備が整いました。
パッケージを使用します
provider
。Flutterを初めて使用し、別のアプローチ(Redux、Rx、フックなど)を選択するやむを得ない理由がない場合は、これがおそらく開始するための最良のアプローチです。パッケージprovider
は習得が容易で、多くのコードを必要としません。彼はまた、他のすべてのアプローチに適用できる概念で動作します。
ただし、他のリアクティブフレームワークから状態を管理する経験がすでに豊富な場合は、オプションページにリストされている他のパッケージやチュートリアルを探すことができます。
例
例として、次の簡単なアプリケーションを考えてみましょう。
このアプリケーションには、カタログとショッピングカート(それぞれウィジェット
MyCatalog
で表されますMyCart
)の2つの別々の画面があります。この場合、これはショッピングアプリですが、単純なソーシャルネットワーキングアプリでも同じ構造を想像できます(カタログを「壁」に、カートを「お気に入り」に置き換えます)。
カタログ画面には、カスタマイズ可能なアプリケーションバー(
MyAppBar
)と複数のリストアイテムのスクロールビュー(MyListItems
)が含まれています。
ウィジェットのツリー形式のアプリケーションは次のとおりです。
したがって、少なくとも5つのサブクラスがあり
Widget
ます。それらの多くは、所有していない状態にアクセスする必要があります。たとえば、それぞれMyListItem
カートに自分を追加できるはずです。また、現在表示されているアイテムがカートに入っているかどうかを確認する必要がある場合もあります。
これは私たちの最初の質問に私たちをもたらします:私たちはバケットの現在の状態をどこに置くべきですか?
増加する条件
Flutterでは、状態をそれを使用するウィジェットの上に配置するのが理にかなっています。
何のために?Flutterのような宣言型フレームワークでは、ユーザーインターフェイスを変更する場合は、それを再構築する必要があります。ただ行って書くことはできません
MyCart.updateWith(somethingNew)
。つまり、ウィジェットのメソッドを呼び出して、ウィジェットを外部から強制的に変更することは困難です。そして、それを機能させることができたとしても、それを助けさせるのではなく、フレームワークと戦うことになります。
// :
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
上記のコードが機能
MyCart
するようになったとしても、ウィジェットで次のことに対処する必要があります。
// :
Widget build(BuildContext context) {
return SomeWidget(
// .
);
}
void updateWith(Item item) {
// - UI.
}
UIの現在の状態を考慮に入れて、新しいデータを適用する必要があります。ここで間違いを避けるのは難しいでしょう。
Flutterでは、コンテンツが変更されるたびに新しいウィジェットを作成します。
MyCart.updateWith(somethingNew)
(メソッド呼び出し)の代わりに、(MyCart(contents)
コンストラクター)を使用します。新しいウィジェットは親のビルドメソッドでしか作成できないため、変更contents
する場合は親MyCart
以上である必要があります。
//
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
これ
MyCart
で、任意のバージョンのユーザーインターフェイスを作成するためのコードパスが1つだけになりました。
//
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// , .
// ···
);
}
この例では、にある
contents
はずMyApp
です。変更されるたびに、MyCartが一番上に再構築されます(詳細は後で説明します)。このMyCart
ようにして、ライフサイクルについて心配する必要はありません。特定のコンテンツに対して何を表示するかを宣言するだけです。変更すると、古いウィジェットMyCart
は消え、完全に新しいウィジェットに置き換えられます。
これは、ウィジェットが不変であると言うときの意味です。それらは変更されません-それらは置き換えられます。
バケットの状態を配置する場所がわかったので、それにアクセスする方法を見てみましょう。
州のアクセス
ユーザーがカタログ内のアイテムの1つをクリックすると、カートに追加されます。しかし、カートが終わったので、
MyListItem
これをどのように行うのですか?
簡単なオプションは
MyListItem
、クリック時に呼び出すことができるコールバックを提供することです。 Dart関数はファーストクラスのオブジェクトであるため、任意の方法で渡すことができます。したがって、内部的にMyCatalog
は、次のように定義できます。
@override
Widget build(BuildContext context) {
return SomeWidget(
// , .
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
これは問題なく機能しますが、さまざまな場所から変更する必要があるアプリケーションの状態では、多くのコールバックを渡す必要があり、すぐに退屈になります。
幸い、Flutterには、ウィジェットがデータとサービスを子孫(つまり、子孫だけでなく、ダウンストリームウィジェット)に提供できるようにするメカニズムがあります。あなたはフラッター、から予想されるようにすべてがウィジェットです:、これらのメカニズムは、単に特別なウィジェットの種類があり
InheritedWidget
、InheritedNotifier
、InheritedModel
など。それらは私たちがやろうとしていることとわずかに一致していないため、ここでは説明しません。
代わりに、低レベルのウィジェットで機能するが使いやすいパッケージを使用します。それはと呼ばれ
provider
ます。
を使用すると、
provider
コールバックやを心配する必要はありませんInheritedWidgets
。ただし、次の3つの概念を理解する必要があります。
- ChangeNotifier
- ChangeNotifierProvider
- 消費者
ChangeNotifier
ChangeNotifier
Flutter SDKに含まれている単純なクラスで、リスナーに状態変更通知を提供します。言い換えれば、何かがあればChangeNotifier
、その変更をサブスクライブできます。 (これはObservableの形式です-用語に慣れていない人のために。)
ChangeNotifier
Inprovider
は、アプリケーションの状態をカプセル化する1つの方法です。非常に単純なアプリケーションの場合、1つでうまくいくことができますChangeNotifier
。より複雑なモデルでは、いくつかのモデルがあり、したがっていくつかのモデルがありますChangeNotifiers
。 (で使用ChangeNotifier
する必要はまったくありませんがprovider
、このクラスは簡単に操作できます。)
サンプルのショッピングアプリでは、でカートの状態を管理し
ChangeNotifier
ます。それを拡張する新しいクラスを作成します。次に例を示します。
class CartModel extends ChangeNotifier {
/// .
final List<Item> _items = [];
/// .
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// ( , 42 ).
int get totalPrice => _items.length * 42;
/// [item] . [removeAll] - .
void add(Item item) {
_items.add(item);
// , , .
notifyListeners();
}
/// .
void removeAll() {
_items.clear();
// , , .
notifyListeners();
}
}
に固有の唯一のコード
ChangeNotifier
は呼び出しnotifyListeners()
です。アプリケーションのUIに反映できるようにモデルが変更されるたびに、このメソッドを呼び出します。他のすべてCartModel
は、モデル自体とそのビジネスロジックです。Flutterの上位レベルのクラスの
ChangeNotifier
一部でflutter:foundation
あり、依存していません。テストは簡単です(ウィジェットテストを使用する必要はありません)。たとえば、簡単なユニットテストはCartModel
次のとおりです。
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
});
cart.add(Item('Dash'));
});
ChangeNotifierProvider
ChangeNotifierProvider
ChangeNotifier
その子にインスタンスを提供するウィジェットです。パッケージに入っていますprovider
。アクセスが必要なウィジェットの上
に配置する場所はすでにわかっ
ChangeNotifierProvider
ています。CartModel
上記の何かを意味する場合MyCart
とMyCatalog
。必要以上
に投稿したくない
ChangeNotifierProvider
(スコープを汚染したくないため)。しかし、私たちの場合、終わった唯一のウィジェットMyCart
とMyCatalog
-それMyApp
。
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
どうしても必要な場合を除い
CartModel.
ChangeNotifierProvider
て再構築しCartModel
ないほどスマートな
新しいインスタンスを作成するコンストラクターを定義していることに注意してください。また、インスタンスが不要になったときに、CartModelでdispose()を自動的に呼び出します。
複数のクラスを提供する場合は、次を使用できます
MultiProvider
。
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
消費者
今、それがされていることを
CartModel
宣言を通じて我々のアプリケーションのウィジェットに提供ChangeNotifierProvider
上部に、我々はそれを使用して起動することができます。
これはウィジェットを介して行われます
Consumer
。
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
アクセスするモデルのタイプを指定する必要があります。この場合、それが必要なので
CartModel
、と書きConsumer<
CartModel>
ます。 generic(<
CartModel>
)を指定しないと、パッケージprovider
は役に立ちません。provider
はタイプベースであり、タイプがないと、必要なものを理解できません。
ウィジェットに必要な引数
Consumer
はbuilder
。だけです。 Builderは、変更時に呼び出される関数ですChangeNotifier
。 (つまり、notifyListeners()
モデルを呼び出すと、関連するすべてのウィジェットのすべてのビルダーメソッドがConsumer
呼び出されます。)
コンストラクターは、3つの引数で呼び出されます。 1つ目は
context
、です。これは、すべてのビルドメソッドでも取得できます。
ビルダー関数の2番目の引数はインスタンスです
ChangeNotifier
..。これが私たちが最初から求めていたものです。モデルデータを使用して、ユーザーインターフェイスが特定のポイントをどのように見るかを決定できます。
3番目の引数は
child
、最適化に必要です。Consumer
モデルが変更されても変更されない大きなウィジェットサブツリーがある場合は、一度ビルドして、ビルダーを介して取得できます。
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// SomeExhibitedWidget, .
child,
Text("Total price: ${cart.totalPrice}"),
],
),
// .
child: SomeExpensiveWidget(),
);
コンシューマウィジェットをツリーのできるだけ深い位置に配置することをお勧めします。詳細がどこかで変更されたという理由だけで、ユーザーインターフェイスの大部分を再構築する必要はありません。
//
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
これの代わりに:
//
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Provider.of
ユーザーインターフェイスを変更するためにモデル内のデータが実際には必要ない場合もありますが、それでもアクセスする必要があります。たとえば、ボタンを
ClearCart
使用すると、ユーザーはカートからすべてを削除できます。カートの内容を表示する必要はありませんclear()
。メソッドを呼び出すだけです。
その
Consumer<
CartModel>
ために使用することもできますが、それは無駄です。フレームワークにウィジェットを再構築するように依頼しますが、ウィジェットを再構築する必要はありません。
このユースケースでは
Provider.of
、パラメータをにlisten
設定して使用できますfalse
。
Provider.of<CartModel>(context, listen: false).removeAll();
buildメソッドで上記の行を使用しても、が呼び出され
notifyListeners
たときにこのウィジェットは再構築されません。
すべてを一緒に入れて
この記事で説明されている例 を確認できます。もう少し簡単なものが必要な場合は、プロバイダーで作成された単純なCounterアプリケーションがどのように見えるかを確認してください。自分で
遊ぶ準備ができたら、
provider
最初にその依存関係を自分のものに追加することを忘れないでくださいpubspec.yaml
。
name: my_name
description: Blah blah blah.
# ...
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0
dev_dependencies:
# ...
今、あなたはできる
'package:provider/provider.dart'
; 構築を開始します...