跳至主要内容

窗口单例已弃用

摘要

#

为了准备支持多个视图和多个窗口,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:uiSingletonFlutterView 类。
  • 来自 flutter_testTestWindow、其构造函数以及所有属性和方法。

以下选项可用于迁移依赖于这些已弃用 API 的应用程序和库代码

如果 BuildContext 可用,请考虑通过 View.of 查找当前的 FlutterView。这将返回 FlutterView,其中将绘制与给定上下文关联的 build 方法构建的小部件。FlutterView 提供对先前在上述已弃用的 window 属性返回的已弃用的 SingletonFlutterView 类上可用的相同功能的访问。但是,一些特定于平台的功能已移至 PlatformDispatcher,可以通过上述 View.of 返回的 FlutterView 通过 FlutterView.platformDispatcher 访问。使用 View.of 是从上述已弃用属性迁移的首选方法。

如果 BuildContext 不可用以查找 FlutterView,则可以直接查询 PlatformDispatcher 以访问特定于平台的功能。它还在 PlatformDispatcher.views 中维护所有可用 FlutterView 的列表,以访问特定于视图的功能。如果可能,应通过绑定(例如 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 的访问,该访问器可用于修改特定于平台的属性,例如平台的语言环境、某些系统功能是否可用等。

迁移指南

#

不要访问静态 window 属性,而是应该访问 BuildContext 的应用程序和库代码应该使用 View.of 来查找上下文关联的 FlutterView。一些属性已移至可通过 platformDispatcher getter 从视图访问的 PlatformDispatcher

迁移前的代码

dart
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.');
}

迁移后的代码

dart
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 不可用,则可以直接查询绑定公开的 PlatformDispatcher

迁移前的代码

dart
double getTextScaleFactor() {
  return WidgetsBinding.instance.window.textScaleFactor;
}

迁移后的代码

dart
double getTextScaleFactor() {
  // View.of(context).platformDispatcher.textScaleFactor if a BuildContext is available, otherwise:
  return WidgetsBinding.instance.platformDispatcher.textScaleFactor;
}

测试

#

在使用 testWidget 编写的测试中,应使用新的 viewplatformDispatcher 访问器代替。

设置特定于视图的属性

#

TestFlutterView 还努力通过使用与相关 getter 同名的 setter(而不是使用 TestValue 后缀的 setter)来使测试 API 更清晰、更简洁。

迁移前的代码

dart
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;
});

迁移后的代码

dart
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>TestValueclearAllTestValues 分别更改为 reset<property>reset

重置单个属性
#

迁移前的代码

dart
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);
});

迁移后的代码

dart
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);
});
一次重置所有属性
#

迁移前的代码

dart
testWidget('test name', (WidgetTester tester) async {
  addTearDown(tester.binding.window.clearAllTestValues);
});

迁移后的代码

dart
testWidget('test name', (WidgetTester tester) async {
  addTearDown(tester.view.reset);
});

设置特定于平台的属性

#

TestPlatformDispatcher 保留了与 TestWindow 相同的功能和命名方案,因此特定于平台的属性的迁移主要包括在新的 WidgetTester.platformDispatcher 访问器上调用相同的 setter。

迁移前的代码

dart
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;
});

迁移后的代码

dart
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 访问器。

重置单个属性
#

迁移前的代码

dart
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);
});

迁移后的代码

dart
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);
});
一次重置所有属性
#

迁移前的代码

dart
testWidgets('test name', (WidgetTester tester) async {
  addTeardown(tester.binding.window.clearAllTestValues);
});

迁移后的代码

dart
testWidgets('test name', (WidgetTester tester) async {
  addTeardown(tester.platformDispatcher.clearAllTestValues);
});

时间线

#

包含在版本中:3.9.0-13.0.pre.20
稳定版发布:3.10.0

参考

#

API 文档

相关问题

相关 PR