LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-19

この記事はこちらの和訳です。Flutterを触り始めて理解があやしかったBuild_Valueライブラリについて、自分の勉強用にまとめます。

イントロ

この記事でBuilt_Valueがなぜ重要なのかを確認した。今回はBuilt_Valueライブラリのセットアップ方法をみていく。

Built_Valueのセットアップ手順

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

1. 必要なdependencyをインポートする

メインのライブラリはbuilt_valueだが、その他に2つのライブラリも使用する – build_runnerbuilt_value_generator

// 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のクラスは下記のようになる。


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

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

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でコードを自動生成する

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

flutter packages pub run build_runner build

すると、contact.g.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つのクラスが生成されている。


part of 'contact.dart';

class _$Contact extends Contact {...}

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

Built Valueクラスの深堀り

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

Built Valueモデルは抽象クラス

abstract class Contact

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

abstract class Contact implements Built

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

abstract class Contact implements Built<Contact, ContactBuilder> {

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

part 'contact.g.dart';

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

Contact._();

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

int get id;

String get fullName;

@nullable
int get age;

@nullable
String get mobile;

@nullable
bool get isFriend;

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

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

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

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

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なので意図しない変更が発生しない。

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
1
0
0

Register as a new user and use Qiita more conveniently

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