#flutter/shop_app
Flutter & Dart - The Complete Guide [2020 Edition]
なんのやつか?
Flutter & Dart - The Complete Guide [2020 Edition]
というUdemyのコースをやっていて便利そうだなと思ったのを備忘録的に載せていく
※コースをやっている途中からノートを取り始めたので、随時追加する
これ便利:https://qiita.com/matsukatsu/items/e289e30231fffb1e4502
Source Code
- コメントとかで補足しているのでこっちを見てもわかるかも。
- providerを使って状態管理(これ、https://pub.dev/packages/provider)
セクション8:State Management [SHOP APP]
#flutter/shop_app #udemy
アプリの概要下書き
[image:1A8638CF-59BB-4C85-A203-4CAEEA251F76-27537-00003A6A262AE47A/shop–app-sketch.png]
main.dart【starting point】
main.dart
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘MyShop’,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘MyShop’),
),
body: Center(
child: Text(‘Let\’s build a shop!’),
),
);
}
}
185. Defining a Data Model
foundation library - Dart API
import ‘package:flutter/foundation.dart’;
のパッケージの説明文:
Core Flutter framework primitives.
The features defined in this library are the lowest-level utility classes and functions used by all the other layers of the Flutter framework.
import ‘package:flutter/foundation.dart’;
class Product {
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
bool isFavorite;
Product(
{@required this.id,
@required this.title,
@required this.description,
@required this.price,
@required this.imageUrl,
this.isFavorite});
}
- class Productでこれから使う商品のステータスを管理している。さらにそれをその下のコンストラクターで商品クラスがinitializeされる時に使用できるようにしている。
- isFavoriteは今後値が変わる可能性があるのでfinalをつけていない
- @requiredでclass作成時に必要な引数として定義づけている
186. Working on the "Products" Grid & Item
dummy product list:
https://drive.google.com/file/d/1RV9487lCdKmajIS1RpjQ1g_VRRMbU1OP/view?usp=sharing
final List<Product> loadedProducts = [];
- 商品一覧を表示する画面に、(Stateless widget)List型で上記商品を定義する。
[];
の中に商品のListを書く。 - Scaffoldで表示するページを定義する。
-
return Scaffold();
としてbuildメソッドの後に書く。
-
GridView classを使って商品一覧を作る
GridView class - widgets library - Dart API
GridView | Flutter Doc JP
lib/screens/products_overview_scree.dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MyShop'),
),
body: GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: loadedProducts.length,
itemBuilder: (ctx, i) => Container(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 3 / 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
),
);
}
}
GridViewの引数:
- itemBuilder: どのようにアイテムを表示するか。例えば、i > 3 のやつだけ表示するなどのif文を書くことで調整できる
- GridDelegate: どのように表示するか。例えば、paddingや、列数、行数を設定することができる。どれくらいのアスペクト比でボックスを表示するかなど
GridItemでこのように商品を並べることができる
source code:
[file:540569E1-8D77-4B91-A1A2-365552FE8C32-27537-00003FCF2A05352F/lib.zip]
[image:57CFF33B-44D8-4106-9733-2C8D7B80647A-27537-00003FBFBA752562/スクリーンショット 2020-08-23 14.34.38.png]
最後に小遺品タイトルや、いいね、カートボタンなどを追加する。
import ‘package:flutter/material.dart’;
class ProductItem extends StatelessWidget {
final String id;
final String title;
final String imageUrl;
ProductItem(this.id, this.title, this.imageUrl);
@override
Widget build(BuildContext context) {
return GridTile(
child: Image.network(
imageUrl,
fit: BoxFit.cover,
),
footer: GridTileBar(
backgroundColor: Colors.black54,
leading: IconButton(
icon: Icon(Icons.favorite),
onPressed: () {},
),
title: Text(
title,
textAlign: TextAlign.center,
),
trailing: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {},
),
),
);
}
}
- GridTileBarでGritTileにバーを追加することができる。
- leadingでタイトルの前に何かを設置できるようにする
- trailingでタイトルの後に表示
それぞれにIcons Widgetからカートとハートのボタンを追加して、onPressedアクションを追加する。現時点ではとくにonPressedアクションには何も指定していない。
187. Styling & Theming the App
テーマフォント、テーマカラーを決める
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: ‘Lato’
),
- main.dartでこのようにPrimarySwatchでメインカラー、accentColorでサブ、fontFamilyなどを決める
- また、pubspec.ymlに58行目あたりにfontを設定するところがあるので、fontを保存するディレクトリをアプリ直下に配置して、そこにフォントファイルを入れる。
fontfile:
[file:4FB632B7-0200-4FB9-B744-04CC903FF848-27537-000041F5C588CE33/fonts.zip]
fontファイルを配置したら
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
fonts:
- family: Lato
fonts:
- asset: assets/fonts/Lato-Regular.ttf
- asset: assets/fonts/Lato-Bold.ttf
weight: 700
- family: Anton
fonts:
- asset: assets/fonts/Anton-Regular.ttf
として、フォントのpathを通してフォントを使えるようにする。
タイルの角をラウンデッドにする
[image:1943918A-581B-4114-BEC5-E045D5F961CA-27537-0000425FB4364829/スクリーンショット 2020-08-23 15.22.46.png]
タイルの角を丸みを帯びた形にする
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
child: Image.network(
imageUrl,
fit: BoxFit.cover,
(省略)
),
),
),
);
ClipRRectでGridTileを囲んで、borderRadius引数を渡してあげることで可能。
ClipRRect class - widgets library - Dart API
188. Adding Navigation to the App
画面遷移
2つの方法
- Pushでルートを作成しないで画面を作る
- routeを設定してそのルートに飛ばす(NamedRoutes)
1. Pushでルートを作成しないで画面を作る
画像をtapした時に次のページに進ませたいので、商品一覧を表示しているlib/widgets/product_item.dartのファイルにonTapアクションで、Navigator.of(context).push(
を記述する。
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => ProductDetailScreen(title),
),
);
},
child: Image.network(
imageUrl,
fit: BoxFit.cover,
),
),
import 'package:flutter/material.dart';
class ProductDetailScreen extends StatelessWidget {
final String title;
ProductDetailScreen(this.title);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
);
}
}
- GestureDetectorのonTtapアクションでMaterialPageRouteに商品の【ProductDetailScreen】詳細画面(タップした時に進むページ)を渡す。
- 写真をタップ時に写真の情報を引数として渡すことで、titleなどに設定できる。
-
builder: (ctx) => ProductDetailScreen(title),
の部分で次の画面にtitleを渡しているが、これはGestureDetectorが使われているファイルにもfinal title
で定数が設定されているため可能である。このtitleはクラス作成時186. Working on the "Products" Grid & Itemのところで作られている。
この方法は簡単にページが作れて便利なのだが、アプリが大きくなった時に設定がどこでされているか、リファクタなどが難しくなってしまう。
2. routeを設定してそのルートに飛ばす(NamedRoutes
ModalRoute class - widgets library - Dart API
Navigate with named routes - Flutter
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: id,
);
前回までは Navigator.of(context).pushだった箇所を、Navigator.of(context).pushNamedに変更して、routeであるProductDetailScreen.routeName,
と、titleなどの引数をarguments: id,
で渡す。
NamedRoutesは同ファイル上でstatic const routeName = ‘/product-datail’;
のようにしてrouteが名付けられている。
これを引数として取ってくる時などに
これを、main.dartでhomeやappBarなどと同列の引数で
return MaterialApp(
title: 'MyShop',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: 'Lato'),
home: ProductsOverviewScreen(),
routes: {
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
});
のようにしてroutesを設定する。
static const routeName = ‘/product-datail’;
や、final productId = ModalRoute.of(context).settings.arguments as String;
は商品の一覧ページから写真をタップして飛ばすので、商品一覧ページで使う必要がある。そのため、商品の一覧ページである。lib/screens/product_detail_screen.dartファイルに設定する。その結果onTapで次のページに飛ばすときの引数としてidなどを渡せるようになる。
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: id,
);
189. Why State Management? And what is “State” and “State Management”?
[file:CEF03178-1DAE-4179-8D11-1430FAE1648E-27537-0000482B99BA7A24/state-and-state-management.pdf]
どのようにアプリ内の変数を管理するか
widget treeが大きくなるにつれて色々な場所の子widget内でmain.dartに定義されている変数が必要になる。
その際に、全ての変数をmain.dart内でのみ管理していると、ごく一部の子widgetがrebuildされた時に全てのファイルをrebuildし直すのでパフォーマンスが非常に悪くなる。それを防ぐために、データとフロントエンドUIに分けて、データの変更のみを適用するようにする。
もう一度はっきりさせたいのですが、何が問題なのでしょうか?
問題は、大規模なアプリでは、たくさんのウィジェットがあり、もちろん、潜在的にはもっと多くの
スライドの上には私よりもウィジェットがあります。
子ウィジェットがデータを必要とする場合、通常は一番上のウィジェットでデータを管理しなければなりません。
アプリの異なる場所でデータを必要とするため、ここではMyAppウィジェットを使用します。
共通分母を使用することで、ウィジェットのコンストラクタにデータを渡すことができます。
一般的な値の配分方法
今すぐに。他のウィジェットへの引数として渡しているので、非常に長い連鎖が発生します。
引数は、それを転送してください、ここでは引数は、我々はウィジェットを介してデータを渡すところで、それを転送してください
子供ウィジェットが気にするからといって気にしない、修正したい問題でもある
コンストラクタにデータを渡すのが面倒で困難な場合があるからです。
開発者としての余計な手間がかかることに加えて、アプリケーションに影響を与えることもあります。
なぜなら、すべてをmain.dartファイルで管理し、メインウィジェットや
いくつかのルートウィジェットを使用すると、何かデータが変更されるたびにウィジェットが再構築されます。
ツリーのどこかにある小さな子ウィジェットが1つだけ本当にある場合でも、ウィジェットツリー全体が再構築されます。
は更新されたデータに興味を持っています。ナビゲーションの例を見てみましょう。
食事アプリにナビゲーションを追加しました 私たちはすべての食事データを管理しています。
ユーザーのお気に入りやユーザーが設定したフィルターなど、すべてのデータは
main.dart ファイルで管理されています。
ここでは、基本的にすべてのウィジェットを再構築します。
これまでルーティングしてきたすべてのウィジェットをスクリーンとしてロードし、何かが変わるたびにウィジェットを再構築していきます。
ここをクリックしてください。
食事をお気に入りに追加したり、フィルターを変更したりすると、アプリ全体が再構築され、さらに
の部分には影響がありません。
例えば食事をお気に入りに追加しても、フィルターには影響しません。
の場合、アプリも再構築されます。
そのため、すべてのものを常に再構築することは、もちろんパフォーマンスのために素晴らしいことではありません。
変更されたデータに本当に依存しているウィジェットでのみビルドメソッドを実行します。
また、必要なデータを変更したからといって、アプリ全体を再構築したくはありません。
をアプリの一部で使用することができます。
だから、それは一つの厄介な部分です。
もう一つ厄介なのは下の方を見てください
すべてのデータを引数として他のウィジェットに渡しているのですが、ここではお気に入りの食事を
TabsScreenです。TabsScreen自体にはお気に入りの食事は必要ありません。
ここでは、タブスクリーンでお気に入りの食事をお気に入りに転送するだけです。
の画面を見て、私もまさにその通りだと思いました。性能のことなどの他にも
はウィジェットを介してデータを転送しているので、その間に新しいウィジェットを追加するたびに
また、そこにあるデータを受け入れて転送しなければなりません。
何かを変更する場合は、受け取る引数を変更しなければなりません。
これを管理するのは本当に面倒で 効率が悪いというか やりたいことができません
したがって、私たちの状態を管理するためのより良い方法が必要です。
そのために、まず、状態と状態管理とは何を意味するのかを明確にしておきましょう。Flutter アプリケーションでは
と、一般的なフロントエンドアプリケーションでは、常にデータとユーザーインターフェースの管理について
はそのデータを反映しています。
データが変更されると、通常はユーザーインターフェース上で何かが変更されます。
本当に最終的にはあなたのデータを反映しているだけですが、ここではデータというのは広義の言葉です。
データは何でも構いません。
アプリに表示される製品であってもよい。
のようなことができます。
またはこのユーザーはログインしていますか?
で、このデータをステートと呼んでいます。状態とは単純にユーザーインターフェイスに影響を与えるデータであり
また、時間の経過とともに変化する可能性があります。
のように、現在新しいデータをロードしていますか?
もしそれが変更された場合は、ユーザーインターフェイス上にロードスピナーを表示したいでしょう。その情報は
はまた、アプリやUIに影響を与えるデータの一部であり、したがって状態とラベル付けされます。
最終的にユーザー インターフェイスは、データまたは状態の関数に過ぎません。
つまり、データが変われば、状態が変われば、ユーザーインターフェースが変わるということです。
状態については、いくつかの異なる種類の状態がありますが、これは難しい分離ではありません。
を学ぶことができます。
状態と言ったときの意味を理解するのに役立ちます。アプリケーション全体の状態と
ウィジェットやローカルステートがあります。名前が示すように、アプリ全体の状態はアプリケーション全体に影響を与えます。
または少なくともアプリの大きな塊
で、ここでの例は、ユーザの認証状態、ユーザが認証されているかどうかです。
ユーザが認証されていない場合は、通常は全く異なる
190. Understanding the “Provider” Package & Approach
191. Working with Providers & Listeners
providerを使ってデータのやり取りを行う
provider | Flutter Package
このページの通り、pubspec.ymlに書いて、installを行う。
Use this package as a library
1. Depend on it
Add this to your package’s pubspec.yaml file:
dependencies:
provider: ^4.3.2+1
2. Install it
You can install packages from the command line:
with Flutter:
$ flutter pub get
Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.
3. Import it
Now in your Dart code, you can use:
import ‘package:provider/provider.dart’;
providerの使い方
データを扱うものなのでmodelに記載する
lib/providers/products.dart
- lib直下にprividersというフォルダとデータに関わる命名でdartファイルを作成する
- withを使って、ChangeNotifierをMin-inで使えるようにする。
import ‘package:flutter/material.dart’;
class Products with ChangeNotifier {}
-
../models/product.dart
にProductsクラスとProduct LIstがあるのでそれをimportして使えるようにする。
_itemsをすることでこのファイル外ではこの変数(LIst)を参照できないようにする。 - getの部分はゲッター
import ‘package:flutter/material.dart’;
import ‘../models/product.dart’;
class Products with ChangeNotifier {
List<Product> _items = [];
List<Product> get items {
return _items;
}
}
- Products classの下にaddProductメソッドを追加する。
- notifyListenersクラスによって、どのデータが変わったかなどを管理する。
Flutterでズンドコ (ChangeNotifier) - Qiita
Provider のススメ | Unselfish Meme
void addProduct() {
_items.add(value);
notifyListeners();
}
- providerを使うにあたって、main.dartに
import 'package:provider/provider.dart’;
を記述する。 - main.dartファイルのMaterialAppをChangeNotifierProviderで囲む
- listeningされている子widget(data?)だけrebuildされる
- ChangeNotifierProviderに
builder: (ctx) => Products(),
のようにbuilderを引数として設定する。(providerのver.が4.0.0以上の場合はbuilder:の代わりにcreate: を使う)
import ‘package:provider/provider.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider( ####ここ
builder: (ctx) => Products(),
child: MaterialApp(
title: ‘MyShop’,
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: ‘Lato’),
home: ProductsOverviewScreen(),
routes: {
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
}),
);
}
}
Listnerを設置する
191. Working with Providers & Listeners · yuks0810/shop_app@0d25e5b · GitHub
Provider.of(context)
という記述でlistenerを設置できる。これによって、listenerが設置されていないファイルではrebuildされない。
これの前に作っていたlib/providers/products.dart
というファイルにlistenしたいステート(状態)を記述したクラスがあるのでそれをproviderとしてimoprtする。Listnerにはどのタイプのproviderを見るのかを指定するために上記に加えて、Provider.of<Products>(context)
として、Products.dartがproviderであることを教えてあげる。
import ‘../providers/products.dart’;
lib/providers/products.dart
では、class Products with ChangeNotifier {
のように、ChangeNotifierが継承されているので、これによってこのproviderへのアクセスが可能になる。なので、
lib/widgets/products_grid.dart
で記載されていた、lib/widgets/products_grid.dart
は不必要となり、代わりに
Provider.of<Products>(context);
でlistenしているlib/providers/products.dart
ファイルのList変数が使えるようになる。
それをproductsData
という変数に格納して、productsData.items
でListのitemsが取得できる。それをproductsという変数に格納することで商品一覧のListの状態を取得可能になる。
providerのlistenされているList変数
List<Product> get items {
return [..._items] ;
}
final productsData = Provider.of<Products>(context);
final products = productsData.items;
193. Providing non-Objects
Typically, when working with the Provider package, you provide objects based on your own custom classes.
This makes sense because you can implement the ChangeNotifier
mix-in in your classes to to then trigger notifyListeners()
whenever you want to update all places in your app that listen to your data.
But you’re not limited to providing objects - you can provide ANY kind of value (lists, numbers, strings, objects without ChangeNotifier
mixing, …).
Example:
Provider<String>(builder: (ctx) => ‘Hi, I am a text!’, child: …);
Of course, if you’re using Provider Package v4 or greater, it would be create: …
instead of builder: …
You might wonder, how this text can change though - it’s a constant text after all. It certainly doesn’t implement the ChangeNotifier
mixin (the String
class, which is built-into Dart, indeed doesn’t - just like numbers, booleans etc.).
It’s important to note, that the above snippet uses Provider
, NOT ChangeNotifierProvider
. The latter indeed only works with objects based on classes that use the ChangeNotifier mixin. And this is the most common use-case, because you typically want to be your global data changeable (and have the app UI react to that).
But in case you just want to provide some global (constant) value which you can then conveniently use like this:
print(Provider.of<String>(context)); // prints ‘Hi, I am a text!’; does never update!
you can do that.
194. Listening in Different Places & Ways
Providerの使い方を説明している章
providerは変数管理に使用するパッケージでproviderファイルに変数を切り出しでき、listenerを設定することでその変数に変更があった場合には変数を変更するようにする。
firstWhere method - Iterable class - dart:core library - Dart API
与えられた記述の test を満たす最初の要素を返します。
要素を繰り返し処理し、testを満たす最初の要素を返します。
- それぞれの要素全てをテストして条件に当てはまるデータを返す
StatefulWidgetについて
アプリ全体をStatefulWidgetで構成することは可能だが、アプリの規模や複雑さが増してくるとproviderを使用する設計にする必要がある。
それは、StatefulWidgetを多用すると複数の場所から参照したいstateを参照できなくなったり、一度変数の値が変化するとその度にStatefulWidgetがアプリをrebuildするため非効率的だから。
Why might StatefulWidget
s alone not do the trick (in bigger/ more complex Flutter apps)?
Q. なぜ StatefulWidgets だけでは(大規模で複雑な Flutter アプリでは)うまくいかないのでしょうか?
A. 複数のウィジェットが同じ状態に依存している可能性があります - ウィジェットのコンストラクタで共有することは面倒で非効率的です (例: build() セルが多すぎます)。
195. Using Nested Models & Providers
Productのいいね機能を実装するためにProduct クラスにもChangeNotifierをつけて変更をチェックするようにする。
また、product.dart(Productクラスがあるファイル)をprovidersフォルダ内に移動する。
import ‘package:flutter/foundation.dart’;
class Product with ChangeNotifier{
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
bool isFavorite;
Product(
{@required this.id,
@required this.title,
@required this.description,
@required this.price,
@required this.imageUrl,
this.isFavorite});
}
with ChangeNotifier
を追加するとことでproviderとして監視対象に置くことができる
Listenerを設定する
商品一覧の画面であるproduct_item.dart
ファイルにリスナーを追加してステータの変化を監視するようにする。
こうすることで、この画面上で何かの変数に変化があった場合にproviderの情報を変更することができる。
195. Using Nested Models & Providers · yuks0810/shop_app@b7437f3 · GitHub
上記コード参照:
with ChangeNotifier
を追加して、providerとして使うことができるようにする
195. Using Nested Models & Providers · yuks0810/shop_app@b7437f3 · GitHub
コード参照:
:17
final product = Provider.of<Product>(context);
として、product変数にproduct.dart providerから変数に代入する
そうすることで、
id = product.id
itemUrl = product.itemUrl
などとaccessorで取得できる
196. Exploring Alternative Provider Syntaxes
ChangeNotifierProvider( create: …) or ChangeNotifierProvider.value
main.dartファイルで、
return ChangeNotifierProvider(
create: (ctx) => Products(),
child: MaterialApp(
title: 'MyShop',
と
return ChangeNotifierProvider.value(
child: MaterialApp(
title: ‘MyShop’,
の2種類の書き方があるが、どちらが正しいのか?
どちらでもアプリは動くが、すでに決められたインスタンスを作る場合は.vlaueを使う。そして、新しくインスタンスを作る場合はcreateを使うのが慣習。
これに従って書くことで、新規にインスタンスを作成する場合に.valueを行っていると効率性が下がるので新規にインスタンスを作る場合はcreateを使った方がアプリ全体の効率が高まる。
197. Using “Consumer” instead of “Provider.of”
Consumer widget
import ‘package:provider/provider.dart’;
providerパッケージによって提供されている。:
Provider.of() ではプロバイダの子孫にあたる Widget の BuildContext が必要ですが、Consumer() はそれが得られない場合にも使えて便利です。
Flutter package:provider の各プロバイダの詳細【Consumer】
Consumer class - provider library - Dart API
Consumer vs. Provider.of
Providerを使うときは何か変数に変化があるとwidget全体をrebuildしてしまうが、Consumerは一部のみをアップデートすることができる。
leading: Consumer( # ここでConsumerを渡して良いねボタンだけ状態変化するので、良いねボタンだけをlistenしている。
class ProductItem extends StatelessWidget {
// final String id;
// final String title;
// final String imageUrl;
// ProductItem(this.id, this.title, this.imageUrl);
@override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context, listen: false);
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
child: GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: product.id,
);
},
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
footer: GridTileBar(
backgroundColor: Colors.black87,
leading: Consumer<Product>( # ここでConsumerを渡して良いねボタンだけ状態変化するので、良いねボタンだけをlistenしている。
builder: (ctx, product, child) => IconButton(
icon: Icon(
product.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: () {
product.toggleFavoriteStatus();
},
color: Theme.of(context).accentColor,
),
),
title: Text(
product.title,
textAlign: TextAlign.center,
),
trailing: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {},
color: Theme.of(context).accentColor,
),
),
),
);
}
}
198. Local State vs App-wide State
メニューのセレクトバーを作る
appBar: AppBar(
title: Text(‘MyShop’),
actions: <Widget>[
PopupMenuButton(
onSelected: (int selectedValue) {
print(selectedValue);
},
icon: Icon(
Icons.more_vert,
),
itemBuilder: (_) => [
PopupMenuItem(child: Text(‘Only Favorites’), value: 0,),
PopupMenuItem(child: Text(‘Show All’), value: 1,)
],
)
],
右上の3つのドットのアイコンを押すと、それぞれのメニューが現れる。
[image:F6B3D56C-5EC9-4F80-A2CD-092ECE30412F-68058-00012F0E07F764C5/スクリーンショット 2020-08-30 13.37.51.png]
[image:CAFCD7E3-3645-4E7D-91F1-1B7DE918FEDC-68058-00012F14877B9DA8/スクリーンショット 2020-08-30 13.38.18.png]
199. Adding Shopping Cart Data
カートクラスを作成
containsKey method - Map class - dart:core library - Dart API
putIfAbsent method - Map class - dart:core library - Dart API
- keyとしてidを使ってカート内商品を管理するので
List<CartItem>
ではなく、Map<String, CartItem>
を利用する- mapはrubyで言うHash
import ‘package:flutter/foundation.dart’;
class CartItem {
final String id;
final String title;
final int quantity;
final double price;
CartItem({
@required this.id,
@required this.title,
@required this.quantity,
@required this.price,
});
}
class Cart with ChangeNotifier {
Map<String, CartItem> _items; // Hashと同じ
Map<String, CartItem> get items {
return {…_items};
}
void addItem(
String productId,
double price,
String title,
) {
if (_items.containsKey(productId)) {
// cart内に商品があるかを判断 containsKeyでproductIdが含まれているかどうかをチェックできる
_items.update(
productId,
(existingCartItem) => CartItem(
id: existingCartItem.id,
title: existingCartItem.title,
price: existingCartItem.price,
quantity: existingCartItem.quantity + 1,
));
} else {
_items.putIfAbsent(
productId,
() => CartItem(
id: DateTime.now().toString(),
title: title,
price: price,
quantity: 1,
));
}
}
}
200. Working with Multiple Providers
MultiProvider class - provider library - Dart API
- 1画面上に複数のproviderを使用するときい使う
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/products_overview_scree.dart';
import './screens/product_detail_screen.dart';
import './providers/products.dart';
import './providers/cart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Products(),
),
ChangeNotifierProvider(
create: (ctx) => Cart(),
),
],
child: MaterialApp(
title: 'MyShop',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: 'Lato'),
home: ProductsOverviewScreen(),
routes: {
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
}),
);
}
}
下記の記述で、ProductsとCart providerをその子要素、child:
に適用しているので、child要素はそれぞれのproviderを使用できる。
main.dartて記述しているため、結果的にそれぞれのproviderはアプリ全体を通して使用できる。
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Products(),
),
ChangeNotifierProvider.value(
value: Cart(),
),
],
201. Connecting the Cart Provider
github参考:
201. Connecting the Cart Provider · yuks0810/shop_app@c63a0c2 · GitHub
lib/providers/cart.dart
import ‘package:flutter/foundation.dart’;
class CartItem {
final String id;
final String title;
final int quantity;
final double price;
CartItem({
@required this.id,
@required this.title,
@required this.quantity,
@required this.price,
});
}
class Cart with ChangeNotifier {
Map<String, CartItem> _items = {}; // Hashと同じ // _itemsをカートがからの場合でもinitializeできるようにす = {}をつける。
Map<String, CartItem> get items {
return {…_items};
}
int get itemCount {
return _items.length;
}
void addItem(
String productId,
double price,
String title,
) {
if (_items.containsKey(productId)) {
// cart内に商品があるかを判断 containsKeyでproductIdが含まれているかどうかをチェックできる
_items.update(
productId,
(existingCartItem) => CartItem(
id: existingCartItem.id,
title: existingCartItem.title,
price: existingCartItem.price,
quantity: existingCartItem.quantity + 1,
));
} else {
_items.putIfAbsent(
productId,
() => CartItem(
id: DateTime.now().toString(),
title: title,
price: price,
quantity: 1,
));
}
notifyListeners();
}
}
lib/widgets/badge.dart
import 'package:flutter/material.dart';
class Badge extends StatelessWidget {
const Badge({
Key key,
@required this.child,
@required this.value,
this.color,
}) : super(key: key);
final Widget child;
final String value;
final Color color;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
child,
Positioned(
right: 8,
top: 8,
child: Container(
padding: EdgeInsets.all(2.0),
// color: Theme.of(context).accentColor,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: color != null ? color : Theme.of(context).accentColor,
),
constraints: BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
),
),
),
)
],
);
}
}
202. Working on the Shopping Cart & Displaying a Total
カートを作っていく
New Classes:
Chip class
この紫の部分
[image:60909463-8A04-4161-9BF3-D255D426EFAE-35620-000152633023954C/スクリーンショット 2020-08-31 0.25.20.png]
Spacer class
MainAxisAlignmentでspaceBetweenやevenのようにそれぞれのアイテムの幅を調整できるが、カスタマイズ的にやりたい場合はSpacerを使う。
203. Displaying a List of Cart Items
import ‘../providers/cart.dart’;
import ‘../widgets/cart_item.dart’;
importしたファイルの中に同じ命名のクラスがあるときの対処法:
cart.dart
class CartItem {
final String id;
final String title;
final int quantity;
final double price;
CartItem({
@required this.id,
@required this.title,
@required this.quantity,
@required this.price,
});
}
cart_item.dart
class CartItem extends StatelessWidget {
final String id;
final double price;
final int quantity;
final String title;
CartItem(
this.id,
this.price,
this.quantity,
this.title,
);
と、二つ別々のファイルの中に同じ名前のクラスがあるときに、一つのファイルで上記二つをimportしてしまうと名前が競合するのでその対処法
1. as
オプションを使う
import ‘../providers/cart.dart’;
import ‘../widgets/cart_item.dart’;
ci.CartItem...
とすることで、どちらのファイルからCartItemを使っているかを判断させることができる。
2. show
キーワードを使う
import ‘../providers/cart.dart’ show Cart;
import ‘../widgets/cart_item.dart’;
show Cartを使うことで、cart.dartからはCartクラスのみを使うと言うことを明示することができる。
cart.dartからはCartItemは使っていないので、cart_item.dartからas
などの特別な宣言なしにCartItemを使うことができる.
204. Making Cart Items Dismissible
カート内のアイテムをスワイプで削除する
Dismissible classを使う
Dismissible class - widgets library - Dart API
- DismissibleでListTileを囲むことで、そのListTileはスワイプで削除するアクションをつけることができる。
Dismissible(
key: ValueKey(id),
background: Container(
color: Theme.of(context).errorColor, // スワイプしている最中にbackgroundに表示される色
child: Icon(
Icons.delete,
color: Colors.white,
size: 40,
),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
),
- keyは必ず必要
-
color: Theme.of(context).errorColor,
でスワイプ中のbackground色を決めることができる。 - childのIconはゴミがこマークをすワイプ時に見せるように設定
- alignmentでどの位置に表示するかを決めている。
-
direction: DismissDirection.endToStart,
オプションをDismissible classに渡すことでどの方向からスワイプして消すことができるかを指定できる。 -
onDismissed: (direction) {}
オプションでスワイプしたときにどんなアクションをするかを指定できる。
[image:A7C83A8A-6B32-4AC7-A1EC-0C23E8D5A97F-35620-0001552ACFE8D02C/99473536-5ECD-4984-9DBD-B3A3A4DBC154.png]
// in cart_item.dart
onDismissed: (direction) {
Provider.of<Cart>(context, listen: false).removeItem(productId);
},
// in cart.dart
// removeItemメソッド追加
// class CartItemniどの商品かを判定するときに使用するfinal String productId;を追加
class CartItem extends StatelessWidget {
final String id;
final String productId;
final double price;
final int quantity;
final String title;
...
void removeItem(String productId) {
_items.remove(productId);
notifyListeners();
}
// in cart_screen.dart
// itemBuilderにcart.items.keys.toList()[i],を追加
itemCount: cart.items.length,
itemBuilder: (ctx, i) => CartItem(
cart.items.values.toList()[i].id,
cart.items.keys.toList()[i],
cart.items.values.toList()[i].price,
cart.items.values.toList()[i].quantity,
cart.items.values.toList()[i].title,
),