初心者がFlutterでアプリ開発するための知見まとめ

## はじめに

日頃、Flutter情報を纏めていたNotionというツールに情報突っ込みすぎて、「更に使いたいなら有料プランへどうぞ」的なのが出てきたのでこちらに情報を整理しつつ退避して、Notionを軽くしようと思います。

ちまちま纏めていきますので、末永くお付き合いください。

また、初心者ゆえの情報の誤り等がある可能性があることは念頭に置いて御覧ください。（誤りがありましたらご指摘いただければよろこんで修正致します）



## Flutterのはじめかた

環境構築的なことは死ぬほど情報があるのでここでは書きません。
もし、あなたがAndroidアプリを作ったことがあるのであれば、以下の記事をご覧いただければ大体の感覚は掴めるのではないでしょうか。

Android開発者のためのFlutter説明文がためになったので全訳&要約 - Qiita https://qiita.com/coka__01/items/db6d482af1a1994aaa73

と、丸投げしましたが、まずは以下のテンプレで初めてみては如何でしょうか。

main()がアプリのエントリポイントになりますので、必須の関数になります。

```dart
import 'package:flutter/material.dart';

void main() {
 runApp(App());
}

class App extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     debugShowCheckedModeBanner: false,  // アプリ右上に表示されるDebugの帯を付けるか否か
     home: Scaffold(
       appBar: AppBar(title: Text("サンプル")) // ヘッダー
       body: Center(child: Text("サンプルアプリ")),
     ),
   );
 }
}
```

これでビルドしてみてください。味気ないアプリが動くと思います^^;

Flutterの特徴として、Hot Reloadというものがあり、一度、ビルドして動作させたあとはコードを弄ってSaveするたびにエミュレータ／実機に変更が反映されます。（ただし、ネストが深くなったりするとHot Reloadが動作しない場合がありますのでご注意ください）

