Irbisa · Шпаргалка
Кодогенерация и build_runner
build_runner, part-файлы, json_serializable, freezed, build.yaml, source_gen, время сборки
Что такое
build_runnerи почему Dart генерирует код на этапе сборки, а не использует рефлексию в рантайме?Зачем модели на
json_serializableнужныpart 'user.g.dart';и методы-обёртки (forwarders) к_$UserFromJson?import 'package:json_annotation/json_annotation.dart';import 'address.dart'; // the part file cannot import anything itself part 'user.g.dart'; // you write this; the generator writes `part of` @JsonSerializable()…Чем отличаются
build_runner build,watchиcleanи что сегодня делает--delete-conflicting-outputs?# one-off generation: after a pull, a dependency upgrade, in CIdart run build_runner build # day-to-day: stays running, rebuilds only what changeddart run build_runner watch…Как управлять именами ключей и значениями enum в
json_serializable:snake_case, одно переименованное поле, пропущенное поле, неизвестное значение enum?enum Status { @JsonValue('in_progress') inProgress, done, unknown,}…Нужно ли коммитить в git сгенерированные файлы
.g.dartи.freezed.dart?Что выведет здесь
print(order.toJson())и почему это может сломать код, который не проходит черезjsonEncode?@JsonSerializable()class Order { const Order(this.id, this.address); final String id; final Address address; // Address is @JsonSerializable too Map<String, dynamic> toJson() => _$OrderToJson(this);} void main() { final order = Order('42', const Address('Almaty')); print(order.toJson()); print(jsonEncode(order.toJson()));}Что произойдёт, если передать каждый из этих трёх ответов в сгенерированный
Profile.fromJson?@JsonSerializable()class Profile { const Profile({required this.name, required this.age, this.bio}); final String name; final int age; final String? bio; factory Profile.fromJson(Map<String, dynamic> json) => _$ProfileFromJson(json);} Profile.fromJson({'name': 'Ann', 'age': 30.0}); // 1Profile.fromJson({'name': null, 'age': 30}); // 2Profile.fromJson({'name': 'Ann', 'age': '30'}); // 3Как сериализовать тип, с которым
json_serializableне справляется, например, время в epoch-миллисекундах или value-объектMoney?class Money { const Money(this.cents); final int cents; // minor units, never a double} class MoneyConverter implements JsonConverter<Money, int> {…Найдите баг: пользователь меняет на экране профиля только имя, а на сервере пропадают его био и аватар.
// PATCH /me: the server updates only the keys present in the body@JsonSerializable(fieldRename: FieldRename.snake)class ProfilePatch { const ProfilePatch({this.name, this.bio, this.avatarUrl}); final String? name; final String? bio; final String? avatarUrl; Map<String, dynamic> toJson() => _$ProfilePatchToJson(this);} // Edit profile screen: the user changed only the nameFuture<void> rename(Dio dio, String name) => dio.patch<void>('/me', data: ProfilePatch(name: name).toJson());Что генерирует
freezedи когда достаточно обычногоsealed-класса или@JsonSerializable?import 'package:freezed_annotation/freezed_annotation.dart'; part 'user.freezed.dart'; // ==, hashCode, toString, copyWithpart 'user.g.dart'; // fromJson / toJson, from json_serializable @freezed…Эта модель компилировалась с freezed 2, а после обновления до freezed 3 падает с «Missing concrete implementations». Что изменилось?
import 'package:freezed_annotation/freezed_annotation.dart'; part 'point.freezed.dart'; @freezedclass Point with _$Point { const factory Point({required double x, required double y}) = _Point;} // error: Missing concrete implementations of// 'getter mixin _$Point on Object.x' and 'getter mixin _$Point on Object.y'.Найдите баг: round-trip-тесты для этих событий проходят, но в продакшене каждое событие
screen_viewне декодируется.// Server events: {"type": "purchase", "sku": "A1", "cents": 990}// {"type": "screen_view", "route": "/home"}@Freezed(unionKey: 'type')sealed class Event with _$Event { const factory Event.purchase({required String sku, required int cents}) = Purchase; const factory Event.screenView({required String route}) = ScreenView; factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);}Для чего нужен
build.yamlи как ограничить файлы, которые обрабатывает билдер?# build.yaml, next to pubspec.yamltargets: $default: builders: json_serializable: generate_for:…Вы добавили строку в ARB-файл и запустили
build_runner, а вAppLocalizationsтак и нет нового геттера. Почему?# l10n.yaml at the project root: read by `flutter gen-l10n`, not build_runnerarb-dir: lib/l10n/arbtemplate-arb-file: app_en.arboutput-dir: lib/l10n # real source files, imported normallyoutput-localization-file: app_localizations.dartoutput-class: AppLocalizations…Полная сборка
build_runnerзанимает несколько минут. Куда уходит время и как его сократить?Как написать собственный генератор кода на
source_genи какbuild_runnerего подхватывает?import 'package:analyzer/dart/element/element.dart';import 'package:build/build.dart';import 'package:source_gen/source_gen.dart';import 'package:my_annotations/my_annotations.dart'; // AnalyticsEvent class EventGenerator extends GeneratorForAnnotation<AnalyticsEvent> {…Почему в монорепозитории
build_runnerничего не генерирует для feature-пакета и как настроить генерацию по всем пакетам?# pubspec.yaml at the repo root: a pub workspacename: shop_workspaceenvironment: sdk: ^3.9.0workspace: - app…Что случилось с макросами Dart и что это значит для кодогенерации в вашем проекте?