Commit 8c3d5838 authored by Yegor's avatar Yegor Committed by GitHub

Fix inherited widget notifications in Localizations (#12213)

* Fix inherited widget notifications in Localizations

* address comments
parent 4a04de5e
...@@ -794,7 +794,7 @@ class _TabBarState extends State<TabBar> { ...@@ -794,7 +794,7 @@ class _TabBarState extends State<TabBar> {
} }
/// A page view that displays the widget which corresponds to the currently /// A page view that displays the widget which corresponds to the currently
/// selected tab. Typically used in conjuction with a [TabBar]. /// selected tab. Typically used in conjunction with a [TabBar].
/// ///
/// If a [TabController] is not provided, then there must be a [DefaultTabController] /// If a [TabController] is not provided, then there must be a [DefaultTabController]
/// ancestor. /// ancestor.
......
...@@ -3287,9 +3287,31 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -3287,9 +3287,31 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
@mustCallSuper @mustCallSuper
void didChangeDependencies() { void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op assert(_active); // otherwise markNeedsBuild is a no-op
_debugCheckOwnerBuildTargetExists('didChangeDependencies');
markNeedsBuild(); markNeedsBuild();
} }
void _debugCheckOwnerBuildTargetExists(String methodName) {
assert(() {
if (owner._debugCurrentBuildTarget == null) {
throw new FlutterError(
'$methodName for ${widget.runtimeType} was called at an '
'inappropriate time.\n'
'\n'
'It may only be called while the widgets are being built. A possible '
'cause of this error is when $methodName is called during '
'one of:\n'
'\n'
' * network I/O event\n'
' * file I/O event\n'
' * timer\n'
' * microtask (caused by Future.then, async/await, scheduleMicrotask)'
);
}
return true;
});
}
/// Returns a description of what caused this element to be created. /// Returns a description of what caused this element to be created.
/// ///
/// Useful for debugging the source of an element. /// Useful for debugging the source of an element.
...@@ -3952,6 +3974,7 @@ class InheritedElement extends ProxyElement { ...@@ -3952,6 +3974,7 @@ class InheritedElement extends ProxyElement {
/// by first obtaining their [InheritedElement] using /// by first obtaining their [InheritedElement] using
/// [BuildContext.ancestorInheritedElementForWidgetOfExactType]. /// [BuildContext.ancestorInheritedElementForWidgetOfExactType].
void dispatchDidChangeDependencies() { void dispatchDidChangeDependencies() {
_debugCheckOwnerBuildTargetExists('dispatchDidChangeDependencies');
for (Element dependent in _dependents) { for (Element dependent in _dependents) {
assert(() { assert(() {
// check that it really is our descendant // check that it really is our descendant
......
...@@ -206,6 +206,7 @@ class _LocalizationsScope extends InheritedWidget { ...@@ -206,6 +206,7 @@ class _LocalizationsScope extends InheritedWidget {
Key key, Key key,
@required this.locale, @required this.locale,
@required this.localizationsState, @required this.localizationsState,
@required this.loadGeneration,
Widget child, Widget child,
}) : super(key: key, child: child) { }) : super(key: key, child: child) {
assert(localizationsState != null); assert(localizationsState != null);
...@@ -214,10 +215,14 @@ class _LocalizationsScope extends InheritedWidget { ...@@ -214,10 +215,14 @@ class _LocalizationsScope extends InheritedWidget {
final Locale locale; final Locale locale;
final _LocalizationsState localizationsState; final _LocalizationsState localizationsState;
/// A monotonically increasing number that changes after localizations
/// delegates have finished loading new data. When this number changes, it
/// triggers inherited widget notifications.
final int loadGeneration;
@override @override
bool updateShouldNotify(_LocalizationsScope old) { bool updateShouldNotify(_LocalizationsScope old) {
// Changes in Localizations.locale trigger a load(), see _LocalizationsState.didUpdateWidget() return loadGeneration != old.loadGeneration;
return false;
} }
} }
...@@ -431,6 +436,11 @@ class _LocalizationsState extends State<Localizations> { ...@@ -431,6 +436,11 @@ class _LocalizationsState extends State<Localizations> {
final GlobalKey _localizedResourcesScopeKey = new GlobalKey(); final GlobalKey _localizedResourcesScopeKey = new GlobalKey();
Map<Type, dynamic> _typeToResources = <Type, dynamic>{}; Map<Type, dynamic> _typeToResources = <Type, dynamic>{};
/// A monotonically increasing number that increases after localizations
/// delegates have finished loading new data, triggering inherited widget
/// notifications.
int _loadGeneration = 0;
Locale get locale => _locale; Locale get locale => _locale;
Locale _locale; Locale _locale;
...@@ -494,9 +504,8 @@ class _LocalizationsState extends State<Localizations> { ...@@ -494,9 +504,8 @@ class _LocalizationsState extends State<Localizations> {
setState(() { setState(() {
_typeToResources = value; _typeToResources = value;
_locale = locale; _locale = locale;
_loadGeneration += 1;
}); });
final InheritedElement scopeElement = _localizedResourcesScopeKey.currentContext;
scopeElement?.dispatchDidChangeDependencies();
}); });
} }
} }
...@@ -521,6 +530,7 @@ class _LocalizationsState extends State<Localizations> { ...@@ -521,6 +530,7 @@ class _LocalizationsState extends State<Localizations> {
key: _localizedResourcesScopeKey, key: _localizedResourcesScopeKey,
locale: _locale, locale: _locale,
localizationsState: this, localizationsState: this,
loadGeneration: _loadGeneration,
child: new Directionality( child: new Directionality(
textDirection: _textDirection, textDirection: _textDirection,
child: widget.child, child: widget.child,
......
...@@ -25,6 +25,18 @@ class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLoc ...@@ -25,6 +25,18 @@ class FooMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLoc
bool shouldReload(FooMaterialLocalizationsDelegate old) => false; bool shouldReload(FooMaterialLocalizationsDelegate old) => false;
} }
/// A localizations delegate that does not contain any useful data, and is only
/// used to trigger didChangeDependencies upon locale change.
class _DummyLocalizationsDelegate extends LocalizationsDelegate<DummyLocalizations> {
@override
Future<DummyLocalizations> load(Locale locale) async => new DummyLocalizations();
@override
bool shouldReload(_DummyLocalizationsDelegate old) => true;
}
class DummyLocalizations {}
Widget buildFrame({ Widget buildFrame({
Locale locale, Locale locale,
Iterable<LocalizationsDelegate<dynamic>> delegates, Iterable<LocalizationsDelegate<dynamic>> delegates,
...@@ -293,4 +305,21 @@ void main() { ...@@ -293,4 +305,21 @@ void main() {
expect(tester.widget<Text>(find.byKey(textKey)).data, 'id_JV'); expect(tester.widget<Text>(find.byKey(textKey)).data, 'id_JV');
}); });
testWidgets('Localizations is compatible with ChangeNotifier.dispose() called during didChangeDependencies', (WidgetTester tester) async {
// PageView calls ScrollPosition.dispose() during didChangeDependencies.
await tester.pumpWidget(new MaterialApp(
supportedLocales: const <Locale>[
const Locale('en', 'US'),
const Locale('es', 'ES'),
],
localizationsDelegates: <_DummyLocalizationsDelegate>[
new _DummyLocalizationsDelegate(),
],
home: new PageView(),
));
await tester.binding.setLocale('es', 'US');
await tester.pump();
await tester.pumpWidget(new Container());
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment