窗口单例已弃用
概述
#为支持多视图和多窗口,window
单例已被弃用。之前依赖 window
单例的代码需要通过 View.of
API 查找要操作的特定视图,或直接与 PlatformDispatcher
交互。
背景
#最初,Flutter 假定一个应用程序只包含一个视图(即 window
),内容可以绘制到其中。在多视图世界中,这个假设不再合理,因此编码了此假设的 API 已被弃用。应用程序和库中依赖这些 API 的部分必须选择要操作的特定视图,并按照本迁移指南中概述的方式迁移到新的多视图兼容 API。
变更说明
#此次更改中被弃用的 API 如下:
dart:ui
中公开的全局window
属性。BaseBinding
类上的window
属性,通常通过以下方式访问:GestureBinding.instance.window
,SchedulerBinding.instance.window
,ServicesBinding.instance.window
,PaintingBinding.instance.window
,SemanticsBinding.instance.window
,RendererBinding.instance.window
,WidgetsBinding.instance.window
,或WidgetTester.binding.window
.
dart:ui
中的SingletonFlutterView
类。flutter_test
中的TestWindow
、其构造函数,以及其所有属性和方法。
对于依赖这些已弃用 API 的应用程序和库代码,存在以下迁移选项:
如果 BuildContext
可用,请考虑通过 View.of
查找当前的 FlutterView
。它返回与给定上下文关联的 build
方法所构建的 widget 将被绘制到的 FlutterView
。FlutterView
提供了与之前已弃用的 window
属性返回的 SingletonFlutterView
类相同的功能。然而,一些平台特定的功能已移至 PlatformDispatcher
,可以通过 View.of
返回的 FlutterView
经由 FlutterView.platformDispatcher
访问。使用 View.of
是从上述已弃用属性进行迁移的首选方式。
如果无法通过 BuildContext
查找 FlutterView
,可以直接查询 PlatformDispatcher
来访问平台特定功能。它还通过 PlatformDispatcher.views
维护所有可用 FlutterView
的列表,以访问视图特定功能。如果可能,应通过 binding(例如 WidgetsBinding.instance.platformDispatcher
)访问 PlatformDispatcher
,而不是使用静态 PlatformDispatcher.instance
属性。这确保了 PlatformDispatcher
的功能可以在测试中被正确地模拟。
测试
#对于在测试中访问 WidgetTester.binding.window
属性以更改窗口属性的测试,以下迁移可用:
在用 testWidgets
编写的测试中,增加了两个新属性,它们共同取代了 TestWindow
的功能。
WidgetTester.view
将提供一个TestFlutterView
,可以类似于WidgetTester.binding.window
进行修改,但仅限于视图特定的属性,例如视图的大小、其显示像素比等。WidgetTester.viewOf
适用于某些多视图用例,但不应作为从WidgetTester.binding.window
迁移的必需项。
WidgetTester.platformDispatcher
将提供对TestPlatformDispatcher
的访问,可用于修改平台特定属性,例如平台的区域设置、某些系统功能是否可用等。
迁移指南
#应用程序和库代码(如果可访问 BuildContext
)不应再访问静态 window
属性,而应使用 View.of
来查找与上下文关联的 FlutterView
。某些属性已通过 platformDispatcher
getter 移动到视图中可访问的 PlatformDispatcher
。
迁移前的代码
Widget build(BuildContext context) {
final double dpr = WidgetsBinding.instance.window.devicePixelRatio;
final Locale locale = WidgetsBinding.instance.window.locale;
return Text('The device pixel ratio is $dpr and the locale is $locale.');
}
迁移后的代码
Widget build(BuildContext context) {
final double dpr = View.of(context).devicePixelRatio;
final Locale locale = View.of(context).platformDispatcher.locale;
return Text('The device pixel ratio is $dpr and the locale is $locale.');
}
如果没有 BuildContext
可用,可以直接查阅 binding 公开的 PlatformDispatcher
。
迁移前的代码
double getTextScaleFactor() {
return WidgetsBinding.instance.window.textScaleFactor;
}
迁移后的代码
double getTextScaleFactor() {
// View.of(context).platformDispatcher.textScaleFactor if a BuildContext is available, otherwise:
return WidgetsBinding.instance.platformDispatcher.textScaleFactor;
}
测试
#在用 testWidget
编写的测试中,应使用新的 view
和 platformDispatcher
访问器。
设置视图特定属性
#TestFlutterView
还通过使用与相关 getter 同名的 setter 而不是带有 TestValue
后缀的 setter,努力使测试 API 更清晰、更简洁。
迁移前的代码
testWidget('test name', (WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 2.0;
tester.binding.window.displayFeaturesTestValue = <DisplayFeatures>[];
tester.binding.window.gestureSettingsTestValue = const GestureSettings(physicalTouchSlop: 100);
tester.binding.window.paddingTestValue = FakeViewPadding.zero;
tester.binding.window.physicalGeometryTestValue = const Rect.fromLTRB(0,0, 500, 800);
tester.binding.window.physicalSizeTestValue = const Size(300, 400);
tester.binding.window.systemGestureInsetsTestValue = FakeViewPadding.zero;
tester.binding.window.viewInsetsTestValue = FakeViewPadding.zero;
tester.binding.window.viewPaddingTestValue = FakeViewPadding.zero;
});
迁移后的代码
testWidget('test name', (WidgetTester tester) async {
tester.view.devicePixelRatio = 2.0;
tester.view.displayFeatures = <DisplayFeatures>[];
tester.view.gestureSettings = const GestureSettings(physicalTouchSlop: 100);
tester.view.padding = FakeViewPadding.zero;
tester.view.physicalGeometry = const Rect.fromLTRB(0,0, 500, 800);
tester.view.physicalSize = const Size(300, 400);
tester.view.systemGestureInsets = FakeViewPadding.zero;
tester.view.viewInsets = FakeViewPadding.zero;
tester.view.viewPadding = FakeViewPadding.zero;
});
重置视图特定属性
#TestFlutterView
保留了重置单个属性或整个视图的功能,但为了更清晰和一致,这些方法的命名已从 clear<property>TestValue
和 clearAllTestValues
分别更改为 reset<property>
和 reset
。
重置单个属性
#迁移前的代码
testWidget('test name', (WidgetTester tester) async {
addTearDown(tester.binding.window.clearDevicePixelRatioTestValue);
addTearDown(tester.binding.window.clearDisplayFeaturesTestValue);
addTearDown(tester.binding.window.clearGestureSettingsTestValue);
addTearDown(tester.binding.window.clearPaddingTestValue);
addTearDown(tester.binding.window.clearPhysicalGeometryTestValue);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
addTearDown(tester.binding.window.clearSystemGestureInsetsTestValue);
addTearDown(tester.binding.window.clearViewInsetsTestValue);
addTearDown(tester.binding.window.clearViewPaddingTestValue);
});
迁移后的代码
testWidget('test name', (WidgetTester tester) async {
addTearDown(tester.view.resetDevicePixelRatio);
addTearDown(tester.view.resetDisplayFeatures);
addTearDown(tester.view.resetGestureSettings);
addTearDown(tester.view.resetPadding);
addTearDown(tester.view.resetPhysicalGeometry);
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetSystemGestureInsets);
addTearDown(tester.view.resetViewInsets);
addTearDown(tester.view.resetViewPadding);
});
一次性重置所有属性
#迁移前的代码
testWidget('test name', (WidgetTester tester) async {
addTearDown(tester.binding.window.clearAllTestValues);
});
迁移后的代码
testWidget('test name', (WidgetTester tester) async {
addTearDown(tester.view.reset);
});
设置平台特定属性
#TestPlatformDispatcher
保持了与 TestWindow
相同的测试 setter 功能和命名方案,因此平台特定属性的迁移主要包括在新 WidgetTester.platformDispatcher
访问器上调用相同的 setter。
迁移前的代码
testWidgets('test name', (WidgetTester tester) async {
tester.binding.window.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
tester.binding.window.alwaysUse24HourFormatTestValue = false;
tester.binding.window.brieflyShowPasswordTestValue = true;
tester.binding.window.defaultRouteNameTestValue = '/test';
tester.binding.window.initialLifecycleStateTestValue = 'painting';
tester.binding.window.localesTestValue = <Locale>[const Locale('en-us'), const Locale('ar-jo')];
tester.binding.window.localeTestValue = const Locale('ar-jo');
tester.binding.window.nativeSpellCheckServiceDefinedTestValue = false;
tester.binding.window.platformBrightnessTestValue = Brightness.dark;
tester.binding.window.semanticsEnabledTestValue = true;
tester.binding.window.textScaleFactorTestValue = 2.0;
});
迁移后的代码
testWidgets('test name', (WidgetTester tester) async {
tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
tester.platformDispatcher.alwaysUse24HourFormatTestValue = false;
tester.platformDispatcher.brieflyShowPasswordTestValue = true;
tester.platformDispatcher.defaultRouteNameTestValue = '/test';
tester.platformDispatcher.initialLifecycleStateTestValue = 'painting';
tester.platformDispatcher.localesTestValue = <Locale>[const Locale('en-us'), const Locale('ar-jo')];
tester.platformDispatcher.localeTestValue = const Locale('ar-jo');
tester.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = false;
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
tester.platformDispatcher.semanticsEnabledTestValue = true;
tester.platformDispatcher.textScaleFactorTestValue = 2.0;
});
重置平台特定属性
#与设置属性类似,重置平台特定属性主要包括将 binding.window
访问器更改为 platformDispatcher
访问器。
重置单个属性
#迁移前的代码
testWidgets('test name', (WidgetTester tester) async {
addTeardown(tester.binding.window.clearAccessibilityFeaturesTestValue);
addTeardown(tester.binding.window.clearAlwaysUse24HourFormatTestValue);
addTeardown(tester.binding.window.clearBrieflyShowPasswordTestValue);
addTeardown(tester.binding.window.clearDefaultRouteNameTestValue);
addTeardown(tester.binding.window.clearInitialLifecycleStateTestValue);
addTeardown(tester.binding.window.clearLocalesTestValue);
addTeardown(tester.binding.window.clearLocaleTestValue);
addTeardown(tester.binding.window.clearNativeSpellCheckServiceDefinedTestValue);
addTeardown(tester.binding.window.clearPlatformBrightnessTestValue);
addTeardown(tester.binding.window.clearSemanticsEnabledTestValue);
addTeardown(tester.binding.window.clearTextScaleFactorTestValue);
});
迁移后的代码
testWidgets('test name', (WidgetTester tester) async {
addTeardown(tester.platformDispatcher.clearAccessibilityFeaturesTestValue);
addTeardown(tester.platformDispatcher.clearAlwaysUse24HourFormatTestValue);
addTeardown(tester.platformDispatcher.clearBrieflyShowPasswordTestValue);
addTeardown(tester.platformDispatcher.clearDefaultRouteNameTestValue);
addTeardown(tester.platformDispatcher.clearInitialLifecycleStateTestValue);
addTeardown(tester.platformDispatcher.clearLocalesTestValue);
addTeardown(tester.platformDispatcher.clearLocaleTestValue);
addTeardown(tester.platformDispatcher.clearNativeSpellCheckServiceDefinedTestValue);
addTeardown(tester.platformDispatcher.clearPlatformBrightnessTestValue);
addTeardown(tester.platformDispatcher.clearSemanticsEnabledTestValue);
addTeardown(tester.platformDispatcher.clearTextScaleFactorTestValue);
});
一次性重置所有属性
#迁移前的代码
testWidgets('test name', (WidgetTester tester) async {
addTeardown(tester.binding.window.clearAllTestValues);
});
迁移后的代码
testWidgets('test name', (WidgetTester tester) async {
addTeardown(tester.platformDispatcher.clearAllTestValues);
});
时间线
#发布版本:3.9.0-13.0.pre.20
稳定版本:3.10.0
参考资料
#API 文档
View.of
FlutterView
PlatformDispatcher
TestPlatformDispatcher
TestFlutterView
TestWidgetsFlutterBinding.window
相关问题
相关 PR