Flutter でよく使う（と思う）コードまとめ

最近 Flutter アプリをいくつか仕事で作っていて、よく使う Widget や API がまとまってきたので書いてみます。

調べればすぐ出てくるような内容ではありますが、「これから Flutter アプリ作ってみたいけど何をどう使ったら何ができるんだろう」、とフワフワしている方へ、「こんな時これが使えるよ」をいくつかまとめて紹介できればと思います。

このページをとっかかりとして、載せているリンク先で理解を深めつつ使ってみる、という流れを想定しています。（くれぐれもこのページの内容をそのままコピペして満足しないように！）

なお、全て `package:flutter/material.dart` を使っている場合の話ですので、 `widgets` や `cupertino` では話が違う場合もあるかもしれません。

では、どうぞ。

## 画面遷移

### 次の画面に遷移する

```dart
Navigator.push(context, MaterialPageRoute(builder: (context) => NextPage()));
```

### 前の画面に戻る、ダイアログを閉じる

```dart
Navigator.pop();
```

### 次の画面に遷移し、元の画面は終了する（バックキーで戻れなくする）

```dart
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => NextPage()));
```

詳しくは以下のドキュメントを参照してください。

[Navigate to a new screen and back - Flutter](https://flutter.dev/docs/cookbook/navigation/navigation-basics)

## リスト操作系

Flutter というよりは Dart の内容ですが、 `Row` や `Column` の `children` を組み立てる時に良く使うので、書いておきます。

### データの配列から Widget の配列を作る

```dart

["Azure", "AWS", "GCP"].map((cloudName) => Text(cloudName)).toList();
// -> [Text("Azure"), Text("AWS"), Text("GCP")]
```

もしくは

```dart

["Azure", "AWS", "GCP"].map((cloudName) { return Text(cloudName); }).toList();
// -> [Text("Azure"), Text("AWS"), Text("GCP")]
```

↑ を `Column` の `children` なんかで使う場合、作った3つの `Text` 以外にも `children` に含めたい場合がありますが、
Dart には Ruby でいう `flatten` がないため、カスケード呼び出しを使って以下のように書いています。（他にもっと良い書き方があったら教えてください）

```dart
Column(
    children: [
        SomeWidget(),
        OtherWidget(),
    ]..addAll(
        ["Azure", "AWS", "GCP"].map((cloudName) { return Text(cloudName); }).toList();
    )
)
```

### 絞り込み

```dart
["Azure", "AWS", "GCP"].where((cloudName) => cloudName.startsWith("A"));
// -> ["Azure", "AWS"]
```

Dart のリスト操作は以下のブログが参考になりました。

[Top 10 Array utility methods you should know (Dart) - Jermaine Oppong
](https://codeburst.io/top-10-array-utility-methods-you-should-know-dart-feb2648ee3a2)

## イベント

### Widget にクリックイベントをつける（Ripple あり）

Widget のタップ時に Ripple エフェクトをつけるには、 [InkWell](https://api.flutter.dev/flutter/material/InkWell-class.html) が便利です。

```dart
InkWell(
    onTap: () {
        // 何か処理
    },
    child: SomeWidget()
}
```

Android ネイティブの Ripple エフェクトの面倒臭さを考えると信じられない手軽さですね。

ただ `onTap: ` に `(){}` で処理を書いてしまうのはもしかしたら Flutter 的ではないかもしれないので、以下のように書くと画面と処理を分離できて良いと思います。

```dart
InkWell(
    onTap: _doSomething,
    child: SomeWidget()
}
```

注意点として、 `InkWell` の親 Widget に背景色がついてしまっていると Ripple してくれないようなので、 `InkWell` を `Material` で包み、 `type: MaterialType.transparency` をつけてあげる必要があったりします。

```dart
Container(
    color: Colors.blue.shade50,
    child: Material(
        type: MaterialType.transparency,
        child: InkWell(
            child: Text("テキストテキスト"),
            onTap: () {},
        ),
    ),
),
```

と書いて改めて調べてみたら、他にもいくつか方法はありそうですので、参考になりそうなリンクだけ貼っておきます。

[InkWell not showing ripple effect - stackoverflow](https://stackoverflow.com/questions/45424621/inkwell-not-showing-ripple-effect)

### Widget にクリックイベントをつける（Ripple なし）

```dart
GestureDetector(
    onTap: _doSomething,
    child: SomeWidget()
}
```

[GestureDetector](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) には `onForcePressStart` や `onPanStart` など、 `InkWell` にはないイベントに反応するためのパラメータが用意されているため、より細かくイベントをとりわけたい場合はこちらを使います。

### 前のページに戻るイベントをキャッチする

ナビゲーションバーの戻るアイコンタップや、 Android のバックキー押下による「前のページに戻る」イベントをキャッチしたい場合、 [WillPopScope](https://api.flutter.dev/flutter/widgets/WillPopScope-class.html) で `Scaffold` を囲ってあげます。

```dart

WillPopScope(
    onWillPop: () async {
        showDialog(
            context: context,
            builder: (buildContext) {
                return SimpleMessageDialog( // これはメッセージを出すための自作クラス（詳細は割愛）
                    message: "前の画面に戻りますか？",
                    onPositiveTapped: () { Navigator.pop(context); },
                );
            },
        );
        return false; // return false することで自動的に戻る処理を無効にする
    },
    child: Scaffold(
        body: BodyWidget(),
    ),
),
```

↑の例のように、戻る前に確認ダイアログを入れたい、データを保存しておきたい、などの場合、これが使えます。

## アニメーション

### AnimatedContainer

幅や高さ、色、その他装飾系の値を段階的に変化させてアニメーションさせたい場合、 [AnimatedContainer](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html) が使えます。

```dart
AnimatedContainer(
    height: _showAll ? 300 : 60,
    duration: Duration(milliseconds: 100),
        child: Text("テキストを${_showAll ? '全部' : '一部'}表示しています"),
    ),
),
```

上の例の場合、通常の `Container` を使うと、 `setState()` で `_showAll` の値を切り替えるたびに高さが一瞬で切り替わりますが、 `AnimatedContainer` を使うと `Duration` で指定した時間を使って少しずつ高さが変わります。（つまりアニメーションしている感じになります）

### AnimationController

`AnimatedContainer` は `Container` に設定できる値の範囲でアニメーションが可能ですが（それでも十分だったりしますが）、より様々な Widget にアニメーションを適用したい場合は [AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html) が使えます。

```dart
var animationController = AnimationController(duration: const Duration(milliseconds: 100), vsync: this);
var animation = Tween<double>(begin: 0, end: 100).animate(_animationController)
    ..addListener(() {
        // アニメーション中、この関数が連続して呼ばれる
        setState(() {
            _iconSize = _animation.value; // _animation.value が 0 から 100 まで増える
        });
    });
animationController.forward();
```

これは Widget を直接変化させるのではなく、指定した値 (begin, end) の範囲が指定した時間 (duration) で変化し続けるので、その値を Widget の好きな場所に使ってあげる、というものです。

Widget そのものを変化させるのではなく、あくまで変化するのは数値のみで、それをどう使うかはこちら次第、というのが柔軟で分かりやすくて良いですね。

アニメーションについての詳細は以下の公式ドキュメントを参照してください。

[Animations tutorial - Flutter](https://flutter.dev/docs/development/ui/animations/tutorial)

## レイアウト

### マージンを入れる

```dart
SomeWidget(),
SizedBox(height: 16),
OtherWidget(),
```

Flutter の Widget にはマージンを設定するパラメータが用意されていないため、「 Widget と Widget の間」を表したい場合は [SizedBox](https://api.flutter.dev/flutter/widgets/SizedBox-class.html) を利用するのが便利です。（ youtube の [The Boring Flutter Development Show](https://www.youtube.com/playlist?list=PLOU2XLYxmsIK0r_D-zWcmJ1plIcDNnRkK) でちらっと使っていたのですが、ちょっとどの回か忘れてしまいました、、、）

「 Widget の内側の余白」を表したい場合は用意されている `padding` パラメータを使いましょう。

### 横幅いっぱいになったら折り返す

複数の Widget を横に並べていき、端まできたら改行するようなレイアウト（CSSでいう `Flexbox` のような）を作りたい場合は、 `Row` ではなく [Wrap](https://api.flutter.dev/flutter/widgets/Wrap-class.html) が使えます。

```dart

Wrap(
    spacing: 8.0, // 横のマージン
    runSpacing: 8.0,  // 縦のマージン
    children: <Widget>[
        Text("#タピオカ"),
        Text("#今日もタピオカ"),
        Text("#映える"),
        Text("#渋谷で"),
        Text("#大切な仲間と"),
        Text("#タピオカ"),
        Text("#タピオカ"),
    ]
)
```

## 通信系

### HTTP POST

```dart

var response = await http.post(
    "https://www.example.com/path/to/api",
    body: {
        "nickname": "chooyan",
        "description": "A freelance mobile app developer",
    },
);
```

[http](https://pub.dev/packages/http) プラグインを入れる必要があるので `pubspec.yaml` に書いておく。

### multipart/form-data による画像アップロード

画像に限らず、ファイルアップロードには [http.MultipartRequest](https://pub.dev/documentation/http/latest/http/MultipartRequest-class.html) が使えます。

```dart
var uri = Uri.parse("https://www.example.com/path/to/upload/api");

var request = http.MultipartRequest("POST", uri);
request.fields["id"] = loginId;

var stream = http.ByteStream.fromBytes(imageData);
var multipartFile = http.MultipartFile("image", stream, imageData.length, contentType: MediaType("multipart", "form-data"), filename: "uploadfile.png");
request.files.add(multipartFile);

var response = await request.send();
```

## Widget 系

### スクロールできるようにする

画面からはみ出るくらい長い Widget をスクロールできるようにしたい場合、 [SingleChildScrollView](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html) で囲ってあげるのが一番カンタンです。

```dart

SingleChildScrollView(
    child: Column(
        children: <Widget>[
            Some(),
            Long(),
            Contents(),
        ]
    )
)
```

たまに `itemCount` が 1 の `ListView` を使っている例を見かけますが、本来の使い方とは違うためこちらを使うのが良いかと思います。

### 丸画像

```dart

Container(
    width: 120,  
    height: 120,
    decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(
            fit: BoxFit.fill,
            image: FileImage(imageFile), // 場合に応じて NetworkImage などに変更
        ),
    ),
);
```

### まだ開発してないけどここに何か入るよ

開発前だけど、枠だけ決まっている場合は [Placeholder](https://api.flutter.dev/flutter/widgets/Placeholder-class.html) が便利です。

```dart

Container(
    height: 300,
    child: Placeholder(),
)
```

枠を確保できるだけでなく、一目で「開発中」であることが分かるようになったり、「あ、デザインには書いてあるこの Widget 入れ忘れてるじゃん」な事故がなくなったりします。

## よく見る資料

### The Boring Flutter Development Show

https://www.youtube.com/playlist?list=PLOU2XLYxmsIK0r_D-zWcmJ1plIcDNnRkK

Google の開発者がノーカットで Flutter でアプリを作る様を眺める動画です。
若干英語が分からない感はあるものの、コードを見ているだけでも「あ、こんなのあるんだ、調べてみよう」が出てくるので、休憩がてら見てみても効果があると思います。

ちゃんと聞いているとそれはそれで「なるほど、こんなこと考えながらコード書いていくのか」を観察できるので、そちらもオススメです。

### Flutter 公式サイト

https://flutter.dev/

なんのひねりもなく公式サイトです。 Flutter はかなりドキュメントが整っていて、且つ読みやすく書かれていますので、ここを読み物として読んでいるだけでだいぶいろいろ勉強になります。チュートリアルや API リファレンスもこの中です。

ただし日本語訳が皆無なので、若干の英語に対する耐性は必要です。

### A curated list of samples

https://github.com/flutter/samples/blob/master/INDEX.md

実際に Flutter で作られたアプリのソースコードがまとまっています。

中途半端なアプリもありますが、それでも `lib` 下のフォルダ構成をどう作るか、どんな Widget をどんなところで使っているのか、など参考になる部分は多々あります。

---

以上、他にも何かあれば随時追記します。
みなさまからもコメントで「こんなのよく使うよ」を教えていただけるとありがたいです。

---

### 宣伝

フリーランスでいただいた仕事として、先日 __AppBox__ というアプリを Flutter で開発してリリースしました。

__Android__
https://play.google.com/store/apps/details?id=jp.co.oakhouse.appbox&hl=ja

__iOS__
https://apps.apple.com/us/app/oak-appbox/id1477787005?l=ja

オークハウスが管理するシェアハウスの住人向けのアプリなので利用シーンは限られますが、 Flutter でこんなのサクッと作れるよ！というイメージとして活用していただけたら嬉しいです。