Built_Valueのすすめ (コードの生成)

この記事は[こちら](https://stacksecrets.com/flutter/how-to-use-built_value-library)の和訳です。Flutterを触り始めて理解があやしかったBuild_Valueライブラリについて、自分の勉強用にまとめます。

## イントロ
[この記事](https://qiita.com/stoneshower/items/8390ccb3befd4f7eb156)でBuilt_Valueがなぜ重要なのかを確認した。今回はBuilt_Valueライブラリのセットアップ方法をみていく。

### Built_Valueのセットアップ手順
1. 必要なdependencyをインポートする
2. モデルに対するbuilt valueクラスを作る
3. build runnerでコードを自動生成する

## 1. 必要なdependencyをインポートする
メインのライブラリは[built_value](https://pub.dev/packages/built_value)だが、その他に2つのライブラリも使用する – [build_runner](https://pub.dev/packages/build_runner)と[built_value_generator](https://pub.dev/packages/built_value_generator)

```yaml
// pubspec.yaml
// ライブラリのバージョンを元記事から変更しています

dependencies:
  flutter:
    sdk: flutter
  built_value: ^6.8.2

　cupertino_icons: ^0.1.3

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.8.1
  built_value_generator: ^6.8.2
```

## 2. モデルに対するbuilt valueクラスを作る
デモとして`Contact`モデルを作成する。
参考までに、built valueを使用しない場合はDartのクラスは下記のようになる。

```dart

class Contact {
  int id;
  String fullName
  int age;
  String mobile;
  bool isFriend;
}
```

今回はbuilt valueを使うので、下記のような書き方になる。

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

part 'contact.g.dart';

abstract class Contact implements Built<Contact, ContactBuilder> {
  Contact._();
  factory Contact([updates(ContactBuilder b)]) = _$Contact;

  int get id;
  
  String get fullName;
  
  @nullable
  int get age;

  @nullable
  bool get isFriend;
}
```

おそらく初めてこれをみると、かなり複雑にみえるかもしれない。
後ほど解説はするが、一旦コードの自動生成をしてみよう。

## 3. build runnerでコードを自動生成する

ターミナルで次のコマンドを叩いてみよう。

```sh
flutter packages pub run build_runner build
```

すると、`contact.g.dart`が下記のコードで生成される。

```dart

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'contact.dart';

// **************************************************************************
// BuiltValueGenerator
// **************************************************************************

class _$Contact extends Contact {
  @override
  final int id;
  @override
  final String fullname;
  @override
  final int age;
  @override
  final String mobile;
  @override
  final bool isFriend;

  factory _$Contact([void Function(ContactBuilder) updates]) =>
      (new ContactBuilder()..update(updates)).build();

  _$Contact._({this.id, this.fullname, this.age, this.mobile, this.isFriend})
      : super._() {
    if (id == null) {
      throw new BuiltValueNullFieldError('Contact', 'id');
    }
    if (fullname == null) {
      throw new BuiltValueNullFieldError('Contact', 'fullname');
    }
  }

  @override
  Contact rebuild(void Function(ContactBuilder) updates) =>
      (toBuilder()..update(updates)).build();

  @override
  ContactBuilder toBuilder() => new ContactBuilder()..replace(this);

  @override
  bool operator ==(Object other) {
    if (identical(other, this)) return true;
    return other is Contact &&
        id == other.id &&
        fullname == other.fullname &&
        age == other.age &&
        mobile == other.mobile &&
        isFriend == other.isFriend;
  }

  @override
  int get hashCode {
    return $jf($jc(
        $jc($jc($jc($jc(0, id.hashCode), fullname.hashCode), age.hashCode),
            mobile.hashCode),
        isFriend.hashCode));
  }

  @override
  String toString() {
    return (newBuiltValueToStringHelper('Contact')
          ..add('id', id)
          ..add('fullname', fullname)
          ..add('age', age)
          ..add('mobile', mobile)
          ..add('isFriend', isFriend))
        .toString();
  }
}

class ContactBuilder implements Builder<Contact, ContactBuilder> {
  _$Contact _$v;

  int _id;
  int get id => _$this._id;
  set id(int id) => _$this._id = id;

  String _fullname;
  String get fullname => _$this._fullname;
  set fullname(String fullname) => _$this._fullname = fullname;

  int _age;
  int get age => _$this._age;
  set age(int age) => _$this._age = age;

  String _mobile;
  String get mobile => _$this._mobile;
  set mobile(String mobile) => _$this._mobile = mobile;

  bool _isFriend;
  bool get isFriend => _$this._isFriend;
  set isFriend(bool isFriend) => _$this._isFriend = isFriend;

  ContactBuilder();

  ContactBuilder get _$this {
    if (_$v != null) {
      _id = _$v.id;
      _fullname = _$v.fullname;
      _age = _$v.age;
      _mobile = _$v.mobile;
      _isFriend = _$v.isFriend;
      _$v = null;
    }
    return this;
  }

  @override
  void replace(Contact other) {
    if (other == null) {
      throw new ArgumentError.notNull('other');
    }
    _$v = other as _$Contact;
  }

  @override
  void update(void Function(ContactBuilder) updates) {
    if (updates != null) updates(this);
  }

  @override
  _$Contact build() {
    final _$result = _$v ??
        new _$Contact._(
            id: id,
            fullname: fullname,
            age: age,
            mobile: mobile,
            isFriend: isFriend);
    replace(_$result);
    return _$result;
  }
}

// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new
```

コードがかなり長いが、よくみると2つのクラスが生成されている。

```dart

part of 'contact.dart';

class _$Contact extends Contact {...}

class ContactBuilder implements Builder<Contact, ContactBuilder> {...}
```

## Built Valueクラスの深堀り

次に生成前のコードをよく見てみよう。

Built Valueモデルは抽象クラス

```dart
abstract class Contact
```

さらにabstract generic Built classをimplementする必要がある（日本語訳だと逆にわからなくなりそうだったので、まま表記）

```dart
abstract class Contact implements Built
```

Builtクラスの最初の型は抽象クラスと同じ名前にする。二番目の型は自動で生成されるBuilderの型とする（`xxxBuilder`ときまっている）

```dart
abstract class Contact implements Built<Contact, ContactBuilder> {
```

partファイル名はモデルファイルと一致させる必要がある
_contact.dart == contact.g.dart_

```dart
part 'contact.g.dart';
```

built valueクラスはプライベートのデフォルトコンストラクタを持たなければならない

```dart
Contact._();
```

各々のプロパティはgetter (get)タイプでないといけない。built valueはimmutableな型であるため。

```dart
int get id;

String get fullName;

@nullable
int get age;

@nullable
String get mobile;

@nullable
bool get isFriend;
```

`@nullable`は明示的に定義する必要がある。pre-definedかどうかのチェックのために。

次のfactoryメソッドはbuilt valueクラスのプロパティを初期化している。

```dart
factory Contact([updates(ContactBuilder b)]) = _$Contact;
```

ちなみに↓のようにも書くことは可能

```dart
factory Contact (id, fullName) => new _$Contact._(
    id: id,
    fullName: fullName
  )
//This forces all fields to be repeated on the constructor.
//It also requires every fields to be passed at once.
//However, with a builder pattern we are not restricted!
```

### インスタンスを作成

built valueはbuilder methodを使って通常のクラスのように初期化することができる。

生成された`contact`オブジェクトはimmutable built valueなので意図しない変更が発生しない。


```dart
Contact contact1 = Contact((b) => b
    ..id = 1
    ..fullName = 'Stack Secrets');

Contact contact2 = Contact((b) => b
    ..id = 1
    ..fullName = 'Stack Secrets');

print(contact1);
print(contact2);
print(contact1 == contact2);

//Contact {
  //id=1
  //fullName=Stack Secrets,
//}

//Contact {
  //id=1
  //fullName=Stack Secrets,
//}

//true
```