Open16
initializeDateFormattingを紐解いてみる
ピン留めされたアイテム
そもそものきっかけはある程度Flutterで開発したことある人は一度は見たことあるであろう
Unsupported operation: Cannot modify unmodifiable map
への対処のため
まとめ
僕の場合だと device_preview
パッケージとの兼ね合いで起きていました。
どのようなコードで起きているか
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
...
await initializeDateFormatting('ja_JP');
runApp(DevicePreview(enabled: kDebugMode, builder: (_) => MyApp()));
}
なぜ起きていたか
- まず、
initializeDateFormatting
でdateTimeSymbols
とdateTimePatterns
が初期化される
- その後、
DevicePreview (v1.0.0)
内 602行目でLocalizations
ウィジェットが生成される
そうすると、この辺の処理が行われ、再度
dateTimeSymbols
や dateTimePatterns
への代入処理が走り起こっていた
intl.date_symbol_data_local.dart
Future<void> initializeDateFormatting([String? locale, String? ignored]) {
initializeDateSymbols(dateTimeSymbolMap);
initializeDatePatterns(dateTimePatternMap);
return new Future.value();
}
めちゃくちゃ長い...!
Map<dynamic, dynamic> dateTimeSymbolMap() => {
// Date/time formatting symbols for locale en_ISO.
"en_ISO": DateSymbols(
),
};
こちらも5000行くらい
Map<String, Map<String, String>> dateTimePatternMap() => const {
/// Extended set of localized date/time patterns for locale af.
'af': const {
'd': 'd', // DAY
'E': 'ccc', // ABBR_WEEKDAY
...
};
intl.date_format_internal.dart
void initializeDateSymbols(Function symbols) {
if (dateTimeSymbols is UninitializedLocaleData<dynamic>) {
dateTimeSymbols = symbols();
}
}
int.date_format_internal.dart
void initializeDatePatterns(Function patterns) {
if (dateTimePatterns is UninitializedLocaleData<dynamic>) {
dateTimePatterns = patterns();
}
}
どんなスタックで起きているのか
StatefulElement._firstBuild
void _firstBuild() {
assert(state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.initState() as dynamic; // ←ここ
assert(() {
if (debugCheckForReturnedFuture is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
ErrorHint(
'Rather than awaiting on asynchronous work directly inside of initState, '
'call a separate method to do this work without awaiting it.',
),
]);
}
return true;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
assert(() {
state._debugLifecycleState = _StateLifecycle.initialized;
return true;
}());
state.didChangeDependencies();
assert(() {
state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
super._firstBuild();
}
localizations.dart
class _LocalizationsState extends State<Localizations> {
...
void initState() {
super.initState();
load(widget.locale); // ←ここ
}
...
}
Localizations
という StatefulWidget
がいるのね
localizations.dart
void load(Locale locale) {
final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
if (delegates == null || delegates.isEmpty) {
_locale = locale;
return;
}
Map<Type, dynamic>? typeToResources;
final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates) // ←ここ
.then<Map<Type, dynamic>>((Map<Type, dynamic> value) {
return typeToResources = value;
});
if (typeToResources != null) {
// All of the delegates' resources loaded synchronously.
_typeToResources = typeToResources!;
_locale = locale;
} else {
// - Don't rebuild the dependent widgets until the resources for the new locale
// have finished loading. Until then the old locale will continue to be used.
// - If we're running at app startup time then defer reporting the first
// "useful" frame until after the async load has completed.
RendererBinding.instance!.deferFirstFrame();
typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
if (mounted) {
setState(() {
_typeToResources = value;
_locale = locale;
});
}
RendererBinding.instance!.allowFirstFrame();
});
}
}
localization.dart
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending>? pendingList;
// Only load the first delegate for each delegate type that supports
// locale.languageCode.
final Set<Type> types = <Type>{};
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (final LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
types.add(delegate.type);
delegates.add(delegate);
}
}
for (final LocalizationsDelegate<dynamic> delegate in delegates) {
final Future<dynamic> inputValue = delegate.load(locale); // ←ここ
dynamic completedValue;
final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
return completedValue = value;
});
if (completedValue != null) { // inputValue was a SynchronousFuture
final Type type = delegate.type;
assert(!output.containsKey(type));
output[type] = completedValue;
} else {
pendingList ??= <_Pending>[];
pendingList.add(_Pending(delegate, futureValue));
}
}
// All of the delegate.load() values were synchronous futures, we're done.
if (pendingList == null)
return SynchronousFuture<Map<Type, dynamic>>(output);
// Some of delegate.load() values were asynchronous futures. Wait for them.
return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
.then<Map<Type, dynamic>>((List<dynamic> values) {
assert(values.length == pendingList!.length);
for (int i = 0; i < values.length; i += 1) {
final Type type = pendingList![i].delegate.type;
assert(!output.containsKey(type));
output[type] = values[i];
}
return output;
});
}
material_localizations.dart
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
...
Future<MaterialLocalizations> load(Locale locale) {
assert(isSupported(locale));
return _loadedTranslations.putIfAbsent(locale, () {
util.loadDateIntlDataIfNotLoaded(); // ←ここ
final String localeName = intl.Intl.canonicalizedLocale(locale.toString());
assert(
locale.toString() == localeName,
'Flutter does not support the non-standard locale form $locale (which '
'might be $localeName',
);
intl.DateFormat fullYearFormat;
intl.DateFormat compactDateFormat;
intl.DateFormat shortDateFormat;
intl.DateFormat mediumDateFormat;
intl.DateFormat longDateFormat;
intl.DateFormat yearMonthFormat;
intl.DateFormat shortMonthDayFormat;
if (intl.DateFormat.localeExists(localeName)) {
fullYearFormat = intl.DateFormat.y(localeName);
compactDateFormat = intl.DateFormat.yMd(localeName);
shortDateFormat = intl.DateFormat.yMMMd(localeName);
mediumDateFormat = intl.DateFormat.MMMEd(localeName);
longDateFormat = intl.DateFormat.yMMMMEEEEd(localeName);
yearMonthFormat = intl.DateFormat.yMMMM(localeName);
shortMonthDayFormat = intl.DateFormat.MMMd(localeName);
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
fullYearFormat = intl.DateFormat.y(locale.languageCode);
compactDateFormat = intl.DateFormat.yMd(locale.languageCode);
shortDateFormat = intl.DateFormat.yMMMd(locale.languageCode);
mediumDateFormat = intl.DateFormat.MMMEd(locale.languageCode);
longDateFormat = intl.DateFormat.yMMMMEEEEd(locale.languageCode);
yearMonthFormat = intl.DateFormat.yMMMM(locale.languageCode);
shortMonthDayFormat = intl.DateFormat.MMMd(locale.languageCode);
} else {
fullYearFormat = intl.DateFormat.y();
compactDateFormat = intl.DateFormat.yMd();
shortDateFormat = intl.DateFormat.yMMMd();
mediumDateFormat = intl.DateFormat.MMMEd();
longDateFormat = intl.DateFormat.yMMMMEEEEd();
yearMonthFormat = intl.DateFormat.yMMMM();
shortMonthDayFormat = intl.DateFormat.MMMd();
}
intl.NumberFormat decimalFormat;
intl.NumberFormat twoDigitZeroPaddedFormat;
if (intl.NumberFormat.localeExists(localeName)) {
decimalFormat = intl.NumberFormat.decimalPattern(localeName);
twoDigitZeroPaddedFormat = intl.NumberFormat('00', localeName);
} else if (intl.NumberFormat.localeExists(locale.languageCode)) {
decimalFormat = intl.NumberFormat.decimalPattern(locale.languageCode);
twoDigitZeroPaddedFormat = intl.NumberFormat('00', locale.languageCode);
} else {
decimalFormat = intl.NumberFormat.decimalPattern();
twoDigitZeroPaddedFormat = intl.NumberFormat('00');
}
return SynchronousFuture<MaterialLocalizations>(getMaterialTranslation(
locale,
fullYearFormat,
compactDateFormat,
shortDateFormat,
mediumDateFormat,
longDateFormat,
yearMonthFormat,
shortMonthDayFormat,
decimalFormat,
twoDigitZeroPaddedFormat,
)!);
});
}
...
}
flutter_localizations
パッケージ
date_localizations.dart
void loadDateIntlDataIfNotLoaded() {
if (!_dateIntlDataInitialized) {
date_localizations.dateSymbols
.cast<String, Map<String, dynamic>>()
.forEach((String locale, Map<String, dynamic> data) {
// Perform initialization.
assert(date_localizations.datePatterns.containsKey(locale));
final intl.DateSymbols symbols = intl.DateSymbols.deserializeFromMap(data);
date_symbol_data_custom.initializeDateFormattingCustom( // ←ここ
locale: locale,
symbols: symbols,
patterns: date_localizations.datePatterns[locale],
);
});
_dateIntlDataInitialized = true;
}
}
intl
パッケージ
date_symbol_data_custom.dart
void initializeDateFormattingCustom(
{String? locale, DateSymbols? symbols, Map<String, String>? patterns}) {
initializeDateSymbols(_emptySymbols);
initializeDatePatterns(_emptyPatterns);
if (symbols == null) {
throw ArgumentError('Missing DateTime formatting symbols');
}
if (patterns == null) {
throw ArgumentError('Missing DateTime formatting patterns');
}
if (locale != symbols.NAME) {
throw ArgumentError.value(
[locale, symbols.NAME], 'Locale does not match symbols.NAME');
}
dateTimeSymbols[symbols.NAME] = symbols;
dateTimePatterns[symbols.NAME] = patterns; // ←ここ
}
MaterialApp.supportedLocales
と MaterialApp.localizationsDelegate
が設定されている場合でも起きる
ログインするとコメントできます