flutterまとめ SHOP_APP

#flutter/shop_app

# [Flutter & Dart - The Complete Guide [2020 Edition]](https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/)

https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/

*なんのやつか？*
Flutter & Dart - The Complete Guide [2020 Edition]
というUdemyのコースをやっていて便利そうだなと思ったのを備忘録的に載せていく
※コースをやっている途中からノートを取り始めたので、随時追加する
これ便利：https://qiita.com/matsukatsu/items/e289e30231fffb1e4502

#### Source Code
https://github.com/yuks0810/shop_app
- コメントとかで補足しているのでこっちを見てもわかるかも。
- providerを使って状態管理（これ、https://pub.dev/packages/provider）

#  **セクション8:State Management [SHOP APP]**
#flutter/shop_app #udemy

アプリの概要下書き
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381710/f8652b26-a1b6-9744-8162-11ef03ab199a.png)
[image:1A8638CF-59BB-4C85-A203-4CAEEA251F76-27537-00003A6A262AE47A/shop–app-sketch.png]

## main.dart【starting point】
[main.dart google Drive ダウンロード](https://drive.google.com/file/d/1RV9487lCdKmajIS1RpjQ1g_VRRMbU1OP/view?usp=sharing)

main.dart

```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](https://api.flutter.dev/flutter/foundation/foundation-library.html)
`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.

```dart
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](https://api.flutter.dev/flutter/widgets/GridView-class.html)
[GridView | Flutter Doc JP](https://flutter.ctrnost.com/basic/layout/gridview/)

lib/screens/products_overview_scree.dart

```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]

最後に小遺品タイトルや、いいね、カートボタンなどを追加する。

```dart
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
## テーマフォント、テーマカラーを決める

```dart
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ファイルを配置したら

<pubspec.yml>

```yaml
  # 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]
**タイルの角を丸みを帯びた形にする**

```dart
return ClipRRect(
      borderRadius: BorderRadius.circular(10),
          child: GridTile(
        child: Image.network(
          imageUrl,
          fit: BoxFit.cover,
        （省略）
          ),
        ),
      ),
    );
```
ClipRRectでGridTileを囲んで、borderRadius引数を渡してあげることで可能。
[ClipRRect class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/ClipRRect-class.html)

# 188. Adding Navigation to the App
## 画面遷移
２つの方法
1. Pushでルートを作成しないで画面を作る
2. routeを設定してそのルートに飛ばす(NamedRoutes)

### 1. Pushでルートを作成しないで画面を作る

画像をtapした時に次のページに進ませたいので、商品一覧を表示しているlib/widgets/product_item.dartのファイルにonTapアクションで、`Navigator.of(context).push(`を記述する。

```dart
child: GestureDetector(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (ctx) => ProductDetailScreen(title),
              ),
            );
          },
          child: Image.network(
            imageUrl,
            fit: BoxFit.cover,
          ),
        ),
