Flutterで "名前付きコンストラクタ" と "リダイレクトコンストラクタ" でウィジェットを作る例

Dartには色々なコンストラクタがありますが、あまりFlutterで使いこなせていない感がありました。

同僚と話していて、こういった使い方なら汎用性があるのでは？というのがあったので、「[名前付きコンストラクタ](https://dart.dev/codelabs/dart-cheatsheet#named-constructors)」と「[リダイレクトコンストラクタ](https://dart.dev/codelabs/dart-cheatsheet#redirecting-constructors)」を使って説明したいと思います。

## 完成のイメージ
 - メンバー一覧用のカードを用意したい
 - 退会したメンバーは背景をグレーにして、「退会済み」の文言を表示したい

<img width="248" alt="スクリーンショット 2019-11-23 17.33.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/2b34916a-ca09-d233-a3b8-c033c8c413c7.png">

## 元となるカードのウィジェットを作成する

メンバー個々のウィジェットを作成する際に下記のデータを送ってもらうこととします。
 - メンバーの名前
 - アバター画像のファイル名
 - 在籍状況
 - 背景色

ウィジェットのコードはこんな感じになると思います。

```dart:main.dart

Widget build(BuildContext context) {
  return Card(
    child: ListTile(
      //アバター画像
      leading: ClipRRect(
        borderRadius: BorderRadius.circular(25),
        child: Image.asset(
          'images/${widget.imgFileName}',
          width: 
          height: 50,
        ),
      ),
      title: Text(widget.name), //名前
      trailing: widget.statusIcon, //在籍状況
    ),
    color: widget.backgroundColor, //背景色
    margin: EdgeInsets.symmetric(vertical: 20),
  );
}
```

## 名前付きコンストラクタ で呼び出す

通常のメンバーカードである`normal`と、退会済みのメンバーカードの`disabled`をそれぞれ指定することでカードの作り分けを行いたいので、コードは以下のようにしたいです。

```dart:main.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'),
  ]
)
```

この`normal`と、`disabled`の部分が名前付きコンストラクタになります。

## 名前付きコンストラクタ を作成する

実際のコンストラクタは以下のようになります。

```dart:main.dart
class MemberCard extends StatefulWidget {
  final String name; //名前
  final String imgFileName; //アバター画像

  // 通常のユーザー
  MemberCard.normal({name, imgFileName})
      : this._init(
        name: name,
        imgFileName: imgFileName
      );

  // 退会したユーザー
  MemberCard.disabled({name, imgFileName})
      : this._init(
        name: name,
        imgFileName: imgFileName
  );

  MemberCard._init({Key key,
    this.name = '',
    this.imgFileName = ''
  }) : super(key: key);

  @override
  _MemberCardState createState() => _MemberCardState();
}
```

コンストラクタの引数は宣言時と同じものです。
その後「：」（コロン）をつけて、`this._init`に追加のコンストラクタ処理を渡しています。
これが、リダイレクトコンストラクタになります。 

実際の`this._init`を見てみます。
`key`は自動的に振り当てられたもので、`name`と`imgFileName`は引数として渡されたものです。

そしてまたそれを親クラスに`super`として処理を渡しています。

しかし、これだけでは`normal`と`disabled`の違いがなく、あまり恩恵を感じられませんので、ひと手間加えてみます。

## 明確に目的を分けて作成する

```dart:main.dart
class MemberCard extends StatefulWidget {
  final String name; //名前
  final String imgFileName; //アバター画像
  final Color backgroundColor; //背景色(追加部分)
  final statusIcon; //メンバーのステータス(追加部分)

  // 通常のユーザー
  MemberCard.normal({name, imgFileName})
      : this._init(
        name: name,
        imgFileName: imgFileName,
        statusIcon: Icon(Icons.check_circle, color: Colors.green,) //(追加部分)
      );

  // 退会したユーザー
  MemberCard.disabled({name, imgFileName})
      : this._init(
      name: name,
      imgFileName: imgFileName,
      backgroundColor: Colors.black12, //(追加部分)
      statusIcon: Text('退会済み') //(追加部分)
  );

  MemberCard._init({Key key,
    this.name = '',
    this.imgFileName = '',
    this.backgroundColor = Colors.white, //(追加部分)
    this.statusIcon = null //(追加部分)
  }) : super(key: key);

  @override
  _MemberCardState createState() => _MemberCardState();
}
```

「`//(追加部分)`」とコメントしている部分が追加したコードの差分です。
ポイントとなる部分を説明します。

#### backgroundColor
新しいインスタンス変数として`backgroundColor`を追加しました。
それぞれのコンストラクタで初期化されていたり、されていなかたりします。

 - `normal` : 初期化して**いない**
 - `disabled` : 初期化して**いる**
 - `init` : 初期化して**いる**

つまり、`normal`で初期化していない`backgroundColor`を`init`で初期化していることになります。


#### statusIcon
インスタンス変数`statusIcon`には型が付いておらず、各コンストラクタでは、下記のように初期化しております。

 - `normal` : `Icon()`
 - `disabled` : `Text()`
 - `init` : `null`

あまり綺麗なやり方ではないと思いますが、型を指定しないことによって自由に初期化にウィジェットを当てることができます。

プログラム全文

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Constructor Demo',
      home: Scaffold(
          appBar: AppBar(
            title: Text('メンバーリスト'),
          ),
          body: 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'),
            ],
          )

      ),
    );
  }
}

class MemberCard extends StatefulWidget {
  final String name; //名前
  final String imgFileName; //アバター画像
  final Color backgroundColor; //背景色
  final statusIcon; //メンバーのステータス

  // 通常のユーザー
  MemberCard.normal({name, imgFileName})
      : this._init(
        name: name,
        imgFileName: imgFileName,
        statusIcon: Icon(Icons.check_circle, color: Colors.green,)
      );

  // 退会したユーザー
  MemberCard.disabled({name, imgFileName})
      : this._init(
      name: name,
      imgFileName: imgFileName,
      backgroundColor: Colors.black12,
      statusIcon: Text('退会済み')
  );

  MemberCard._init({Key key,
    this.name = '',
    this.imgFileName = '',
    this.backgroundColor = Colors.white,
    this.statusIcon = null
  }) : super(key: key);

  @override
  _MemberCardState createState() => _MemberCardState();
}

class _MemberCardState extends State<MemberCard> {
  @override
  Widget build(BuildContext context) {
    return Card(
      color: widget.backgroundColor,
      margin: EdgeInsets.symmetric(vertical: 20),
      child: ListTile(
        leading: ClipRRect(
          borderRadius: BorderRadius.circular(25),
          child: Image.asset(
            'images/${widget.imgFileName}',
            width: 50,
            height: 50,
          ),
        ),
        title: Text(widget.name),
        trailing: widget.statusIcon,
      ),
    );
  }
}
```

プログラム全文（github）
https://github.com/naoaki-kaito/flutter_constructor_example