以下、わかり易い説明がありましたので[こちら](https://apps-gcp.com/introduction-of-flutter-about-header-and-footer/)から抜粋します。

> App Widget はMaterialデザインを作るための [MaterialApp Widget](https://api.flutter.dev/flutter/material/MaterialApp-class.html) をもっており、MaterialApp Widget は モバイルアプリの基本的な構造を持つ [Scaffold Widget](https://api.flutter.dev/flutter/material/Scaffold-class.html) からなります。Scaffold Widget の body プロパティは アプリ( Scaffold Widget )のメインコンテンツの Widget を 表しています。今回はこのbodyプロパティは 子(childプロパティ)の Widget を真ん中に配置する位置指定の [Center Widget](https://api.flutter.dev/flutter/widgets/Center-class.html) を持っており、[Center Widget](https://api.flutter.dev/flutter/widgets/Center-class.html) は 文字列を表示する [Text Widget](https://flutter.dev/docs/development/ui/widgets/text) を持っているという入れ子構造になっています。言葉で説明するとややこしいですが、図にするとイメージしやすいです。

![](https://lh3.googleusercontent.com/jsWI0NbqkAa8c38xBfB_qFRpU08hglIe19VaYLBaLCKmlYTMwUFzxPTTfjZcJk8KlYmpjniwuNPZQ8sGNWMeLs5YmEpRZk4=s1024)

> この後、ヘッダーとフッターをつくっていきますが、**Widget を 入れ子にして UI 作っていく**ということを念頭に置いておくと後の流れがわかりやすいと思います。
> 　ちなみに、記事の途中ですが、どんな Widget があるのか詳細を知りたい方は個人的に下記のページをおすすめします。私もこの記事の作成にあたり、どの Widget を使うかで参考にさせていただきました。
>
> [Flutter:Widget一覧](https://qiita.com/matsukatsu/items/e289e30231fffb1e4502)



## Flutterプロジェクトにおける各ファイル／フォルダの意味

Flutterの環境構築が終わってさてFlutterアプリの雛形を作ると出てくるフォルダ／ファイル類。おおざっぱに以下に整理します。

### pubspec.lock

パッケージ管理ファイルの**pubspec.yaml**では管理しきれない依存関係を管理します。

コードを見る必要はありませんが、触る必要もありません。

下手に触ってしまうと、アプリを動かすためのパッケージをあやまって消した時に同じパッケージ構成を再現できないことがあります。

**pubspec.yaml**は人が見て編集する用、**pubspec.lock**は**pubspec.yaml**よりもさらに詳細な依存関係が管理されています。（そんなの入れたっけ的なやつが大半です）

### .packages

**.packages**ファイルには、アプリケーションで使用される依存関係のリストが含まれています。

Flutterはどこにパッケージを置いているのか気になっていましたが、隠しファイルにキャッシュしているようですね〜

このファイルは基本的に見る必要はありません、

### .metadata

このファイルはこのFlutterプロジェクトのプロパティを追跡します。

Flutterツールで機能を評価し、アップグレードなどを実行するために使用されます。

このファイルはバージョン管理されている必要があり、手動で編集しないでください。

と、ファイル自体に説明が書かれていますが、なんなんですかね〜



## Dart記法

いきなり丸投げですが、以下でも読んでいただいたほうがてっとり速いかなと思います。

>  Dartの基本文法を振り返る - Qiita https://qiita.com/sileader/items/875f7f1fdc3995ceafda

私が知らなかったところと特徴的なところだけ抜粋します。
まずはdynamicとvarについて。

> Dartの型の表記には`dynamic`と`var`もあります。
> `dynamic`は動的型で`List`などのように書きたい時にも使えます。
> `var`は型推論を行い代入時の型になります。なので、再代入時に別の型の値を入れるとエラーになります。
>
> 動的型を乱用することは良いとは思いませんが、動的型を使わないと記述不可能な場面は出てくると思いますので、ここぞという時に使うと良いと思います。

finalとconst。constはちょーつよいfinalって認識。constは初期値がないとエラーになります。

> Dartは型の修飾子として`final`と`const`が存在しています。
> `final`はJavaの`final`と同じ意味です。つまり再代入不可であることを示します。
> `const`はコンパイル時定数。つまり、「再代入不可 + そのメモリ領域の書き換え不可」を表します。

名前付きコンストラクタ。あまり使いみちは分かっていない…

```dart
class Klass {
  Klass.fromString(String f);
}
```

ファクトリ。~~こちらも効果的な使い方は不明。~~ Singletonを定義できるので、２つとインスタンスを作られたくない処理を書くときに（共通設定画面系とか）。

```dart
class Klass {
  Klass._private(); // 非公開な名前付きコンストラクタ
  factory Klass() {
    var instance = Klass._private(); // newは省略できる
    return instance;
  }
}
```

定数コンストラクタ。

```dart
class Klass {
  final String name;
  const Klass(this.name); // this.nameのようにするとnameという引数で受けそのままthis.nameに代入できる
}
```

ちなみに、クラスのフィールドに値を代入する際は、コンストラクタの引数を活用する（initializing formalというらしい）。
以下のようにコンストラクタの引数を直接フィールドに代入できます。

initializingFormalの例

```dart
class Point {
  num x, y;
  Point(this.x, this.y);
}
```

以下のようにfinalなフィールドはコンストラクタのbodyで初期化できないため、Initializerという機能を使う。Initializerはコロンに続いてコンマ区切りの初期化処理を書くことができる。

```dart
class Dog {
  String name;
  final Owner owner;

  Dog(this.name): owner = Owner('John');
}
```

Initializerを使って、別のコンストラクタにリダイレクトすることもできる。

```dart
class Dog {
  String name;
  final Owner owner;

  Dog(this.name): this.owner = Owner('John');
  
  Dog.pochi(): this.name = 'Pochi',
               this.owner = Owner('John');
               
  Dog.taro(String name): this(name);
}
```



### 繰り返し

forはフツー？なのでそれ以外を。

forEach文

```dart
List<int> list = [1, 2, 3];
Map map = { 1 : 'first',
            2 : 'second',
            3 : 'third',
}; 
// forEach
list.forEach((int value){print(value);});
map.forEach((key,value){print('$key : $value');});
```

for-in文

```dart
List<int> list = [1, 2, 3];
// for-in
for(var value in list){
    print(value);
}
```

do-while文

```dart
List<int> list = [1, 2, 3];
var index = 0;
do {
    print(list[index]);
    index++;
} while(index < list.length);
```

### 覚えておくべき関数

操作するリスト
`var fruits = ['banana', 'pineapple', 'watermelon'];`
`var numbers = [1, 3, 2, 5, 4];`

map()

与えられた要素に処理を掛けたあとにその要素群に対する新しいリストを作成する。

```dart
 var mappedFruits = fruits.map((fruit) => 'I love $fruit').toList();
 print(mappedFruits); // => ['I love banana', 'I love pineapple', 'I love watermelon']
```

reduce() / fold()

与えられた関数を使って、要素を単一の値に圧縮する。

```dart
 var sum = numbers.reduce((curr, next) => curr + next);
 print(sum); // => 15
 const initialValue = 10;
 var sum2 = numbers.fold(initialValue, (curr, next) => curr + next);
 print(sum2); // => 25
```













### Mix-in

以下のサイトの説明が丁寧と思われる。現時点で自分はあまり使いそうにないので割愛。

すぐにFlutterを始めたい人のためのDart入門(前編) https://itome.team/blog/2019/12/flutter-advent-calendar-day3/



### 名前付き引数

チョー重要。

> 名前付き引数は引数の名前を指定して値を渡す引数です。
> 名前付き引数を使うことで、次のような利点があります。
>
> 1. 仮引数名が適切であれば読みやすいコードになる
> 2. 引数の順番にこだわる必要がない
>
> 名前付き引数は引数を`{}`で括ります。

```dart
func({first, last});

// 呼び方
func(first: 'f'); // firstだけ指定
func(last: 'l'); // lastだけ指定
func(first: 'f', last: 'l'); // firstとlastを指定
```

必須の引数は **@required** を付ける。オプション引数は **[]** で囲う。

クラスのsetter/getterは以下の通り。

```dart
class Klass {
  String _name; // 外部からは取得だけ許可したい

  String get name => _name;
}
```

```dart
class Klass {
  String _name;

  String get name => _name;
  set name(String v) {
    _name = v;
  }
}
```

### Listの扱い方

- [ここ](Dart List チートシート - Flutter入門 https://priceless-bardeen-f24139.netlify.com/array-list-cheat-sheet/)が参考になります。

- よく使われ処理を列挙します

  操作するリスト
  `var fruits = ['banana', 'pineapple', 'watermelon'];`
  `var numbers = [1, 3, 2, 5, 4];`

  - forEach()

    ```dart
     fruits.forEach((fruit) => print(fruit)); // => banana pineapple watermelon
    ```

  - map()

    ```dart
    var mappedFruits = fruits.map((fruit) => 'I love $fruit').toList();
    print(mappedFruits); // => ['I love banana', 'I love pineapple', 'I love watermelon']
    ```

  - contains()

    ```dart
     print(numbers.contains(6)); // => false
     print(fruits.contains('pineapple')); // => true
    ```

  - sort()

    ```dart
    print(numbers.sort((num1, num2) => num1 - num2)); // => [1, 2, 3, 4, 5]
    print(fruits.sort((a, b) => a.length.compareTo(b.length))); // => [banana, pineapple, watermelon]
    ```

  - reduce(), fold()

    ```dart
    var sum = numbers.reduce((curr, next) => curr + next);
    print(sum); // => 15
    const initialValue = 10;
    var sum2 = numbers.fold(initialValue, (curr, next) => curr + next);
    print(sum2); // => 25
    ```

  - every()

    ```dart
     List<Map<String, dynamic>> users = [
       { "name": 'John', "age": 18 },
       { "name": 'Jane', "age": 21 },
       { "name": 'Mary', "age": 23 },
     ];
     var is18AndOver = users.every((user) => user["age"] >= 18);
     print(is18AndOver); // => true
    
     var hasNamesWithJ = users.every((user) => user["name"].startsWith('J'));
     print(hasNamesWithJ); // => false
    ```

  - where(), firstWhere(), singleWhere()

    ```dart
    // See #6 for users list
     var over21s = users.where((user) => user["age"] > 21);
     print(over21s.length); // => 1
     var nameJ = users.firstWhere((user) => user["name"].startsWith(‘J’), orElse: () => null);
     print(nameJ); // => {name: John, age: 18}
     var under18s = users.singleWhere((user) => user["age"] < 18, orElse: () => null);
     print(under18s); // => null
    ```

  - take(), skip()

    ```dart
     var fiboNumbers = [1, 2, 3, 5, 8, 13, 21];
     print(fiboNumbers.take(3).toList()); // => [1, 2, 3]
     print(fiboNumbers.skip(5).toList()); // => [13, 21]
     print(fiboNumbers.take(3).skip(2).take(1).toList()); // => [3]
    ```

  - List.from()

    ```dart
    var clonedFiboNumbers = List.from(fiboNumbers);
    print('Cloned list: $clonedFiboNumbers');
    ```

  - expand()

    ```dart
    var pairs = [[1, 2], [3, 4]];
    var flattened = pairs.expand((pair) => pair).toList();
    print('Flattened result: $flattened'); // => [1, 2, 3, 4]
    var input = [1, 2, 3];
    var duplicated = input.expand((i) => [i, i]).toList();
    print(duplicated); // => [1, 1, 2, 2, 3, 3]
    ```


- Wigetを繰り返し配置するfor文の例

  ```dart
  final children = <Widget>[];
  for (var i = 0; i < 10; i++) {
    children.add(new ListTile());
  }
  return new ListView(
    children: children,
  );
  ```

  or

  ```dart
  return new ListView(
    children: new List.generate(10, (index) => new ListTile()),
  );
  ```


### ジェネリクス

```dart
main() {
  // ジェネリックスのインスタンス化は型を指定する
  var cache = new Cache<String>();
  cache.setByKey('key', 'test');
  print('key=${cache.getByKey('key')}');

  // メソッドのジェネリックス
  // 型の制限をするときは型のextendsを使う
  T sum<T extends num>(List<T> list, T init){
    T sum = init;
    list.forEach((value) { 
      sum += value;
    });
    return sum;
  }

  int r1 = sum<int>([1,2,3], 0);
  print(r1);
  // 型指定しない場合は左辺の型に暗黙的に型推定される
  double r2 = sum([1.1, 2.2, 3.3], 0.0);
  print(r2);
}

// 型だけが違う実装をしたクラスを実装したい場合はジェネリックスを使うと便利
// インスタンス化するときTに型を指定する
class Cache<T> {
  Map<String, T> store = <String, T>{};

  T getByKey(String key) {
    return store[key];
  }

  void setByKey(String key, T value) {
    this.store.addAll(<String, T>{key: value});
  }
}
```

### 列挙型

```dart
// 列挙型
// 列挙子は宣言された順にインデックス（0始まり）が割り振られていて、 index で参照できる。
// enumを継承できなかったり（mixinにも使えない）、enumのインスタンスを自前で生成できない（定数のみしか使えない）
// 実装を持つことが出来ない以外、Javaとほぼ同じ
enum Color { red, green, blue }
```

### 例外処理

```dart
main() {
  void errorFunc() {
    try {
      // throw Exceptionで意図的に例外を投げる
      throw Exception('例外です');
    } on Exception catch(e) {
      // 捕まえる型を指定するには on ~~ catch を使う
      // eはException型
      print(e);
      // rethrowでtry-catch-finallyブロックの外に例外を投げ直す事ができる（関数の外などでcatchする必要あり）
      rethrow;
    } finally {
      // finallyブロックは例外の有無にかかわらず実行される、省略可。
      print('finally');
    }
  }

  // 例外処理：try-catch文
  try {
    errorFunc();
  } catch (e, s) {
    // 型を指定しないcatchは、何型かわからない例外全部キャッチする
    // catchに仮引数を２つ指定すると、２つ目はStackTraceオブジェクトが入る
    print(s);
  }
}
```

### その他、知らなかった書き方

??は左側がnullの場合、右を返す。

```dart
return '$from says $msg platform: ${device} mood: ${mood ?? 'unknown'}';	
```

?.という書き方もある。

```dart
main () {
    // メソッド呼び出しを`?.`とすることで、nullならそのメソッドが実行されなくなる。
    a = null;
    assert(null == a?.map((v) => v * 2)?.reduce((v1, v2) => v1 + v2));

    // nullじゃなければ普通に実行される。
    a = [1, 2, 3];
    assert(12 == a?.map((v) => v * 2)?.reduce((v1, v2) => v1 + v2));
}
```

??=は左辺がnullのときだけ右辺の値を代入する。上の？系含め、これらはNull-aware演算子というらしい。

```
String str = null;
str ??= '';

// ↓と同じ
String str = null;
if (str == null) {
  str = '';
}
```

isは型判定するときに使う。

```daRT
var a = "value"
//Stringなのでtrueがかえります
a is String
```

カスケード記法。

```dart
main() {
  // カスケード記法、..で対象のインスタンスに対するメンバ関数呼び出しの操作を続けられる
  var fullString = StringBuffer()
    ..write('Use a StringBuffer for ')
    ..writeAll(['efficient', 'string', 'creation'], ' ')
    ..toString();
  print(fullString);
  // 次と等価
  // var sb = StringBuffer();
  // sb.write('Use a StringBuffer for ');
  // sb.writeAll(['efficient', 'string', 'creation'], ' ');
  // var fullString = sb.toString();
  // print(fullString);
}
```

ドキュメントコメント

- 型や関数は`[]`で囲む
- サマリーを１行目に書き、２行目は開ける
- コードサンプルを書く場合は```で囲む
- 他の言語でよくある`@param`などは使用せず、文章で説明する

```daRT
/// Defines a flag.
///
/// Throws an [ArgumentError] if there is already an option named [name] or
/// there is already an option using abbreviation [abbr]. Returns the new flag.
///
/// ```
/// addFlag('sampleName', '.....');
/// ```
Flag addFlag(String name, String abbr) => ...
```

#### part

外部のパッケージを読み込むときはimportを使いますが、自分自身のパッケージ内のファイルの依存関係の解決はpartとpart ofを使う。それぞれ、

- part
  - 私のパーツはこの子です
- part of
  - 私はこの親のパーツです

という宣言になる。

````dart
// something.dart(親)
library something;

part "src/something_foo.dart";

void main () {
    Foo foo = new Foo();
}
````

``` daRT
// src/something_foo.dart(子)
part of something;

class Foo() {
    // something
}
```



## Flutterのコーディングのススメ方

### 初歩的なところ

- まずはアプリのベースとなるWidgetを知る

  - 以下のようなテンプレをスニペットにでも登録しとくとよいかも

    ```dart
    void main() { // main.dartは基本これだけを呼ぶ
      runApp(MaterialApp( // runAppにはStatelessWidget or StatefullWidgetを継承したものを渡す
        home: HomePage(),  //homeで、ルーティング情報ここにかいてく。
      ));
    }
    
    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(); //Scaffold() アプリの中身（appbar+title構造）を簡単につくれる
      }
    }
    ```

    タイトルとバーを作るスニペット

    ```dart
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("あああああ"),
            backgroundColor: Colors.green,
          ),
        );
      }
    ```

    scaffold()は、バー、タイトル、中身、アクション、ボトムの典型的なアプリ構造を簡単にできる仕組みらしい。

    

- StatefulWidgetとStatelessWidgetを知る

  - Statelessは静的なコンテンツを表示する際に使うWidget
  - Statefulは状態を持つ、動的な画面を作る際に使うWidget
  - どちらも内部にbuildメソッドを持ち、buildメソッドの返り値としてレンダリングしたいWidgetを記載する
  - StatefulWigetはStateクラスを継承したクラスを作成し、そのクラスがもつsetStateメソッドで状態を変化させることでbuildによる再レンダリングを呼び出し、画面描画しなおす（Rebuild）
    - このsetStateを呼びまくってbuildが走りまくるとめっちゃ重いアプリになること必須
    - setStateで状態を保持する変数は、一時的な変数として、_（アンダーバー）で始まるローカル変数とするとよい。
      - なのでBLoC等のデザインパターン？があると認識

- レイアウトを構成するWidgetを知る

  - Wiget → child or children → Wiget 以下無限ループ、みたいなWigetの入れ後構造でレイアウトを組んでいくことになります
  - Row、Column、Padding、Continer等のレイアウトを組むWidgetの使いかたを覚えましょう
  - ListviewやGridView、Card等、簡単に見栄えの良いコンテンツを表示できるWidgetの使い方も覚えておくと便利です
    - ListTileも簡単に画像＋タイトルみたいなリストを作れるので便利ですよ！

- コンストラクタを知る(上にも書いてあるけど再掲)

  - `super` 基本クラスのコンストラクターを呼び出すために使用されます。

    `Key` Flutterでウィジェットを識別するために使用されるタイプで、ツリー内で移動されたウィジェットが以前に別の場所にあったウィジェットと同じである場合にFlutterが認識できるようにします。キーに関する良いビデオがここにあります：https://www.youtube.com/watch?v=kn0EOS-ZiIc

  - 名前付きコンストラクタとリダイレクトコンストラクタはちょっとわかりづらいが、[こちら]([https://qiita.com/naoaki_kaito/items/9ea5d956d5d6e23c5d6a#%E5%85%83%E3%81%A8%E3%81%AA%E3%82%8B%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A6%E3%82%A3%E3%82%B8%E3%82%A7%E3%83%83%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B](https://qiita.com/naoaki_kaito/items/9ea5d956d5d6e23c5d6a#元となるカードのウィジェットを作成する))が分かりやすいと思います。ちょっと抜粋させていただくと、

    > ```dart
    > Column(
    > children: <Widget>[
    >  MemberCard.normal(name: '鈴木 たかし', imgFileName: 'avator1.png'),
    >  MemberCard.disabled(name: '高橋 ゆうさく', imgFileName: 'avator2.png'),
    >  MemberCard.normal(name: 'Sarah Adams', imgFileName: 'avator3.jpeg'),
    >  MemberCard.normal(name: 'Richard Mason', imgFileName: 'avator4.jpeg'),
    > ]
    > )
    > ```

    オプショナルにしたければ{}で囲む。必須なら@requiredを付けるのがオススメ。

    以下も抜粋しますが、イニシャライザ（：）につづいてthis.コンストラ名と記載するとコンストラクタの処理をリダイレクトすることができる。

    

- 画面遷移の仕方を知る

  - Flutterでは、ページ間を移動する方法がいくつかあります。
    ・route名をMapに指定（MaterialApp）
    ・routeに直接移動する（WidgetApp）

  - 例として、以下のように定義し、

    ```daRT
    void main() {
      runApp(MaterialApp(
        home: MyAppHome(), // becomes the route named '/'
        routes: <String, WidgetBuilder> {
          '/a': (BuildContext context) => MyPage(title: 'page A'),
          '/b': (BuildContext context) => MyPage(title: 'page B'),
          '/c': (BuildContext context) => MyPage(title: 'page C'),
        },
      ));
    }
    ```

    以下のように呼び出すことで画面遷移が可能

    ```dart
    Navigator.of(context).pushNamed('/b');
    ```

- スタックに画面を積むイメージです。pushで積んでpopで取り去るイメージを持って貰えればよいかと思います。

  以下のようなRouteの作り方もある。

  ```dart
  Navigator.of(context).push(
    MaterialPageRoute<void>(
      builder: (BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Hello')),
          body: ...,
        );
      },
    ),
  );
  ```

  引数を渡すときは、以下のようにし、

  ```dart
  Navigator.of(context).pushNamed('/a', arguments: 'Hello');
  ```

  受け取り側では以下のように値を取り出す。

  ```dart
  final args = ModalRoute.of(context).settings.arguments;
  
  print(args); // 'Hello'
  ```

  複数の値を渡したいときは、以下のようにクラスを作る。

  ```dart
  class MyPageArguments {
    MyPageArguments({this.name, this.greeting});
  
    final String name;
    final String greeting;
  }
  
  final args = ModalRoute.of(context).settings.arguments;
  
  print(args); // MyPageArguments { name: .., greeting: .. }
  ```

  遷移先から遷移元へ返却したい値を `pop` メソッドに渡すこともできます。

  ```dart
  Navigator.of(context).pop('Hi!');
  ```

  遷移元は、 `Navigator.push` メソッドが返す `Future` から 遷移先が返した値を取り出すことができます。 `Future` で値を返すのは他のフレームワークと比べても珍しいですが、ギャラリーを開いて画像を取得したり 外部サービスでログインしてからアプリに戻ってきたりなどのときにも使うFlutterの頻出パターンです。

  ```dart
  final result = await Navigator.of(context).pushNamed('/a', arguments: 'Hello');
  
  print(result); // 'Hi';
  ```



### Futureとasync/await

- 非同期処理を含むクラスはFuture型とする。

- JavaScriptと同じくasnyc/await方式。

  - 非同期処理（時間を要し、ユーザビリティに影響を与え得る処理）が必要な関数にはasync宣言
  - awaitを書くことで以降の処理を待ち合わせる（メソッド自体がasyncで非同期になっているため、シングルスレッドを妨害しない）

- アロー演算子で書いた非同期処理は以下みたいな感じ

  ```dart
  const foo = async () => {
    // do something
  }
  ```

- 即時関数として書いた場合は以下みたいな感じ

  ```dart
  (async () => {
      console.log(await asyncFunction());
  })();
  ```

- [こちら](DartのFutureを使って非同期処理を書く https://sbfl.net/blog/2015/01/05/writing-asynchronous-operation-with-future-in-dart/)がよく纏まっています

### FutureBuilder

[こちら](FutureBuilderを使って非同期にデータ表示 -ひとりアドベントカレンダー Flutter編 2018 その6-｜nbht｜note https://note.com/nbht/n/n3af5f1a311c9)がよく纏まっています。

### StreamBuilder

StreamBuilderのイメージは、コードの順序関係なく流れてきたデータをキャッチして処理を行う感じです。
そして2つの要素、streamとbuilderから構成されています。

```dart
 Widget userName() {

    return StreamBuilder<QuerySnapshot>(

        //表示したいFiresotreの保存先を指定
        // snapshotsでデータ取得
        stream: Firestore.instance
        .collection('users')
        .document(firebaseUser.uid)
        .collection("transaction")
        .snapshots(),

        //streamが更新されるたびに呼ばれる
        builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot> snapshot) {

          //データが取れていない時の処理
          if (!snapshot.hasData) return const Text('Loading...');

          return Text(snapshot.data.documents[0]['userName']);

        }
     );
  }
