跳至主要內容

window 單例已被棄用

摘要

#

為了支援多個視圖和多個視窗,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_testTestWindow、其建構子,以及其所有屬性和方法。

以下是一些選項,可供遷移依賴這些已棄用 API 的應用程式和函式庫程式碼:

如果 BuildContext 可用,請考慮透過 View.of 查詢當前的 FlutterView。這會返回 FlutterView,與給定內容相關聯的 build 方法所建立的 Widget 將繪製到其中。FlutterView 提供與先前在已棄用的 SingletonFlutterView 類別(由上述已棄用的 window 屬性返回)上可用的相同功能。但是,某些平台特定的功能已移至 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 的存取,可用於修改平台特定的屬性,例如平台的地區設定、是否可用某些系統功能等。

遷移指南

#

應用程式和函式庫程式碼具有 BuildContext 的存取權限,應使用 View.of 來查詢與內容相關聯的 FlutterView,而不是存取靜態 window 屬性。某些屬性已移至 PlatformDispatcher,可透過視圖上的 platformDispatcher getter 存取。

遷移前的程式碼

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 也努力使測試 API 更清晰且更簡潔,方法是使用與相關 getter 同名的 setter,而不是使用帶有 TestValue 後綴的 setter。

遷移前的程式碼

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