```

<lib/screens/product_detail_screen.dart>

```dart
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](https://api.flutter.dev/flutter/widgets/ModalRoute-class.html)
[Navigate with named routes  - Flutter](https://flutter.dev/docs/cookbook/navigation/named-routes)

```dart
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などと同列の引数で

```dart
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などを渡せるようになる。

```dart
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に影響を与えるデータの一部であり、したがって状態とラベル付けされます。

最終的にユーザー インターフェイスは、データまたは状態の関数に過ぎません。

つまり、データが変われば、状態が変われば、ユーザーインターフェースが変わるということです。

状態については、いくつかの異なる種類の状態がありますが、これは難しい分離ではありません。

を学ぶことができます。

状態と言ったときの意味を理解するのに役立ちます。アプリケーション全体の状態と

ウィジェットやローカルステートがあります。名前が示すように、アプリ全体の状態はアプリケーション全体に影響を与えます。

または少なくともアプリの大きな塊

で、ここでの例は、ユーザの認証状態、ユーザが認証されているかどうかです。

ユーザが認証されていない場合は、通常は全く異なる

www.DeepL.com/Translator（無料版）で翻訳しました。

# 190. Understanding the “Provider” Package & Approach
![スクリーンショット 2020-08-31 1.44.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381710/7e18aee3-3a5f-152e-291e-527e03a2fb3f.png)



# 191. Working with Providers & Listeners
## providerを使ってデータのやり取りを行う
[provider | Flutter Package](https://pub.dev/packages/provider/install)
このページの通り、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で使えるようにする。

```dart
import ‘package:flutter/material.dart’;

class Products with ChangeNotifier {}
```

* `../models/product.dart`にProductsクラスとProduct LIstがあるのでそれをimportして使えるようにする。
_itemsをすることでこのファイル外ではこの変数（LIst）を参照できないようにする。
*  getの部分はゲッター

```dart 
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](https://qiita.com/sankentou/items/90ce0e04b6a170a316fe)
[Provider のススメ | Unselfish Meme](https://ja.unflf.com/tech/flutter/provider/)

```dart
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: を使う）

```dart
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](https://github.com/yuks0810/shop_app/commit/0d25e5baa5d29a43fdebaa8c4f925dae0da976d5)

`Provider.of(context)`という記述でlistenerを設置できる。これによって、listenerが設置されていないファイルではrebuildされない。
これの前に作っていた`lib/providers/products.dart`というファイルにlistenしたいステート（状態）を記述したクラスがあるのでそれをproviderとしてimoprtする。Listnerにはどのタイプのproviderを見るのかを指定するために上記に加えて、`Provider.of<Products>(context)`として、Products.dartがproviderであることを教えてあげる。

```dart
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変数

```dart
List<Product> get items {
    return [..._items] ;
  }
```

```dart
	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:**

```dart
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:

```dart
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](https://api.flutter.dev/flutter/dart-core/Iterable/firstWhere.html)
与えられた記述の 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フォルダ内に移動する。

```dart
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](https://github.com/yuks0810/shop_app/commit/b7437f3cb644aa677fa54cd573e755b35d017633#diff-a308a70c91b56cbd0a2244a297bf941a)
上記コード参照：
`with ChangeNotifier`を追加して、providerとして使うことができるようにする

[195. Using Nested Models & Providers · yuks0810/shop_app@b7437f3 · GitHub](https://github.com/yuks0810/shop_app/commit/b7437f3cb644aa677fa54cd573e755b35d017633#diff-d4de0ff21a32254efa148aaf0f0c8522)
コード参照：
:17
```dart
final product = Provider.of<Product>(context);
```
として、product変数にproduct.dart providerから変数に代入する

そうすることで、
```dart
id = product.id
itemUrl = product.itemUrl
```
などとaccessorで取得できる

# 196. Exploring Alternative Provider Syntaxes

## ChangeNotifierProvider( create: …) or ChangeNotifierProvider.value
main.dartファイルで、

```dart
return ChangeNotifierProvider(
          create: (ctx) => Products(),
			child: MaterialApp(
          title: 'MyShop',
```
と
```dart
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】](https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9#consumer)
[Consumer class - provider library - Dart API](https://pub.dev/documentation/provider/latest/provider/Consumer-class.html)

### Consumer vs. Provider.of<WidgetName>

Providerを使うときは何か変数に変化があるとwidget全体をrebuildしてしまうが、Consumerは一部のみをアップデートすることができる。


leading: Consumer<Product>(   # ここでConsumerを渡して良いねボタンだけ状態変化するので、良いねボタンだけをlistenしている。

```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) {
    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
メニューのセレクトバーを作る

```dart
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,)
            ],
          )
        ],
```
右上の３つのドットのアイコンを押すと、それぞれのメニューが現れる。

[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](https://api.flutter.dev/flutter/dart-core/Map/containsKey.html)
[putIfAbsent method - Map class - dart:core library - Dart API](https://api.dart.dev/stable/2.9.2/dart-core/Map/putIfAbsent.html)

- keyとしてidを使ってカート内商品を管理するので`List<CartItem>`ではなく、`Map<String, CartItem>`を利用する
	- mapはrubyで言うHash
- 

```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と同じ

  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](https://pub.dev/documentation/provider/latest/provider/MultiProvider-class.html)
- 1画面上に複数のproviderを使用するときい使う

```dart
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はアプリ全体を通して使用できる。

```dart
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](https://github.com/yuks0810/shop_app/commit/c63a0c27c56779684e0cb388ec774ef62501d7d4)

lib/providers/cart.dart

```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

```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 - material library - Dart API](https://api.flutter.dev/flutter/material/Chip-class.html)
- [Spacer class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/Spacer-class.html)


#### 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

```dart
import ‘../providers/cart.dart’;
import ‘../widgets/cart_item.dart’;
```
## importしたファイルの中に同じ命名のクラスがあるときの対処法：
cart.dart

```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

```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`オプションを使う

```dart
import ‘../providers/cart.dart’;
import ‘../widgets/cart_item.dart’;

ci.CartItem...
```
とすることで、どちらのファイルからCartItemを使っているかを判断させることができる。

### 2. `show`キーワードを使う

```dart
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](https://api.flutter.dev/flutter/widgets/Dismissible-class.html)

- DismissibleでListTileを囲むことで、そのListTileはスワイプで削除するアクションをつけることができる。

```dart
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]

```dart
// 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,
              ),

```