```

   

### key

以下をあとで纏める予定！！

<a href="https://qiita.com/kurun_pan/items/0517fb62f1b47c90882c">Flutter Widget Keyの種類と使い方について - Qiita</a>

ちょっとした通知と簡単なアクションを促すのに便利なスナックバー - 各種 Material ウィジェットの使い方 - Flutter 入門 https://flutter.keicode.com/basics/snackbar.php



### Intentの処理方法

Under Construction



### StatefulWidgetのメソッド構成

| クラス | メソッド                          | メソッド                                                     |
| ------ | --------------------------------- | ------------------------------------------------------------ |
| Widget | createState()                     | ビルド後に呼ばれるメソッドで必須。型はState。                |
| State  | initState()                       | 最初に一度呼ばれる。Widgetツリーの初期化を実行。             |
|        | didChangeDependencies()           | initState()呼び出し直後に呼ばれる。Widgetツリーの変更を要素に通知する。 |
|        | build()                           | didChangeDependencies()呼出し後にしか呼ばれない。複数回呼ばれる。Widgetツリーを置き換える。 |
|        | didUpdateWidget(Widget oldWidget) | リビルド時のinitState()的な位置づけ。                        |
|        | setState()                        | 任意で呼べるメソッド。Widgetツリーを再構成して、変更を反映させる。簡単な変数の代入だけでなく非同期処理でも使える。 |
|        | deactivate()                      | StateをWidgetツリーから削除する。滅多に使わないらしい。      |
|        | dispose()                         | Stateを永続的に削除する。画面をおとすときやストリーム停止で使うらしい。 |



### Collection if

Flutterのchildrenが書きやすいようになるためだけに生まれたような新機能です。childrenの要素に条件分岐が入るときでも宣言的な構文を崩さずにかけます。

```dart
Widget build(BuildContext context) {
  var children = [
    IconButton(icon: Icon(Icons.menu)),
    Expanded(child: title)
  ];

  if (isAndroid) {
    children.add(IconButton(icon: Icon(Icons.search)));
  }

  return Row(children: children);
}
```

### Collection for

Collection ifと一緒に追加された機能。こちらもFlutterの影響がかなり大きいです。

```dart
Widget build(BuildContext context) {
  
  return Row(
    children: titles.map((title) => Text(title)).toList(),
  );
}
```

```dart
Widget build(BuildContext context) {
  return Row(
    children: [
      for (final title in titles) Text(title),
    ],
  );
}
```



### カスタムフォントを使う

- pubspec.yamlに以下を記載（fontsフォルダは作ってください）

  ```yaml
  fonts:
     - family: MyCustomFont
       fonts:
         - asset: fonts/MyCustomFont.ttf
         - style: italic
  ```

  そして、フォントをTextWidgetに割り当てる

  ```dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(
        child: Text(
          'This is a custom font text',
          style: TextStyle(fontFamily: 'MyCustomFont'),
        ),
      ),
    );
  }
  ```

## 覚えておきたいWidget

### Text

基本的な使い方

```dart
Text(
                  'Text largeeeeeeeeeeeeeeeeeeeeeee',
                  overflow: TextOverflow.ellipsis,
                  style: new TextStyle(
                    fontSize: 13.0,
                    fontFamily: 'Roboto',
                    color: new Color(0xFF212121),
                    fontWeight: FontWeight.bold,
                  )
```

長くなってはみ出しそうなときは、EllipsisもしくはFadeを使うとよい

### TextField

「TextEditingController」（以下ではそのインスタンスをeCtrlとしている）はテキスト入力を制御するのに利用します。
「TextField」や、「TextFormField」に対して「TextEditingController」を使って、複雑な制御を行うことができます。
「TextField」の制御を「TextEditingController」を利用して行います。

```dart
TextField(
  controller: eCtrl,
  onSubmitted: (text) {
    litems.add(text);  // Append Text to the list
    eCtrl.clear();     // Clear the Text area
    setState(() {});   // Redraw the Stateful Widget
  },
),
```

eCtrl.text() でフィールドに入力した内容を取得する
eCtrl.clear() でフィールドに入力した内容をクリアする

Text(
  "This is a long text",
  overflow: TextOverflow.ellipsis,
),

Fade

```dart
Text(
  "This is a long text",
  overflow: TextOverflow.fade,
  maxLines: 1,
  softWrap: false,
),
```

### Container

ちなみに、正攻法かは？ですが、以下のように特定の値によってWidgetを表示／何も表示しない、というのをContainerで実装することもできるようです。

```dart
Widget foo(bool isVisible) {
  if (isVisibile) {
    return Text("foo");
  } else {
    return Container(); // Empty Container Widget
  }
}
```



### FlatButton （ボタンのデザインは[こちら](https://qiita.com/coka__01/items/30716f42e4a909334c9f)参照）

```dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Form',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Form'),
        ),
        body: Center(
          child: ChangeForm(),
        ),
      ),
    );
  }
}

class ChangeForm extends StatefulWidget {
  @override
  _ChangeFormState createState() => _ChangeFormState();
}

class _ChangeFormState extends State<ChangeForm> {
  int _count = 0;

  void _handlePressed() {
    setState(() {
      _count++;
    });
  }

  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(50.0),
      child: Column(
        children: <Widget>[
          Text(
            "$_count",
            style: TextStyle(
              color:Colors.blueAccent,
              fontSize: 30.0,
              fontWeight: FontWeight.w500
            ),
          ),
          FlatButton(
            onPressed: _handlePressed,
            color: Colors.blue,
            child: Text(
              '更新',
              style: TextStyle(
                color:Colors.white,
                fontSize: 20.0
              ),
            ),
          )
        ],
      )
    );
  }
}
```



### RaisedButton

```dart
RaisedButton(
  onPressed: _handlePressed,
  color: Colors.blue,
  child: const Text(
    '更新',
    style: TextStyle(
        color:Colors.white,
        fontSize: 20.0
    ),),
)
```



### OutlineButton

```dart
OutlineButton(
  onPressed: _handlePressed,
  borderSide: BorderSide(color: Colors.blue),
  child: const Text(
    '更新',
    style: TextStyle(
        color:Colors.blue,
        fontSize: 20.0
    ),
  ),
)
```



### IconButton

```dart
IconButton(
  iconSize: 100,
  onPressed: _handlePressed,
  color: Colors.blue,
  icon: Icon(Icons.add_circle_outline),
)
```



### FloatingActionButton

```dart
FloatingActionButton(
  onPressed: _handlePressed,
  backgroundColor: Colors.blue,
  child: Icon(Icons.add)
)
```



文字も設定できる。

```dart
FloatingActionButton(
  onPressed: _handlePressed,
  backgroundColor: Colors.blue,
  child: const Text(
    '更新',
    style: TextStyle(
        color:Colors.white,
        fontSize: 20.0
    ),
  ),
)
```



横長にもできる。

```dart
FloatingActionButton.extended(
  onPressed: _handlePressed,
  backgroundColor: Colors.blue,
  icon: Icon(Icons.add),
  label: const Text('ボタン'),
)
```



### PopupMenuButton

```dart
PopupMenuButton<String>(
  onSelected: _handleChange,
  itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
    const PopupMenuItem<String>(
      value: "1",
      child: Text('選択1'),
    ),
    const PopupMenuItem<String>(
      value: "2",
      child: Text('選択2'),
    ),
    const PopupMenuItem<String>(
      value: "3",
      child: Text('選択3'),
    ),
    const PopupMenuItem<String>(
      value: "4",
      child: Text('選択4'),
    ),
  ],
),
```



### DropdownButton

```dart
class _ChangeFormState extends State<ChangeForm> {

  String _defaultValue = 'りんご';
  List<String> _list = <String>['りんご', 'オレンジ', 'みかん', 'ぶどう'];
  String _text = '';

  void _handleChange(String newValue) {
    setState(() {
      _text = newValue;
      _defaultValue = newValue;
    });
  }

