この記事はこちらの和訳です。Flutterを触り始めて理解があやしかったBuild_Valueライブラリについて、自分の勉強用にまとめます。
イントロ
この記事でBuilt_Valueがなぜ重要なのかを確認した。今回はBuilt_Valueライブラリのセットアップ方法をみていく。
Built_Valueのセットアップ手順
- 必要なdependencyをインポートする
- モデルに対するbuilt valueクラスを作る
- build runnerでコードを自動生成する
1. 必要なdependencyをインポートする
メインのライブラリはbuilt_valueだが、その他に2つのライブラリも使用する – build_runnerとbuilt_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