LoginSignup
1
2

More than 3 years have passed since last update.

flutterまとめ SHOP_APP

Last updated at Posted at 2020-08-30

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

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

セクション8:State Management [SHOP APP]

flutter/shop_app #udemy

アプリの概要下書き
image.png
[image:1A8638CF-59BB-4C85-A203-4CAEEA251F76-27537-00003A6A262AE47A/shop–app-sketch.png]

main.dart【starting point】

main.dart google Drive ダウンロード

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つの方法
1. Pushでルートを作成しないで画面を作る
2. 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に影響を与えるデータの一部であり、したがって状態とラベル付けされます。

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

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

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

を学ぶことができます。

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

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

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

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

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

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

190. Understanding the “Provider” Package & Approach

スクリーンショット 2020-08-31 1.44.23.png

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;
  }
}
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 StatefulWidgets 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
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ファイルで、

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】
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,
              ),

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2