  Widget build(BuildContext context) {
    return Container(
        padding: const EdgeInsets.all(50.0),
        child: Column(
          children: <Widget>[
            Text(
              "$_text",
              style: TextStyle(
                  color:Colors.blueAccent,
                  fontSize: 30.0,
                  fontWeight: FontWeight.w500
              ),
            ),
            DropdownButton<String>(
              value: _defaultValue,
              onChanged: _handleChange,
              items: _list.map<DropdownMenuItem<String>>((String value) {
                return DropdownMenuItem<String>(
                  value: value,
                  child: Text(value),
                );
              }).toList(),
            ),
          ],
        )
    );
  }
}
```



### ListTile

アイコン付きのリストみたいなのを簡単に表示できるのでとても便利です。
ダイアログを出すときにも使えます。isThreeLineがtrueの場合、heightが高くなります。denseがtrueの場合、defaultTextStyleの設定がなくなり、テキスト間の距離が密になります。contentPaddingを設定すると、デフォルトで適用されているpaddingがなくなり、指定したpaddingが適用されます。
CheckboxListTileとRadioListTileってのもありますが自分はあまり使わなそうなので割愛。

```dart
Card(
  child: ListTile(
    leading: Icon(Icons.people),
    title: Text("ListTile with isThreeLine true"),
    trailing: Icon(Icons.more_vert),
    subtitle: Text(
        "This is subtitle. Subtitle is very long and use three lines."),
    onTap: () {},
    isThreeLine: true,
    dense: true,
    contentPadding: EdgeInsets.all(10.0),
  ),
),
```



### ListView

### ListView.builder

ListViewに動的コンテンツ（Listとか）を表示する場合に使う。

使い方としては、いくつのリストを表示するかをitemCountに設定し、実際の表示コンテンツはitemBuilderに設定する。以下、サンプル。

```dart
body: new ListView.builder
  (
    itemCount: litems.length,
    itemBuilder: (BuildContext ctxt, int index) {
     return Text(litems[index]);
    }
  )
```



### [GridView](https://flutter.ctrnost.com/basic/layout/gridview/)

GridViewは写真などのボックス状のレイアウトを配置しスクロールするのに便利なウィジェットです。

GridViewを構築するためには4つの方法があります。

1. 「GridView.count」は最も一般的に使用されるグリッドレイアウトとなり、横に並べる数を固定数で指定してグリッドを表示します。
2. 「GridView.extent」は横に並べる幅を指定値分最大限確保してグリッドを並べる表示に方法になります。
3. 「GridView.builder」で作成すると、実際に表示されている子要素のみビルダーが呼び出されるため随時読み込みなど、多数(または無限)のリストを表示する場合に利用する作成方法です。
4. 「GridView.custom」で作成すると、「SliverGridDelegate」を追加してカスタマイズでき、整列されていないまたは重ねた配置を実現できます。

### GridView.countのサンプル実装

```dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var list = [
      _photoItem("pic0"),
      _photoItem("pic1"),
      _photoItem("pic2"),
      _photoItem("pic3"),
      _photoItem("pic4"),
      _photoItem("pic5"),
    ];
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text('GridView'),
            ),
            body: GridView.count(
                crossAxisCount: 2,
                children: list
            )
        )
    );
  }

  Widget _photoItem(String image) {
    var assetsImage = "assets/img/" + image + ".png";
    return Container(
      child: Image.asset(assetsImage, fit: BoxFit.cover,),
    );
  }
}
```

`crossAxisCount`で横に並べる件数を指定することでグリッドが並びます。

### GridView.builderのサンプル実装

`GridView.builder`は「ListView」の時同様、表示する要素が事前にわからない場合に利用する書き方です。
`itemBuilder`は画面表示時に実行されるため、無限にグリッドを作成することが可能です。
ギャラリー、検索結果の表示などに利用すると良いかと思います。

```dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var grid = ["pic0", "pic1", "pic2", "pic3", "pic4", "pic5",];
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text('GridView'),
            ),
            body: GridView.builder(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                ),
                itemBuilder: (BuildContext context, int index) {
                  if (index >= grid.length) {
                    grid.addAll(["pic0", "pic1", "pic2", "pic3", "pic4", "pic5",]);
                  }
                  return _photoItem(grid[index]);
                }
            )
        )
    );
  }

  Widget _photoItem(String image) {
    var assetsImage = "assets/img/" + image + ".png";
    return Container(
      child: Image.asset(assetsImage, fit: BoxFit.cover,),
    );
  }
}
```

builderで利用している、SliverGridDelegateWithFixedCrossAxisCountはGridView.countと同じです。
crossAxisCountで横に並べる数を指定して、グリッドを作成します。

グリッド同士の間にスペースを作成したいときに指定するのが、`mainAxisSpacing`と`crossAxisSpacing`になります。
RowやColumnでも同じ考え方が出てきましたが、以下のようなイメージで利用します。

このプロパティは、「GridView」クラスであれば上記で説明したどれでも利用できます。

### StreamBuilder

StreamBuilderのイメージは、コードの順序関係なく流れてきたデータをキャッチして処理を行う感じです。
そして2つの要素、streamとbuilderから構成されています。

### StreamBuilderのサンプル実装

```dart
Widget userName() {

    return StreamBuilder<QuerySnapshot>(

        //表示したいFiresotreの保存先を指定
        stream: Firestore.instance
        .collection('users')
        .document(firebaseUser.uid)
        .collection("transaction")
        .snapshots(),

        //streamが更新されるたびに呼ばれる
        builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot> snapshot) {

          //データが取れていない時の処理
          if (!snapshot.hasData) return const Text('Loading...');

          return Text(snapshot.data.documents[0]['userName']);

        }
     );
  }
```

### Expanded and Flexible

[こちら](https://qiita.com/kalupas226/items/5aa41ca409730606000f)がよくまとまっていますので一読ください。

注意したいのは、どちらもRowやColumnのなかでつかうものであることと、Widgetのサイズの比率を調整するものであることです。違いとしては、

- Expandedは内部に配置したWidgetのサイズ（例えばTextの長さ）に関わらずに指定した比率にする
- FlexibleはExpandedと同様にflexプロパティで比率を指定するものの、内部に配置したWidgetのサイズに合わせて柔軟に比率を変更する

<u>ちなみに、Columnの下でListView.builder等の縦幅不明のWidgetを使う場合、Expandedで囲まないとエラーになったりする。</u>

### AlertDialog

おなじみの注意を促すダイアログ。

```dart
            var alert = new AlertDialog(
              title: new Text("グループを作成する"),
              actions: <Widget>[
                new FlatButton(onPressed: () {
                  Navigator.pop(context);
                  return Navigator.pushNamed(context, "/create_group");
                }, child: new Text("作成する")),
              ],
            );
            showDialog(
                context: context,
                builder: (BuildContext context) {
                  return alert;
                });
```

ちなみにNavigator.pop(context)でダイアログを取り消せる。

事前にルーティング情報を定義しておく場合は、あらかじめビルド関数を定義するので動的にパラメータを渡すことができません。パラメータを渡す場合は、遷移都度ルーティング情報を設定する方法で対応します。

```dart
Navigator.push(
    context,
    new MaterialPageRoute<Null>(
    settings: const RouteSettings(name: "/my-page-1"),
    builder: (BuildContext context) => MyPage1(/* 必要なパラメータがあればここで渡す */),
    ),
);
```

ダイアログの表示をsetState()でリフレッシュしたい場合も注意が必要です。

詳しくは[こちら](FlutterでDialogの表示を変更できなかったお話 - makicamelの日記 https://makicamel.hatenablog.com/entry/2019/04/06/005255)を御覧ください。

## Widgetの応用　

### StreamBuilderとGridViewの組み合わせ

[ここ](https://stackoverflow.com/questions/54923549/gridview-builder-with-firebase-realtime-database-and-futurebuilder)参考

```dart
StreamBuilder(
              stream: FirebaseDatabase.instance
                  .reference()
                  .child('messages')
                  .child(groupId)
                  .orderByChild('messagetype')
                  .equalTo(1)
                  .onValue,
              builder: (BuildContext context, AsyncSnapshot<Event> snapshot) {
                if (snapshot.hasData) {
                  if (snapshot.data.snapshot.value != null) {
                    Map<dynamic, dynamic> map = snapshot.data.snapshot.value;
                    List<dynamic> list = map.values.toList()
                      ..sort(
                          (a, b) => b['timestamp'].compareTo(a['timestamp']));

                    return GridView.builder(
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 3),
                      itemCount: list.length,
                      padding: EdgeInsets.all(2.0),
                      itemBuilder: (BuildContext context, int index) {
                        return Container(
                          child: GestureDetector(
                            onTap: () {
                              Navigator.push(
                                context,
                                MaterialPageRoute(
                                    builder: (context) => SecondScreen(
                                        imageUrl: list[index]["imageUrl"])),
                              );
                            },
                            child: CachedNetworkImage(
                              imageUrl: list[index]["imageUrl"],
                              fit: BoxFit.cover,
                            ),
                          ),
                          padding: EdgeInsets.all(2.0),
                        );
                      },
                    );
                  } else {
                    return Container(
                        child: Center(
                            child: Text(
                      'Es wurden noch keine Fotos im Chat gepostet.',
                      style: TextStyle(fontSize: 20.0, color: Colors.grey),
                      textAlign: TextAlign.center,
                    )));
                  }
                } else {
                  return CircularProgressIndicator();
                }
              })),
```

### Contextについて

[こちら](Flutterのcontextの「お気持ち」を理解する - Qiita https://qiita.com/agajo/items/93d75aafe87bdc7b2026)と[こちら](https://itome.team/blog/2019/12/flutter-advent-calendar-day6/)が非常に分かりやすく纏まっています。

>  BuildContextとは親Widget自身

私の理解を纏めると、BuildContextは、宣言された（build関数の中など）場所でのElement（Widgetへの参照）を返す。よって、例えばStateクラス下のbuildで受け取ったcontextは、そのbuildクラスのなかで、Scaffold.of(context)などとしても、上方向にScaffoldをたどることはできない（正確には辿ってもScaffoldがない）。

この場合、以下のようにしてScaffoldを辿れるようにするか、Builder widgetをScaffoldより下で使うことで解決できる。

```dart
// ここ重要
+  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

   @override
   Widget build(BuildContext context) {
    return Scaffold(
+     key: _scaffoldKey,
      body: Center(
        child: Text(
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
-         Scaffold.of(context).showSnackBar(SnackBar(content: Text('message')));
+         _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('message')));
            _incrementCounter();
        },
        child: Icon(Icons.add),
      ),
    );
  }
