概述

#

为支持多视图和多窗口,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 方法所构建的小部件将被绘制到的 FlutterViewFlutterView 提供了与先前在已弃用的 window 属性(如上所述)上可用的相同功能。但是,一些平台特定的功能已移至 PlatformDispatcher,可以通过 View.of 返回的 FlutterView 上的 FlutterView.platformDispatcher 访问。使用 View.of 是迁移离开上述已弃用属性的首选方法。

如果没有 BuildContext 可用于查找 FlutterView,则可以直接咨询 PlatformDispatcher 来访问平台特定的功能。它还维护着所有可用的 FlutterView 列表,存储在 PlatformDispatcher.views 中,以便访问视图特定的功能。如果可能,应通过绑定(例如 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 的情况下,应使用 View.of 来查找与该上下文关联的 FlutterView,而不是访问静态的 window 属性。某些属性已移至可以通过视图的 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 相同的测试 setter 功能和命名约定,因此平台特定属性的迁移主要包括在新的 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