```

もしくは、

```dart
   @override
   Widget build(BuildContext context) {
    return Scaffold(
      body: ・・・ ,
      floatingActionButton: Builder(builder: (context) {
        return FloatingActionButton(
          onPressed: () {
            Scaffold.of(context).showSnackBar(SnackBar(content: Text('message')));
            _incrementCounter();
          },
          child: Icon(Icons.add),
        );
      }),
```



## pubspec.yamlの記法

Under Construction

ちなみに、import 'dart:convert' のように、dart: から始まるプラグインについてはdartの基本プラグインとして最初から内包されており、pubspec.yamlに追記する必要はない。



## おすすめプラグイン

### http

[こちら](HTTPにリクエストする | わかりやすいFlutterリファレンス エヌ次元株式会社 (n次元 エヌジゲン) https://nzigen.com/flutter-reference/2018-04-19-http.html) 参照。GETリクエストとかでREST APIからデータ取得するような操作をやるときには必須。

使い方は以下を参考に。

```dart
import 'package:http/http.dart' as http;
import 'dart:convert';

List<String> _data;
 
 void fetchPosts() async {
    const url = 'https://jsonplaceholder.typicode.com/posts';
    http.get(url)
        .then((response) {
      print("Response status: ${response.statusCode}");
      print("Response body: ${response.body}");
      setState(() {
        List list = json.decode(response.body);
        _data = list.map<String>((value) {
          return value['title'];
        }).toList();
      });
    });
  }

  @override
  void initState() {
    _data = [];
    fetchPosts();

    super.initState();
  }
```

ちなみに、上記の例ではjson.decodeでJSONのデコードをしているが、 [json_serializable](https://flutter.dev/docs/development/data-and-backend/json)ってのを使うとより簡単に型安全に記載できるらしい。そこらへんはまた追って追記します。

また、APIにGETリクエストを投げてどんなレスポンスがあるか簡単に知りたい場合は、[Postman](https://www.postman.com/)みたいなツールを使うと私みたいな素人にも分かりやすく簡単に扱えてよいのではないでしょうか。

（注）Unhandled Exception: InternalLinkedHashMap<String, dynamic> is not a subtype of type List<dynamic> が発生したときは[こちら](https://stackoverflow.com/questions/55430061/unhandled-exception-internallinkedhashmapstring-dynamic-is-not-a-subtype-of)を参照してください。JSONの形によりパースの仕方が異なります。



### flutter_advanced_networkimage

画像を表示するライブラリ。キャッシュやプリキャッシュを使う処理がかんたんに書ける。

詳しくは[こちら](https://pub.dev/packages/flutter_advanced_networkimage)

### Example

```dart
// using image provider
Image(
  image: AdvancedNetworkImage(
    url,
    header: header,
    useDiskCache: true,
    cacheRule: CacheRule(maxAge: const Duration(days: 7)),
  ),
  fit: BoxFit.cover,
)
// work with precacheImage
precacheImage(
  AdvancedNetworkImage(
    url,
    header: header,
    useDiskCache: true,
    cacheRule: CacheRule(maxAge: const Duration(days: 7)),
  ),
  context,
);

// or svg provider (flutter_svg)
SvgPicture(
  AdvancedNetworkSvg(url, SvgPicture.svgByteDecoder, useDiskCache: true),
)
// get the disk cache folder size
int cacheSize = await DiskCache().cacheSize();
// clean the disk cache
bool isSucceed = await DiskCache().clear();
```

### Flutter_launcher_icons

 iOS・Androidアプリアイコンを生成するとても便利なツール。
使い方は、以下のような感じ。

```dart
# pubspec.yaml(flutter_launcher_icons-development.yamlでも良い)
flutter_icons:
  image_path: "assets/images/icon.png"
  android: true
  ios: true
```

アプリアイコンを反映するには、image_pathを設定してから、以下コマンドを打つ必要あり。

```
flutter pub pub run flutter_launcher_icons:main
```



### その他

以下の記事で素敵なライブラリをたくさん纏めてくださっています。

【Flutter】アプリ開発_初心者のアプリをプロっぽくする最強のpackageを紹介 - Qiita https://qiita.com/kazumaz/items/876e162cf429014661d8#%E3%81%95%E3%81%8F%E3%81%A3%E3%81%A8%E8%A6%8B%E6%A0%84%E3%81%88%E3%82%92%E3%82%88%E3%81%8F%E3%81%99%E3%82%8B%E7%B7%A8



## Firebase

### Firebase CLI

GUIからだと面倒な手順はCLIから操作できたほうが便利で早い。

| コマンド               | 操作                                                         |
| ---------------------- | ------------------------------------------------------------ |
| firebase login         | ログイン                                                     |
| firebase projects:list | プロジェクト一覧を表示                                       |
| firebase init          | 現在のディレクトリに新しい Firebase プロジェクトを関連付けて設定する。このコマンドを実行すると、[`firebase.json`](https://firebase.google.com/docs/cli/?hl=ja#the_firebasejson_file) 構成ファイルが現在のディレクトリに作成される。 |
| firebase use           | CLI にアクティブな Firebase プロジェクトを設定する。<br/>プロジェクトのエイリアスを管理する。 |
| firebase logout        | ログアウト                                                   |
| firestore:delete       | アクティブ プロジェクトのデータベース内のドキュメントを削除する。CLI を使用すると、コレクション内のすべてのドキュメントを再帰的に削除できる。 |
| firestore:indexes      | アクティブなプロジェクトのデプロイされたインデックスをリスト表示する。 |



### セキュリティルール

テストモードで開発していると、Firebaseから、「データベースへのクライアント アクセスが◯日後に期限切れになります」みたいなメールをもらうことになります。その際はセキュリティルールを設定しないとアクセスできなくなります。

セキュリティルールは以下のように記載します。以下の例では、すべてのアプリユーザにデータベース全体の任意のドキュメントに対する読み書き権限を与えることになります。詳しくは[こちら](https://firebase.google.com/docs/firestore/security/rules-structure)を御覧ください。

```
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}
```

すべての Cloud Firestore セキュリティ ルールは、データベース内のドキュメントを識別する `match` ステートメントと、それらのドキュメントへのアクセスを制御する `allow` 式で構成されています。

ルールの設定はFirebaseコンソールまたはCLIからできます。

ルール シミュレータを使用すると、読み取り、書き込み、削除についてそれぞれ認証済みと未認証の動作をシミュレートできます。認証済みリクエストをシミュレートする場合は、さまざまなプロバイダから認証トークンを作成してプレビューすることができます。シミュレートされるリクエストは、現在デプロイされているルールセットではなく、エディタ内のルールセットと照らし合わせて実行されます。

```
service cloud.firestore {
  match /databases/{database}/documents {

    // Match any document in the 'cities' collection
    match /cities/{city} {
      allow read: if <condition>;
      allow write: if <condition>;
    }
  }
}
```

すべての match ステートメントは、コレクションではなくドキュメントを指す必要があります。match ステートメントでは `match /cities/SF` のように特定のドキュメントを指すことや、`match /cities/{city}` のようにワイルドカードを使用して、指定されたパスのすべてのドキュメントを指すことができます。

上の例では、match ステートメントで `{city}` ワイルドカード構文を使用しています。つまり、このルールは、`cities` コレクションの、`/cities/SF` や `/cities/NYC` などのすべてのドキュメントに適用されます。match ステートメントの `allow` 式が評価されると、`city` 変数は `SF` や `NYC` などの city のドキュメント名に解決されます。

状況によっては、`read` と `write` をより詳細なオペレーションに分割すると便利です。たとえば、アプリで、ドキュメントの作成に対して、削除の場合とは異なる条件を適用する必要がある場合があります。または、単一のドキュメントの読み取りを許可し、大規模なクエリを拒否することが必要になる場合があります。

`read` ルールは `get` と `list` に分割でき、`write` ルールは `create`、`update`、`delete` に分割できます。

```
service cloud.firestore {
  match /databases/{database}/documents {
    // A read rule can be divided into get and list rules
    match /cities/{city} {
      // Applies to single document read requests
      allow get: if <condition>;

      // Applies to queries and collection read requests
      allow list: if <condition>;
    }

    // A write rule can be divided into create, update, and delete rules
    match /cities/{city} {
      // Applies to writes to nonexistent documents
      allow create: if <condition>;

      // Applies to writes to existing documents
      allow update: if <condition>;

      // Applies to delete operations
      allow delete: if <condition>;
    }
  }
}
```

[こちら](https://flattsecurity.hatenablog.com/entry/2020/04/10/122834)も非常にわかりやすく纏まっているので参照ください。



### Firebase Authentication

[こちら](https://github.com/tattwei46/flutter_login_demo/blob/master/lib/main.dart)や[こちら](https://qiita.com/iketeruhiyoko/items/062ca43e1a4c474304bb)、[こちら](https://ayusch.com/flutter-firebase-authentication-tutorial/)が参考になる。

ユーザの作成

createUserWithEmailAndPasswordメソッドを呼び出すか、[Google ログイン](https://firebase.google.com/docs/auth/web/google-signin?hl=ja)や [Facebook ログイン](https://firebase.google.com/docs/auth/web/facebook-login?hl=ja)などのフェデレーション ID プロバイダを使用して、ユーザーが初めてログインすると、Firebase プロジェクトに新しいユーザーが作成されます。

現在ログインしているユーザを取得する

```javascript
firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  } else {
    // No user is signed in.
  }
});	
```

`currentUser` プロパティを使用しても、現在ログインしているユーザーを取得できます。ユーザーがログインしていない場合、`currentUser` は null です。

```dart
var user = firebase.auth().currentUser;

if (user) {
  // User is signed in.
} else {
  // No user is signed in.
}
```

ユーザーのプロフィールを取得する

ユーザーのプロフィール情報を取得するには、`User` のインスタンスのプロパティを使用します。

```javascript
var user = firebase.auth().currentUser;
var name, email, photoUrl, uid, emailVerified;

if (user != null) {
  name = user.displayName;
  email = user.email;
  photoUrl = user.photoURL;
  emailVerified = user.emailVerified;
  uid = user.uid;  // The user's ID, unique to the Firebase project. Do NOT use
                   // this value to authenticate with your backend server, if
                   // you have one. Use User.getToken() instead.
}
```

ユーザーのプロバイダ別のプロフィール情報を取得する

ユーザーにリンクされているログイン プロバイダからプロフィール情報を取得する場合は、`providerData` プロパティを使用します。

```javascript
var user = firebase.auth().currentUser;

if (user != null) {
  user.providerData.forEach(function (profile) {
    console.log("Sign-in provider: " + profile.providerId);
    console.log("  Provider-specific UID: " + profile.uid);
    console.log("  Name: " + profile.displayName);
    console.log("  Email: " + profile.email);
    console.log("  Photo URL: " + profile.photoURL);
  });
}
```

ユーザーのプロフィールを更新する

`updateProfile` メソッドを使用して、ユーザーの基本的なプロフィール情報（ユーザーの表示名とプロフィール写真の URL）を更新できます。

```javascript
var user = firebase.auth().currentUser;

user.updateProfile({
  displayName: "Jane Q. User",
  photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
  // Update successful.
}).catch(function(error) {
  // An error happened.
});
```

他の処理は[こちら](https://firebase.google.com/docs/auth/web/manage-users?hl=ja)から。



### Firestore

Collection、document、dataの概念は[こちら](https://gurutaka-log.com/flutter-firebase-firestore-sample)がわかりやすかったので参照してください。

分かりやすい絵を引用させていただきます。

![](C:\Users\okape\Dropbox\typora\Flutter.assets\structure-data.png)

また、登録

```dart
void setData(String collection, Map data) {
 Firestore.instance.collection(collection).document().setData(data);
}
```

Map型のデータ形式は以下のとおり。

```json
Map data = {
  "field名": "登録するデータ",
};
```

更新

```dart
void updateData(String collection, String documentID, Map data) {
  Firestore.instance.collection(collection).document(documentID).updateData(data);
}
```

削除

```dart
void deleteData(String collection, String documentId) {
  Firestore.instance.collection(collection).document(documentId).delete();
}
```



その他、以下を知っておくと良い。

- `CollectionReference`
- `Query`
- `DocumentReference`
- `QuerySnapshot`
- `QueryDocumentSnapshot`
- `DocumentSnapshot`

### firestoreからのデータ取得の流れ

具体的な話の前に、firestoreからデータを取得するときに、クライアント側でどういう処理をするかの全体像を確認しておきます。

1. データへの参照を作成する（例えば、`db.collection('cities')`や`db.collection('cities').doc('TOK')`）
2. 参照からスナップショットを得る（`get()`メソッド）
3. スナップショットからデータを得る（`data()`メソッド）

ここで、1の参照の作成と、3のスナップショットからデータへの変換は同期的で、2の参照からスナップショットへの変換だけが非同期（`Promise`が返ってくる）ことを覚えておくと良いでしょう。

それでは、"参照"と"スナップショット"について、具体的に以下で見ていきます。

### 参照（`CollectionReference`、`Query`、`DocumentReference`）

`CollectionReference`、`Query`、`DocumentReference`はいずれもデータへの参照です。このうち、`CollectionReference`と`Query`はほとんど同じもので、いずれも複数のドキュメントへの参照です。`DocumentReference`は一つのドキュメントへの参照です。

#### `CollectionReference`

`CollectionReference`はコレクションやサブコレクションへの参照で、以下のように作成できます。

```
const citiesRef = db.collection('cities')
```

`CollectionReference`を使って、コレクションにドキュメントを追加することができます。

```
citiesRef.add(data)
```

また、条件を絞り込んだり、ドキュメントのidを指定することで、後述する`Query`や`DocumentReference`を得ることができます。

```
const capitalsRef = citiesRef.where('capital', '==', true) // Query型
const SFRef = citiesRef.doc('SF') // DocumentReference型
```

#### `Query`

コレクションの中で、特定の条件を満たすドキュメントだけを取り出したいことや、一定の数のドキュメントを取り出したいことがあります。`Query`は、その絞り込んだ結果への参照で、例えば以下のように生成します。

```
// capitalプロパティがtrueなドキュメントの集合への参照
const capitalsRef = citiesRef.where('capital', '==', true) 

// ドキュメントの集合３つのみへの参照
const threeCitiesRef = citiesRef.limit(3)

// 人口で昇順にソートされたcitiesへの参照
const citiesSortedByPopulationRef = citiesRef.orderBy('population')
```

`Query`の使い方は、`CollectionReference`とほとんど同じです（ドキュメントによると`CollectionReference`は`Query`のサブクラスのようです）。そのため、例えば`CollectionReference`から絞り込んで`Query`を生成したように、`Query`型をさらに絞り込んで`Query`を生成することができます。以下の例では、`citiesSortedByPopulationRef`という`Query`を絞り込んで、`threeLargestCitiesRef`という別の`Query`を取得しています。

```
// 人口が多い順にソートしたcitiesへの参照
const citiesSortedByPopulationRef = citiesRef.orderBy('population', 'desc')

// もっとも人口が多い３つの都市への参照
const threeLargestCitiesRef = citiesSortedByPopulationRef.limit(3)
```

#### `DocumentReference`

ある一つのドキュメントの参照が`DocumentReference`です。ドキュメントが所属するコレクションを参照する`CollectionReference`の`doc`メソッドの引数にドキュメントのidを指定することで、以下のように生成できます。

```
const tokyoRef = citiesRef.doc('TOK')
```

### スナップショット（`QuerySnapshot`、`QueryDocumentSnapshot`、`DocumentSnapshot`）

ここまでは、あくまでデータへの"参照"を取得する話でした。firestoreに保存されているデータの内容を取得するには、"参照"をもとに、実際にデータを持っている"スナップショット"を得る必要があります。"スナップショット"について、以下で説明します。

#### `DocumentSnapshot`

`DocumentSnapshot`は、`DocumentReference`から得られるスナップショットで、単一のドキュメントのデータを持っています。

ここでもう一度、参照からスナップショットを得るための方法について確認しておきます。スナップショットを得るには参照に生えた`get()`メソッドを呼べば良いです。`get()`の返り値の型はスナップショットを返す`Promise`です。例えば、`DocumentReference`から`DocumentSnapshot`を得るには以下のようにします。

```
const tokyoRef = citiesRef.doc('TOK')
tokyoRef.get().then(docSnapshot => { 
  // docSnapshotはidが'TOK'であるドキュメントのデータをもつDocumentSnapshot
  // ここでdocSnapshotを使ってなんかやる
})
```

もちろん、`async/await`を使っても良いです。

```
(async () => {
  const tokyoRef = citiesRef.doc('TOK')
  const docSnapshot = await tokyoRef.get()
  // ここでquerySnapshotを使ってなんかやる
})()
```

さて、実際にfirestoreに保存されているデータを取得したいときは、上記の方法で生成した`DocumentSnapshot`の`data()`メソッドを呼びます。

```
const tokyoRef = citiesRef.doc('TOK')
tokyoRef.get().then(docSnapshot => {
  console.log(docSnapshot.data())
  // { capital: true,
  //   country: 'Japan',
  //   name: 'Tokyo',
  //   population: 9000000,
  //   state: null }
})
```

また、特定のフィールドの値だけが欲しい時は`data()`の代わりに`get()`メソッドを呼びます。

```
const tokyoRef = citiesRef.doc('TOK')
tokyoRef.get().then(docSnapshot => {
  console.log(docSnapshot.get('country'))
  // Japan
})
```

#### `QuerySnapshot`

`QuerySnapshot`は、`CollectionReference`と`Query`から得られるスナップショットです。`DocumentSnapshot`が単一のドキュメントのデータを持っていたのに対して、`QuerySnapshot`は複数のドキュメントのデータを持つスナップショットです。`Query`だけでなく`CollectionReference`からも`QuerySnapshot`が得られることに注意してください。

`QuerySnapshot`は、以下のように使います。`CollectionReference`である`citiesRef`からスナップショットを得ている例です。

```
citiesRef.get().then(querySnapshot => {
  // ここでquerySnapshotを使ってなんかやる
})
```

`Query`からも全く同じように`QuerySnapshot`が得られます。以下の例では、`querySnapshot`は`capital`が`true`である都市のみのデータを持っています。

```
const capitalsRef = citiesRef.where('capital', '==', true) // capitalsRefはQuery型
capitalsRef.get().then(querySnapshot => {
  // ここでquerySnapshotを使ってなんかやる
})
```

#### `QueryDocumentSnapshot`

複数のドキュメントのデータを持つ`QuerySnapshot`から実際にデータを取得する際には、一つ一つのドキュメントのデータを持つ`QueryDocumentSnapshot`に対して操作を行います。コレクションから一つ一つドキュメントを取り出して、何らかの操作を行うイメージです。具体的には、主に以下の２つの方法が取られます。

```
// 1. forEach
citiesRef.get().then(querySnapshot => {
  querySnapshot.forEach(queryDocSnapshot => {
    // ここでqueryDocSnapshotを使ってなんかやる
  })
})

// 2. docs
citiesRef.get().then(querySnapshot => {
  const queryDocsSnapshot = querySnapshot.docs
  // ここでqueryDocsSnapshotを使ってなんかやる
})
```

1の`queryDocSnapshot`は`QueryDocumentSnapshot`、2の`queryDocsSnapshot`は`QueryDocumentSnapshot`の配列になっています。`QueryDocumentSnapshot`はほとんど`DocumentSnapshot`と同じように使えます。例えば、コレクションを構成するドキュメントのデータの配列が欲しいときには、`data()`メソッドを使って以下のようにすれば良いです。

```
citiesRef.get().then(querySnapshot => {
  const cities = querySnapshot.docs.map(doc => doc.data())
})
```

また、`forEach`を使った例で、`capital`である都市について出力したい場合は、例えば以下のようになるでしょう。

```
citiesRef.get().then(querySnapshot => {
  querySnapshot.forEach(citySnapshot => {
    const city = citySnapshot.data()
    if (city.capital) {
      console.log(`${city.name} is capital!`)
    }
  })
})
// Beijing is capital!
// Washington, D.C. is capital!
// Tokyo is capital!
```

`QueryDocumentSnapshot`と`DocumentSnapshot`の違いについては明示的に意識しなくても良いと思いますが、例えば [Firestore - Difference between DocumentSnapshot and QueryDocumentSnapshot](https://stackoverflow.com/questions/49859954/firestore-difference-between-documentsnapshot-and-querydocumentsnapshot) を読むとわかると思います。

取得したデータの絞り込み、ソートについては以下の通り。

- where：特定のデータ指定
- orderBy：昇順、降順

```dart
Stream getStreamSnapshots(String collection) {
  return Firestore.instance
      .collection(collection)
      .where("title", isEqualTo: "test")
      .orderBy('createdAt', descending: true)
      .snapshots();
}
```

トランザクション処理もできるらしい。[ここ](https://qiita.com/subaru44k/items/a88e638333b8d5cc29f2)参照。

### CLI経由で大量のデータをFirestoreにぶっこむ

Typescriptで大量のデータを突っ込む方法は下記参照。上表のとおり、まだfirestoreのCLIはあまり無い模様。

[こちら](https://qiita.com/yukin01/items/9840405e0ec4898b861a)参照 



### CLI経由でデータを削除する

```
firebase firestore:delete [options] <<path>>
```

Collectionすべて削除する場合は、

```swift
firebase firestore:delete --all-collections
```

具体的には、自分のプロジェクトでは以下のようにして'books'というcollectionのなかのdocumentをすべて削除できた。

```
firebase firestore:delete books/ -r --project flutter-app-798c0
```



### Cloud Functions for Firebase

何ができるかは本家リファレンス参照。関数はJavaScriptまたはTypeScriptで書ける。

> Cloud Functions for Firebase を使用すると、Firebase 機能や HTTPS リクエストによってトリガーされたイベントに応じて、バックエンド コードを自動的に実行できます。コードは Google のクラウドに保存され、マネージド環境で実行されます。独自のサーバーを管理およびスケーリングする必要はありません。

何を基に発火するかは、以下のトリガーがある。

- [Cloud Firestore トリガー](https://firebase.google.com/docs/functions/firestore-events?authuser=0)
- [Realtime Database トリガー](https://firebase.google.com/docs/functions/database-events?authuser=0)
- [Remote Config トリガー](https://firebase.google.com/docs/functions/rc-events?authuser=0)
- [Firebase Test Lab トリガー](https://firebase.google.com/docs/functions/test-lab-events?authuser=0)
- [Firebase Authentication トリガー](https://firebase.google.com/docs/functions/auth-events?authuser=0)
- [Google アナリティクス トリガー](https://firebase.google.com/docs/functions/analytics-events?authuser=0)
- [Crashlytics トリガー](https://firebase.google.com/docs/functions/crashlytics-events?authuser=0)
- [Cloud Storage トリガー](https://firebase.google.com/docs/functions/gcp-storage-events?authuser=0)
- [Cloud Pub/Sub トリガー](https://firebase.google.com/docs/functions/pubsub-events?authuser=0)
- [HTTP トリガー](https://firebase.google.com/docs/functions/http-events?authuser=0)

>  [Admin SDK](https://firebase.google.com/docs/admin/setup?authuser=0) を Cloud Functions とともに使用して、Firebase のさまざまな機能を統合したり、独自の webhook を作成してサードパーティのサービスと統合したりすることができます。Cloud Functions はボイラープレート コードを最小限に抑え、ファンクション内で Firebase と Google Cloud を使いやすくします。

マネージドなので、関数を書くことにだけ集中すればよく、インフラのメンテはすべてGoogleがやってくれる。
また、Flutterクライアントアプリ側でTokenやServerキーなどを内包することはセキュリティ上のリスクがあるため、それらを含むロジックをよりセキュアなCloud Functionsに実装するという選択肢がある。

Firebase CLIによるCloud Functionsのローカルエミュレート

開始するには、

```
firebase emulators:start
```

特定のエミュレータだけ起動する場合は、

```
firebase emulators:start --only functions
```

エミュレータの起動後にテストスイートやテスト スクリプトを実行する場合は、

```
firebase emulators:exec "./my-test.sh"
```

エミュレータは関数からのログを実行中のターミナル ウィンドウにストリーミングします。関数内の `console.log()`、`console.info()`、`console.error()`、`console.warn()` のステートメントからの出力がすべて表示されます。



### Firebase Cloud Messaging

（要確認）たぶん無課金だと本機能は使えない。大まかな手順的には、

1. 通知許可の実装
2. 通知受信時の挙動の実装

といったところでしょうか。実際に通知を送る実装は、CloudFunctionsにでも実装し、上述したようにHTTPリクエストやFirestoreの特定のデータ更新等をトリガにPushを送る実装をすると考えておけばよいと思います。なぜCloudFunctionsに置くか、という点については、ServerKeyやToken等の秘匿情報の隠蔽、共通ロジックの追い出し等、幾つかの理由があるかと思います。[こちら](https://fireship.io/lessons/flutter-push-notifications-fcm-guide/)がよく纏まっています。

1、2ともにアプリ起動時等の初期段階で実行させたい処理であるため、initState()等に実装するのがよいのではと思います。以下に実装例を記します。

```dart
    // ここで通知許可の設定を行う
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(sound: true, badge: true, alert: true));
    _firebaseMessaging.onIosSettingsRegistered
        .listen((IosNotificationSettings settings) {
      print("Settings registered: $settings");
    });
    // ここで通知受信時の挙動を設定しています。
    _firebaseMessaging.configure(
        // アプリがフォアグラウンドにある際に呼ばれる
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        _buildDialog(context, "onMessage");
      },
        // 通知を押してアプリが起動する際に呼ばれる
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
        _buildDialog(context, "onLaunch");
      },
        // 通知を押してアプリが再開する際に呼ばれる
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
        _buildDialog(context, "onResume");
      },
    );
```

受信側がトピックをサブスクライブ（購読）して、送信側がトピックに対してPushするPub/Sub型もある。

```dart
void _onChanged1() {
    _firebaseMessaging.subscribeToTopic("/topics/gsacademy");
    _buildDialog(context, '通知の受信を開始します');
  }
```

以下は、クライアント側、サーバ側（CloudFunctionsにNode.jsで実装）のデータ授受の例。

▼クライアント側

```dart
    final HttpsCallable callable = CloudFunctions.instance
        .getHttpsCallable(functionName: 'sendMessage')
          ..timeout = const Duration(seconds: 30);
          
         final HttpsCallableResult result = await callable.call(
                <String, dynamic>{
                  'registerUser': book.data['registerUser'],
                  'displayName': displayName,
                  'bookTitle': book.data['title'],
                },
              );
```

▼サーバ側

```javascript
const functions = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

const db = admin.firestore();
const fcm = admin.messaging();

// sendMessageがクライアント側から呼び出す際の関数名になる
exports.sendMessage = functions.https.onCall(async (data, context) => {
  const registerUserId = data.registerUser;
  const registerUserTokenRef = await db
    .collection("users")
    .doc(registerUserId)
    .get();
  const registerUserToken = registerUserTokenRef.data()["token"];

  const sender = data.displayName;
  const bookTitle = data.bookTitle;

  console.log(
    "registeredUserId: " +
      registerUserId +
      " registerUserToken: " +
      registerUserToken +
      " sender: " +
      sender +
      " bookTitle: " +
      bookTitle
  );

  const payload = {
    notification: {
      title: "テスト",
      body: `${sender} が ${bookTitle} を…`,
      // icon: "your-icon-url",
      // click_action: "FLUTTER_NOTIFICATION_CLICK", // required only for onResume or onLaunch callbacks
    },
  };

  return fcm.sendToDevice(registerUserToken, payload);
});

```

CloudFunctionsへのデプロイは以下のコマンドで。

```
firebase deploy --only functions
```

Push通知メッセージを開いたあとにタスクを実行する方法は[こちら](https://ja.coder.work/so/android/875287)を参照ください。



## （たぶん）中・上級者向け

### Stateful WidgetのStateライフサイクル

[こちら](https://tech-rise.net/what-is-lifecycle-of-state/)がよく纏まっています。

Stateのライフサイクルは以下の10ステップに分かれています。

1. createState
2. mounted is true
3. initState
4. didChangeDependencies
5. build
6. didUpdateWidget
7. setState
8. deactivate
9. dispose
10. mounted is false

ここで絶対に覚えて置く必要があるのは、1, 3, 5, 7でしょうか。これが分かっていないと画面のレンダリングに掛かる処理をどこに書けば良いのか？となるかと思います。

initStateはレンダリング時に1回だけ呼ばれます。

また、2, 10にある**mounted**についても知っておくべきかと思います。[トラブルシューティング](#トラブルシューティング)にも書きましたが、StateオブジェクトがWidgetツリーに組み込まれたタイミングで何かしたい場合に必要になるので、覚えておくとよいでしょう。

4はInheritedWidgetのBuildContext.inheritFromawidgetOfExactTypeが実行された場合に呼ばれるとのこと。InheritedWidgetは下位Widgetから上位WidgetのオブジェクトをO(1)で参照できるものですが、その際にcontextにアクセスしようとすると、initState内からではタイミング的にcontextにアクセスできない（そうです）。その際にはdidChangeWidgetを使いましょう。

6は親Widgetが変更されて再ビルドされる場合に呼び出される（そうです。使ったことないので知らない…）。initStateで初期化したデータを再び初期化する場合に用いられる。例として挙げられているのは、Stateのbuild関数がStreamや他のオブジェクトに依存してた場合に古いオブジェクトの購読を解除して、新しいオブジェクトの購読をし直す場合。これは実装してみないと分かりづらいですね。改めて実装して確認したら掲載します。

### 状態管理

- 単純にWidget毎に個別に状態を持つこともできる

  - この場合は１Widget－1State。StateクラスのsetStateで管理する。

- 複数のWidgetで状態を共有するような場合、状態管理が必要になる

  - ここでよく聞く？ReduxやBLoCが出てくる
  - 例えば以下のような場面で必要
    - ユーザー設定
    - ログイン情報
    - SNSの通知
    - ショッピングカート
    - ニュースアプリの読んだ、読んでないの状態

- ここではプロバイダーパッケージに触れる

  - プロバイダーとはなにか

    [こちら](Flutterのpackage:providerを使ったBloc的アーキテクチャ全体像をサンプルで理解するまとめ - Qiita https://qiita.com/osamu1203/items/526a13d730500decf58c)参照

    - ビュー(UI)とロジックやステート(Model)を適切に分離する
    - 具体的にはコードを以下のように分離する
      - UI・・・Stateless Widgetを中心としたViewで構成されます。
      - Model・・・BlocやViewModelと呼ばれる層で、ロジックとステート（状態）を持ちます。
      - Repository・・・Modelが外部リソースとやりとりする、その接続点としてRepositoryをインターフェースとして扱います。
      - DAOやAPI・・・外部リソースへのアクセスを行います。（本アプリではAPIは扱っていません）
    - プロバイダーのポイントはざっくり以下の４つ
      - ChangeNotifierのModelを作って、ロジックと値（ステート）を作る
      - ChangeNotifierProviderで、ModelをWidgetで使えるように内包する
      - Provider.ofを使ってModelを呼び出し、値（ステート）をViewに使う
      - Model内のnotifyListeners()で値（ステート）の変更を通知する

  

- Android Studio では「Flutter Performance」ウィンドウで各Widgetがどれだけrebuildされているかがわかる。

  Flutter Performanceを使うときは、通常の実行ではなく、デバッグビルドで実行すること。

  Rebuild stats のWidget名の横に黄色の丸が表示されるものについては状態管理を見直したほうがよいかもしれない。これに対するリファクタリングに係る対策は２つ、

  1. 大きなビルドメソッドを複数のウィジェットに分割する

  2. 状態管理を見直す

     

  Flutter Performanceは以下のよくある４つのパフォーマンス問題を改善するのを手伝ってくれます。

  1. 画面全体（または画面の大部分）が単一のStatefulWidgetによって構築されるため、不要なUI構築が発生します。小さいbuild（）関数を使用して、UIを小さいウィジェットに分割します。 
  2. オフスクリーンウィジェットが再構築されています。これは、たとえば、ListViewが画面外に広がる高い列にネストされている場合に発生する可能性があります。または、画面外に拡張するリストにRepaintBoundaryが設定されておらず、リスト全体が再描画される場合。 
  3. AnimatedBuilderのbuild（）関数は、アニメーション化する必要のないサブツリーを描画し、静的オブジェクトの不要な再構築を引き起こします。 
  4. 不透明度ウィジェットがウィジェットツリーの不必要に高い位置に配置されています。または、不透明度アニメーションは、不透明度ウィジェットの不透明度プロパティを直接操作して作成され、ウィジェット自体とそのサブツリーが再構築されます。

  

#### Inherited Widget

FlutterのInherited Widgetクラスを継承したWidgetを作れば、State/状態の保持とウィジットツリー下流への伝播及び、State/状態の参照と更新も実現できる。

Inherited Widgetは簡単にいうと、Scaffold.of(context)だと子孫Widgetから目的Widgetまで線形に辿っていくところを、Inherited Widgetを実装していると、O(1)で一発で辿れるようになるから軽くて早いよ！というものだと認識している。

Inherited Widgetを使わない場合、ツリー上位のWidgetから下位Widgetに値を伝播する際に、いちいちコンストラクタで値をリレーしていく必要があり、めんどいうえに上位ツリーが伝播している値が変わると下位ツリーすべてにでんぱすることでRebuildが連発し、非常に非効率な実装となる。でも、以下のWidgetはRebuildされない。

- const を指定したWidget
- StateのフィールドにキャッシュされたWidget
- 上位ツリーからchild引数などで渡されて使い回されるWidget

ProviderモデルはこのInherited WidgetをWrapしたもので、より簡単にキレいにIhnerited Widgetを実装することができるもの。

[こちら](https://medium.com/flutter-jp/inherited-widget-37495200d965)に分かりやすい解説があります。

>  つまり、InheritedWidgetの変更伝播の仕組みは、上述のリビルド抑制の仕組みを何かしら使って無駄なリビルドを塞き止めた上で、さらに必要な状態の変更のみをバイパスして伝播してリビルドする、という二段構えのものなのです。



#### Scoped Model

Inherited Widgetを含有して拡張した外部パッケージのScoped Modelクラスを使えばStateの参照や更新とツリーの一部の効率的な再描画が実現できる。（Inherited Widgetではツリーの一部の効率的な再描画ができない）



## テスト

以下、まとめる予定

- https://qiita.com/chooyan_eng/items/700076224d1297de2d4b
- https://qiita.com/ko2ic/items/78c4a035a0aef9cfc78d



## リファクタリング

### Widgetの切り出し

階層が深くなったときには積極的にWidgetの切り出し（追い出し？）を行うべき。
Windowsであれば、Ctrl＋Shift＋Aで表示されるクイックアクションから「extract widget」でできます。

### Placeholder

レイアウトを組むときに、とりあえずスペースだけは確保しておきたいときに使うWidget。
あとでリファクタリング（実装？）すべし。



## Logging

Android Studioの場合、print() でLogcatに表示される。

きちんとLogとして出したい場合、以下のようにする。

```dart
import 'package:logging/logging.dart';

final log = new Logger('hoge');

main() {
  Logger.root.level = Level.All;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });
}
```

上記の例では'hoge'のところを別の文字列にすれば他のロガー
Logger.root.level`で、出力するログレベルを指定する。ログレベルは以下のとおり。

| ログレベル | 説明                           | Level.value |
| :--------- | :----------------------------- | :---------- |
| OFF        | 全てのログ無し                 | 2000        |
| SHOUT      | デバッグ時に目立つ騒がしいログ | 1200        |
| SEVERE     | 深刻な障害情報                 | 1000        |
| WARNING    | 問題の可能性があるログ         | 900         |
| INFO       | 情報提供                       | 800         |
| CONFIG     | コンフィグ設定のメッセージ     | 700         |
| FINE       | トレース用情報                 | 500         |
| FINER      | そこそこ詳細なトレース用情報   | 400         |
| FINEST     | かなり詳細なトレース用情報     | 300         |
| ALL        | すべてのログ                   | 0           |

そして、`Logger.root.onRecord.listen`によって、どのようにログ出力するかを書く。



## コーディングスタイルガイド

[ここ](https://sbfl.net/blog/2014/12/20/dart-style-guide/)見ましょう。好きずきはあるのかもしれませんが、ある程度、どの言語にも共通のガイドになっているかと思います。



## アプリアイコンの作成と設定

### 作成

取り敢えずなんでもよければ[Android Asset Studio]([Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/))でも如何でしょうか。

### 設定

Under Construction



## Android Studio

### ショートカット 

★は自分（と多くの人）にとって必須

| キー                     | 内容                                                         |
| ------------------------ | ------------------------------------------------------------ |
| ★stless                  | [Live Template] StatelessWidgetのテンプレートを作成してくれる |
| ★stful                   | [Live Template] StatefulWidgetのテンプレートを作成してくれる |
| ★stanim                  | [Live Template] AnimationControllerを含むテンプレートを作成してくれる |
| inh                      | [Live Template] InheritedWidgetのテンプレートを作成してくれる |
| Ctrl ＋ Space            | コード補完                                                   |
| ★Ctrl ＋ P               | メソッドのパラメータ情報の表示                               |
| Shift + F6               | 変数のリネーム。ファイル名変更も                             |
| ★Ctrl + B                | 定義箇所へ移動                                               |
| Alt + F7                 | 利用箇所検索                                                 |
| ★ALT + Enter             | 以下のようなときに便利（ほんの一部です）<br />・Widgetを削除したいとき<br />・Widgetを何かしら（パディングやマージンなど）でラップしたいとき<br />・child→childrenに変換したいとき<br />・Wigetを上下に動かしたいとき<br />・Stateless→Statefulに変換したいとき<br /> |
| ★Ctrl ＋ ｗ              | 選択範囲を徐々に広げる　※Shiftを押しながら実行すると選択範囲を縮小できる。 |
| Ctrl ＋ Q                | Wigetのプロパティ・定義を見る                                |
| ★Ctrl ＋ Shift ＋ I      | これもWidgetの定義を確認する                                 |
| Ctrl + Shift + F         | プロジェクト内で文字検索ができる                             |
| Ctrl + F10               | 実行                                                         |
| Ctrl + Alt + S           | 設定画面を表示する                                           |
| ★Ctrl + Shift + '+'      | コードを折りたたむ                                           |
| ★Ctrl + Shift + '-'      | コードを展開する                                             |
| Ctrl + '.'               | カーソル箇所のコードの折りたたみと展開のトグル               |
| ★Ctrl + fn ＋ [          | 対応する括弧に戻る                                           |
| ★Ctrl + fn ＋ ]          | 対応する括弧に進む                                           |
| ★Ctrl ＋ Y               | 行削除                                                       |
| Shift ＋ Alt ＋ ↑↓       | 行の入れ替え                                                 |
| ★Ctrl + /                | コメント／アンコメント                                       |
| F11                      | 行のブックマーク                                             |
| Ctrl + Shift + V         | コピー履歴から貼り付け                                       |
| ★Shift + Alt + Insert    | 矩形選択モード                                               |
| ★Ctrl + R                | 置換・・・現在のドキュメントが対象                           |
| Shift + F6               | 変数の名前変更・・・プロジェクト全体が対象                   |
| ★Ctrl + ↑↓               | カーソルを維持したまま上下にスクロール                       |
| Ctrl + Shift + BackSpace | 前の場所に戻る                                               |
| ★Shift + F10             | 実行                                                         |
| Ctrl + F10               | 変更を適用して実行 Instant Run                               |
| Ctrl + Shift + Z         | やり直し（REDO）                                             |

### Plugin

Floobits: Plugin Help: IntelliJ IDEA https://floobits.com/help/plugins/intellij



## トラブルシューティング

### 実装上のトラブル

- 遷移先の画面からすぐに遷移元の画面に戻った際にエラーになる

  これは[この](https://blog.mrym.tv/2019/12/traps-on-calling-setstate-inside-initstate/)ページでも報告されていて、解決策も解説されてますね。遷移先画面のStateオブジェクトのmountedプロパティをチェックすればそのWidgetがTree上に存在しているかわかるそうなので、以下のように書けば良いとのこと。

  ```dart
    Future<void> _loadData() async {
      final HogeData data = await fetchHogeData(widget._hogeId);
      
      if(mounted) { // ←これを追加！！
        setState(() => _hogeData = data); 
      }
    }
    @override  Widget build(BuildContext context) {
      // 省略
    }
  }
  ```

  

### IDEのトラブル（Android Studio）

- 以下のエラーが出る

  ```
   ADB server didn't ACK
  * failed to start daemon *
  ```

  - タスクマネージャからadb.exeを終了させ、Android Studio再起動

  

- エミュレータや実機にインストールする前に、一度、アンインストールしてからインストールしたい

  以下の実行構成の該当するエントリポイントの「Before launch: Gradle task Activate tool window」→「Tasks」に「:app:uninstallDebug」と記載

- Flutter Outlineを開いても「Nothing to show」としか表示されない

  - 左下の（Dart Anlysis）の回転している矢印マークをクリックする

  

## 参考

- Flutter入門のためのDart入門 - Qiita https://qiita.com/teradonburi/items/913fb8c311b9f2bdb1dd#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB
- Dartの変数定義時の修飾static/final/const、そしてconst constructorについて - Qiita https://qiita.com/uehaj/items/7c07f019e05a743d1022
- 個人的にAndroidStudioでよく使うショートカット(Windows編) - Qiita https://qiita.com/takke/items/5cbc629f7f65d6a49906



##  後で要整理

### Widgetのキャッシュ

Widgetをメンバ変数として定義する。

### Widgetのconst指定によるパフォーマンス向上

constを指定してWidgetを定義することでコンパイル時定数としてパフォーマンスを向上させることができる。また、呼び出し時にconstをつけて呼び出すことで再インスタンス化を防ぐことができる。



### ツリー間の値の受け渡し

GlobalKeyを使うことで上位→下位の参照が可能

使い方は、

```dart
GlobalKey<参照したい下位クラス> sampleGlobalKey = GlobalKey();
```

次に参照したい下位クラスにsampleGlobalKeyをコンストラクタで渡す。最後に、GlobalKeyを作成したクラスにて参照したい下位クラスの値を参照する。



逆方向の参照（下位クラスが上位クラスのオブジェクトを参照したい場合）は、ancestorWidgetOfExactTypeを使う。

例えば、MyWidgetの下にAnotherWidgetがあるような構造の場合、AnotherWidgetのbuildメソッド内で、以下のように記述する。

```
final MyWidget myWidget = context.ancestorWidgetOfExactType(MyWidget);
```

StatefulWidgetではなく、Stateインスタンスにアクセスしたい場合、そのStateインスタンスをgetterで公開しておく必要がある。

ただし、GlobalKeyやancestorWidgetOfExactTypeの方法では、直線的にすべてのツリーを探索していくことになりパフォーマンスが悪い→InheritedWidget

具体的にはinheritFromWidgetOfExactTypeを使うことでO(n)コストがO(1)になる。これで下位ツリーから上位ツリーへの参照がO(1)で可能になり、かつ、上位→下位の特定Widgetへの変更通知が可能となる。

InheritedWidgetの実装時に注意する点は以下３つ。

- 変更通知したいデータ（ステート）の決定
- 上位ツリーのBuildContextを受け取るstaticメソッドの実装
- 変更通知の条件定義

Inheritedクラスではコンストラクタで状態となるデータの受け渡しをしています。これは上位ツリーのステートが持つ値への参照とその変更を監視するために定義しています。



また、Inheritedクラスでは観葉的にstaticのofメソッドを実装します。上位ツリーのBuildContextを受け取り、取りたいデータをO(1)で探索するために必要です。第２引数のbool値であるobserveは、データ変更を監視する必要があるかどうかを外部から指定可能にしています。

変更が必要な場合は、context.inheritFromWidgetOfExactType(Inherited)を呼び出し、不要な場合には、context.ancestorInheritedElementForWidgetOfExactType(Inherited).widgetを呼び出します。



### ScopedModel

```sequence
Title: Scoped Modelにおける各クラスのやり取り
User -> View: input
View -> Scoped Model: Notify input
Scoped Model -> Model: Create a new state
Model -> Scoped Model: Notify the new state
Scoped Model -> View: Create a new UI
```

基本はModelが管理する状態をViewに反映すること。Scoped ModelではScopedModelとScopedModelDescendantの２つのクラスを使ってModelの状態をViewに反映する。

```sequence
Title: ScopedModelライブラリにおける各クラスのやり取り
Widget -> ScopedModel: Notify the user input
ScopedModel -> Model: Create a new state
Model -> ScopedModelDescendant: Notify the new state
ScopedModelDescendant -> Widget: create a new UI
```

ScopedModelDescendantは状態をWidgetに変換する役割がある。

ScopedModelでは、以下の２つの役割を担う。

1. Viewからの入力をModelに伝達する（左から右の流れ）
2. 状態をWidgetに変換する（右から左の流れ）



### Redux

比較的大規模なアプリケーションにも対応できるアーキテクチャパターン。Facebookが提唱するFluxを更に発展させたもの。思想としては、GUIアプリの開発で複雑になりがちなデータの流れを１方向にするというもの。

```sequence
View -> Action:
Action -> Dispatcher: 
Dispatcher -> Store:
Store -> View:
```

Actionクラスはユーザの行動によって生成されるクラス。どのページを閲覧したか、どのボタンをクリックしたかなどの情報を持つ。Dispatcherクラスは受け取ったActionを状態を管理するStoreクラスに渡す。Dispatcherはアプリ内でユニークな存在として作成する。Actionを受け取ったStoreはActionの内容から新たな状態であるStateを生成する。
ViewはStoreから受け取ったStateからViewを構築する。

```sequence
View -> Action:
Action -> Reducer:
Reducer -> Store:
Store -> View:
```

FluxではDispatcherにActionを渡していたが、ReduxではReducerに渡す。ReducerはActionの内容と現在の状態を読み取り、新しい状態を作成する役割を担う。StoreはReducerによって変化する状態を保持する。ReduxではStoreがアプリ内でユニークになる。つまり、どの画面に遷移しても一元的に同じStoreを参照する。

### BLoC

Precentation ComponentはWidgetなどGUIを扱う部分のこと。Business Logic Componentが状態を管理する部分。BackendはWebAPIなどの通信先を表している。

```sequence
Presentation Component -> Business Logic Component: Send event
Business Logic Component -> Backend: Request
Backend -> Business Logic Component: Result of request
Business Logic Component -> Presentation Component: Notify a new state
```

Presentation Componentは入力情報を持つEventオブジェクトをBusiness Logic Componentに送る。Business Logic Componentは受け取ったEventの内容を判断する。Eventを判断したら内容に応じて必要な処理を実行して、自らが持つプロパティを変更する。

### アプリ内広告
<AdMobについて掲載予定>


### アプリ内課金

アプリ内課金の定期購入（サブスクリプション）をFlutterとFirebaseで実装するときのポイント - Studyplus Engineering Blog https://tech.studyplus.co.jp/entry/2020/04/13/102204