【文章翻譯】Announcing Dart 2.10

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

宣佈 Dart 2.10

一個新的、統一的 dart 工具,適用於所有核心任務。此外,還更新了空安全時間表和遷移原則。

作者: Michael ThomsenKevin Moore

今天,我們宣佈推出 Dart 的新版本 2.10(_二點一零_)。此版本具有一個新的、統一的 Dart 開發工具:一個滿足所有開發人員需求的單一工具,例如建立專案、分析和格式化程式碼、執行測試以及編譯應用程式。我們還更新了空安全路線圖和時間表,並討論了將現有程式碼遷移到空安全的原則。

一個新的、統一的 Dart 開發工具

Dart 構成了 Flutter SDK 的基礎:它不僅提供了支援 Flutter 應用程式的語言和執行時環境,還支援許多核心開發人員任務,例如格式化、分析和測試程式碼。然而,雖然 Flutter 有一個單一的、通用的開發人員工具(flutter 命令),但 Dart 歷來有許多較小的開發人員工具(例如,dartfmt 和 dartanalyzer)。Dart 2.10 有一個新的、統一的 dart 開發工具,它與 flutter 工具非常相似。這個新工具支援所有常見任務,例如建立新專案和套件;分析和格式化程式碼;以及執行、編譯和測試專案。要使用該工具,只需執行 dart 命令:

透過執行 dart help 了解 2.10 dart 工具。

Flutter 在 Flutter SDK 中包含了這個新的 Dart 工具。從今天的 Flutter 1.22 SDK 開始,/bin 目錄(您可能在 PATH 中有此目錄)包含 flutter 和 dart 命令。如果您同時進行 Flutter 和通用 Dart 開發,則可以從單個 Flutter SDK 中獲得兩種開發人員體驗,而無需安裝任何其他東西。

注意:如果您想要下載並安裝第二個 Dart SDK(可能是因為您需要不同的版本),請確保您想要預設使用的 dart 工具的 SDK 位於 PATH 環境變數的開頭。

在即將發佈的穩定版本中,我們計劃在此 dart 工具中加入更多功能,並逐步棄用較小的工具(dartdoc、dartfmt、dartanalyzer 等)。明年,我們預計將發佈僅包含單個 dart 工具的 Dart SDK。我們建議您現在執行 Dart 命令時切換到新工具,無論是在終端機中手動執行還是透過持續整合(_CI_)指令碼執行,如果缺少任何功能或功能未按預期工作,請向我們提供回饋

展望空安全

自從幾個月前我們推出第一個技術預覽版以來,我們對空安全產生了濃厚的興趣。我們都期待空安全作為一種避免難以發現的空錯誤的工具,此外,我們還期望看到聲音空安全帶來的效能提升。如果您渴望了解更多資訊,我們推薦您閱讀我們新的理解空安全頁面。如果您更喜歡觀看短片,請查看幾個月前 Flutter Day 活動 中的空安全影片

空安全何時可以使用?以下是目前的時間表:

  1. Flutter 使用 技術預覽版 2 進行實驗:我們已成功遷移 Flutter 的大部分內容。很快——可能在下個月內——我們預計將遷移完整的 Flutter 架構,從而準備好與 Flutter 一起啟用實驗性使用。您將能夠在 Flutter 範例中嘗試空安全,並對您的 Flutter 應用程式和套件進行試驗性遷移。您將需要傳遞一個實驗標誌,不應在生產環境中使用它,也不應發佈任何已遷移的套件。
  2. 使用 測試版 進行早期套件遷移:今年晚些時候,我們將完成效能調整,並擁有足夠的測試覆蓋率,讓我們確信該功能能夠按預期工作,並且向後相容性是可靠的。屆時,我們將發佈該功能的測試版,您將不需要傳遞實驗標誌。我們希望看到套件所有者開始將他們的套件遷移到空安全,並進行最後一輪驗證,以確保該功能已準備好發佈穩定版。
  3. 使用 穩定版 進行生產環境使用:根據測試版發佈的回饋,我們將修復任何剩餘問題,然後發佈到穩定版。很難說明具體的時間表,但我們認為是在明年年初。一旦該功能穩定下來,我們希望看到空安全得到廣泛採用,將空安全應用程式發佈到應用程式商店,並在 pub.dev 上發佈許多穩定版本的空安全套件。

遷移到空安全的原則

我們想分享我們指導空安全遷移的原則。

準備好後再採用

空安全是 Dart 類型系統的根本性變革。它改變了變數宣告的基礎,因為我們決定預設將變數設為不可為空:

1
2
無空安全                      有空安全
String s; // 一個 String 或 null。 String s; // 一個 String,不可為 null。

如果我們堅持強制採用,如此根本性的變革將會極具破壞性。我們希望讓 決定何時是合適的時機,因此空安全是一個可選功能:您將能夠使用最新的 Dart 和 Flutter 版本,而無需在準備好之前強制啟用空安全。您甚至可以依賴尚未啟用空安全的應用程式或套件中已啟用空安全的套件。

按順序逐步採用

我們強烈建議按順序遷移程式碼,首先遷移依賴圖的葉子。例如,如果 C 依賴於 B,而 B 依賴於 A,則首先將 A 遷移到空安全,然後是 B,然後是 C。此順序適用於 A、B 和 C 是程式庫、套件還是應用程式。

為什麼順序很重要?儘管您可以在依賴項遷移之前在遷移程式碼方面取得一些進展,但如果您的依賴項在其遷移期間更改其 API,則您可能會冒險進行第二次遷移。我們將提供工具來幫助您找出哪些依賴項已遷移。如果您是套件作者,為了避免破壞 API 的風險,請等到所有依賴項都已遷移後再發佈空安全版本。

使用自動化工具來降低遷移成本

當您的依賴項準備就緒並且您選擇進行遷移時,您可以使用我們的遷移工具。該工具透過分析所有現有程式碼來工作,尋找哪些宣告可以不可為空(保持不變),哪些必須可為空(需要在宣告上加上 ? 空值標記)。

遷移工具是互動式的,因此您可以檢查該工具推斷出的空值屬性。如果您不同意該工具的任何結論,您可以加入空值提示來更改推論。例如,即使需要一些重構,如果您想要使 API 不可為空,您可以告知該工具並重新執行遷移分析。加入一些遷移提示可以對遷移品質產生巨大影響。

充分利用才能獲得全部收益

一旦 所有 程式碼——以及它所依賴的套件——都已遷移,您的程式碼就可以在 聲音 空安全的情況下執行。在此之前,您的程式碼將繼續像今天一樣執行和編譯,但完全聲音的空安全可以啟用完整的執行時驗證和編譯器優化。使用聲音空安全執行測試可幫助您避免執行時空斷言的問題,使用聲音空安全編譯應用程式可確保您獲得目前和未來的優化,例如更小的編譯輸出和更快的執行速度。

後續步驟

新的 dart 開發人員工具現已在 Dart 2.10 SDK 和 Flutter 1.22 SDK 中提供。如果您已經擁有 Flutter SDK,只需執行 flutter upgrade 即可獲得帶有 dart 命令的 Dart SDK;這將為您提供嵌入 Dart 2.10 的 Flutter 1.22 SDK。我們建議您立即切換到新的 dart 工具,如果缺少任何功能或功能未按預期工作,請向我們提供回饋

我們很快就會有更多關於空安全的新聞——很可能在下個月內,我們預計 Flutter 團隊的朋友們將會有一個啟用空安全的 Flutter 架構,準備用於實驗。請密切關注 Flutter 部落格 以獲取更新。同時,您可以使用 DartPad with Null Safety 嘗試使用空安全的 Dart 程式碼,並透過閱讀我們的空值安全文件來了解更多關於功能設計的資訊。


宣佈 Dart 2.10 最初發佈在 Medium 上的 Dart 中,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Announcing Flutter 1.22

【文章內容使用 Gemini 1.5 Pro 自動產生】

宣布 Flutter 1.22

我們很高兴地宣布 Flutter 的最新版本,它对 iOS 14 和 Android 11 提供了广泛的支持。Flutter 1.22 在以前版本的基础上构建,让开发者能够从单一代码库构建快速、美观的跨平台用户体验。我们的季度稳定版本包含最新的功能、性能改进和错误修复,适合广泛的生产使用。

由于这是新移动操作系统版本的发布季节,因此此版本专注于确保 Android 11 和 iOS 14 与 Flutter 完美配合。对这两个操作系统的更新包括大量幕后工作,以符合最新的 SDK,并确保一切通过我们的全面测试套件。对于 iOS 14,此版本包含对新 Xcode 12、新图标的支持,以及对新 iOS 14 App Clips 功能的预览支持。对于 Android 11,更新支持新的显示切口类型,以及在弹出软键盘时的更流畅动画。

此版本在我们的 1.20 版本发布两个月后发布,因此比大多数版本都要短。即使在这段时间内,我们也关闭了 3,024 个问题,并合并了来自 197 位贡献者的 1,944 个 PR。在这 197 位贡献者中,有 114 位(58%)来自更广泛的社区,他们贡献了 271 个 PR。最大的单一贡献者是 a14n,他再次成为我们的顶级贡献者榜单,贡献了 20 个 PR,其中大多数是作为 Flutter 中支持空安全的工作的一部分完成的(更多相关内容即将推出)。

除了对新移动操作系统版本的支持外,我们还有很多新闻要分享,包括对 Android 最受欢迎功能之一的预览:状态恢复、一个新的 Material 按钮“宇宙”、与热重载配合使用的新国际化和本地化支持、一个新的 Navigator、Platform Views 的稳定版本(Google Maps 和 WebView Plugin 的基础)以及可以在代码中添加的开关,以改善具有高频率显示屏的设备上的滚动效果。我们还提供了一个新的工具来剖析应用大小,并确保您构建的 Plugin 仅支持您想要支持的平台。

针对 iOS 14

每当宣布新版本的移动操作系统时,我们都会对其进行彻底测试,寻找影响 Flutter 及其工具的不兼容性或更改。

在 iOS 14 的情况下,我们对 Flutter 做了一些更改,以确保它按开发者想要的方式工作:

  • Xcode 12 要求 iOS 9.0 或更高版本,因此我们的默认模板将默认值从 8.0 提高到 9.0。
  • iOS 14 特定的崩溃和字体渲染问题已在 Flutter 1.22 中修复。
  • 截至 Flutter 1.20.4,已修复部署到实体设备的问题。
  • 一项新政策,当应用访问其剪贴板时会显示使用通知,导致 Flutter 应用出现虚假通知,这个问题已在 Flutter 1.20.4 中修复。
  • 限制禁止在 iOS 14 设备上运行调试应用,除非作为调试过程的一部分。
  • 针对本地调试的 Flutter 应用的网络安全新政策会导致 iOS 14 显示一次性确认对话框(仅在开发期间,而不是针对已发布的 Flutter 应用)。

底线:如果您要针对 iOS 14 使用 Flutter 应用,我们强烈建议您使用 Flutter 1.22 重新构建它并将其部署到 App Store,以确保您的 iOS 14 用户获得最佳体验。

有关使用 Flutter 针对 iOS 14 的更多详细信息,包括一些 Add-to-App、深层链接和通知注意事项,请参阅 flutter.dev 上的 iOS 14 文档

希望所有这些对工具和 SDK 支持的工作可以让您专注于您关心的编码——利用新的 iOS 14 功能。

其中一项功能是对 iOS 的新 SF Symbols 字体的更新支持,这启发了我们花了一些时间对 cupertino_icon 包 进行刷新。一旦您将 cupertino_icons 依赖项更新到新的 1.0 主要版本,现有的 CupertinoIcons 使用情况将自动映射到新样式。如果您将 cupertino_icons 1.0 与 Flutter 1.22 结合使用,您还可以通过 CupertinoIcons API 访问约 900 个新图标。

您可以在 cupertino_icons 预览页面上查看完整的图标列表,以及 flutter.dev 上的迁移详细信息页面

您可以尝试在 iOS 14 上使用 Flutter 的另一项功能是 App Clips,这是一项新的 iOS 14 功能,支持快速、无需安装的应用执行,可以执行 10MB 以下的应用的轻量级版本。在 Flutter 1.22 版本中,我们对使用 Flutter 构建的 App Clip 目标进行了预览。

由 Flutter 提供支持的 App Clip 体验

有关使用 Flutter 构建 App Clips 的更多详细信息,请查看 flutter.dev 上的文档。您还可以参考这个 简单的示例项目

Android 11

此版本的 Flutter 也与本月推出的 Android 11 版本同步。Flutter 框架和引擎已更新,以支持最新版本的 Android 中引入的两个新功能。

首先,Flutter 现在支持公开 Android 切口、切出部分和瀑布显示边缘的安全内边距。

通过使用 MediaQuerySafeArea API,您可以确保将活跃的 UI 和交互式元素放置在设备显示屏的无障碍区域。此外,您还应避免在容易发生意外触碰的瀑布边缘区域放置手势检测器。

其次,动画与 Android 11 同步,因为它显示了软件键盘。

查看 FAB 的位置动画

问题 #19279 是一个长期存在的问题,即系统键盘显示/隐藏动画与 Flutter 的内边距不同步。这个问题已在 Android 11 中修复。

关于 Android 嵌入 API 的一个说明。在去年的 Flutter 1.12 版本中,我们为 Android 推出了新的 Flutter 引擎和 Flutter Plugin API 集。我们创建了这些 v2 API 以更好地支持我们在 Android 上的 Add-to-App 用户。一年后,超过 80% 的 Android Plugin 使用了新的 Android API。从 1.22 版本开始,我们将弃用旧的 v1 API。

如果您仍在使用 Android v1 API,以下内容与您相关:

  • 新创建的 Plugin 将不再针对 v1 API。
  • Flutter 工具的 -no-enable-android-embedding-v2 配置标志已删除,现在是默认行为。
  • 仍在使用 v1 API 的旧应用将在构建过程中显示一个弃用警告,该警告指向 支持新的 Android Plugin API 的文档

同时,如果您仍然具有基于 v1 Android API 的 Flutter 应用,它将继续工作。但是,您可能会开始遇到仅针对 v2 API 且无法被 v1 Android API 使用的新 Plugin。有关更多详细信息,请参阅 重大更改文档

扩展按钮“宇宙”

一个新的 Material Design 按钮“宇宙”

现有的 Flutter 按钮看起来不错,但可能 难以使用,特别是在您需要自定义主题时。此外,Material 规范已扩展到包含具有新样式的新按钮。

为了让 Flutter 与 Material 指南保持同步,我们很高兴地宣布 Flutter 1.22 中推出了全新的按钮“宇宙”。

与其尝试就地演变现有的按钮类及其主题,此 PR 引入了新的替换按钮 Widget 和主题。除了让我们摆脱演化现有类所必需的后向兼容迷宫外,新名称还使 Flutter 与 Material Design 规范 同步,该规范使用新名称来表示按钮组件。

新主题遵循 Flutter 最近为新的 Material Widget 采用的“规范化”模式。如果您想尝试一个演示,这里有一个很棒的 DartPad 演示。这不是重大更改,因为 FlatButton、OutlineButton、RaisedButton、ButtonBar、ButtonBarTheme 和 ButtonTheme 的语义不会改变。您可以根据自己的喜好混合和匹配旧按钮和新按钮。

新的国际化和本地化支持

从 Flutter 的诞生之日起,Flutter 就提供了应用程序国际化 (i18n) 和本地化 (l10n) 所需的核心功能。但是,在此版本中,我们将对最佳实践的看法融入到我们的工具中,甚至启用热重载支持,以便在您添加新的 l10n 信息时更新您的应用。

如果您希望了解有关 Flutter 对 l10n 支持的更多详细信息,包括本地化消息、带有参数的消息、日期、数字和货币,请 阅读 Flutter 国际化用户指南

此外,如果您对 i18n 和 l10n 感兴趣,那么您可能也对不适合纯 ASCII 字符串的字符(例如 Unicode 和表情符号)感兴趣。最近,Dart 团队发布了 characters 包,它帮助开发者处理 Unicode(扩展)字形群集。此包帮助解决了诸如如何将类似于“A 🇬🇧 text in English”的字符串正确缩短到前 15 个字符的问题。使用 String 类,该缩写将是“A 🇬🇧 text in”,这只有 12 个用户感知字符。另一方面,使用 characters 包会产生正确的缩写“A 🇬🇧 text in Eng”。

通过 此 PR,Flutter 使用 characters 包来正确处理这些复杂的字符。例如,当使用带有 maxLength 限制的 TextField 时,像👨‍👩‍👦这样的字符现在被正确地视为单个字符。此外,通过 此 PR,characters 包在 Flutter 所在的项目中自动可用,无需手动添加它。希望这将使处理来自所有语言环境的所有类型的字符串变得更加容易。有关 characters 包的更多详细信息,请查看这篇文章:如何正确进行 Dart 字符串操作

Google Maps 和 WebView Plugin 现已做好生产准备

在 Flutter 团队中,我们通常谨慎地将某些东西标记为“生产就绪”,直到我们自己对其进行彻底测试。在 google_maps_flutterwebview_flutter Plugin 的情况下,限制因素一直是底层的 Platform Views 实现,它允许来自 Android 和 iOS 的原生 UI 组件托管在 Flutter 应用中。在此版本的 Flutter 中,我们很高兴地宣布,我们已充分增强了框架管道,足以宣布这两个 Plugin 为生产就绪。

webview_flutter Plugin 托管 flutter.dev

在 Flutter 1.22 中,我们添加了一个替代的 Platform Views 实现,它修复了 Android 视图的所有已知键盘和辅助功能问题。此外,它与 Android API 级别 19 及更高版本配合使用(以前需要级别 20)。我们还在 iOS 上进行了线程改进,这些改进使平台视图更有效、更健壮(并且不再需要将 io.flutter.embedded_views_preview 标志添加到您的 iOS Info.plist 中)。

webview_flutter Plugin 支持新的 Android Platform Views 模式,但目前需要 手动启用。一旦它在更广泛的社区中得到更多使用,我们将在未来的版本中默认启用它。

Google Maps 和 WebView Plugin 已经从 Platform Views 中的改进中受益。如果您想使用 Platform Views 在 iOS 或 Android 上托管您自己的原生 UI 组件,您可以在 使用 Platform Views 在 Flutter 应用中托管原生 Android 和 iOS 视图 上了解如何操作。

如果您以前在 Flutter 应用中使用过 导航,您可能已经注意到核心数据结构(用户正在浏览的页面堆栈)对您隐藏了。相反,要管理它,您会调用 Navigator.pop() 或 Navigator.push()。例如,假设您想在主页上显示一个 Widget 列表,并允许用户点击其中一个 Widget 以进入专门针对该颜色的详细信息页。

这两个屏幕可以这样实现:

使用最简单的 Navigator 1.0 样式可以让您以看起来非常简单的方式在这两个屏幕之间导航:

调用 Navigator.push() 是将另一个页面推到第一个页面之上的所需操作,从而创建一个包含两个页面的堆栈。但是,与在 ColorListScreen 的 build 方法中创建的 Container 列表不同,该堆栈对您隐藏了。由于它被隐藏了,因此很难管理其他场景,例如处理由原生嵌入提供的初始路由的深层链接,例如来自网页的 URL 或来自 Android 的 intent。管理相同页面不同排列之间的嵌套路由也极其困难。

Navigator 2.0 通过使页面堆栈可见来解决这些问题以及更多问题。以下是在 ColorListScreen 和 ColorScreen 之间导航的更新示例:

应用明确地创建一个 Navigator,并为它提供一个页面列表,该列表表示完整的堆栈。我们创建了一个空 _selectedColor 来指示尚未选择任何颜色,因此我们不会最初显示 ColorScreen。当用户选择颜色时,我们会像往常一样调用 setState() 以指示 Flutter 您希望再次调用 build() 方法,该方法现在将在顶部创建一个包含 ColorScreen 的堆栈。

例如,您可以在 OnPopPage 回调中更新您的状态,如果用户弹出了,那么他们就“取消选择”了当前颜色,我们不再希望显示该页面。

如果 Navigator 2.0 看起来与 Flutter 的其他部分一样,那就是我们的意图——它是声明性的,与命令式的 Navigator 1.0 不同。我们的想法是统一导航和 Flutter 其他部分之间的模型,同时修复大量问题并添加功能。事实上,这个小例子只是 Navigator 2.0 中一部分内容的表面描述。有关详细信息,我强烈推荐这篇文章:Flutter 中的声明式导航和路由

此外,您现有的 Navigator 1.0 使用方式将继续与今天一样工作,并且不会很快被移除。如果您喜欢该模型,您当然可以继续使用它。但是,如果您尝试使用 Navigator 2.0,我们认为您会喜欢它。

预览:Android 的状态恢复

此版本中可供您体验的新功能是对 Android 上的状态恢复 的支持。这是我们 最受欢迎的功能之一,获得了 217 个点赞!

对于那些不熟悉状态恢复需求的人来说,移动操作系统可能会终止后台应用以回收资源以供前台应用使用。发生这种情况时,操作系统会通知要终止的应用尽快保存任何 UI 状态,以便在用户返回到该应用时恢复它。如果实现正确,这将为用户提供无缝的体验,同时更好地利用设备资源。到目前为止,Flutter 不支持状态恢复,在没有框架支持的情况下正确地做到这一点非常困难。这就是我们很高兴能够为 Android 提供此功能的基础实现的原因。

以下是如何 恢复默认 Flutter Counter 应用的状态的简单示例

简而言之,每个 Widget 都获得一个存储桶,该存储桶使用唯一的 ID 与 RestorationMixin 关联。通过使用 RestorableProperty 类型(此处使用的是 RestorableInt)来存储 UI 特定的数据,并通过将该数据与状态恢复功能关联,数据将在 Android 终止应用之前自动存储,并在应用恢复时恢复。就这样。存储在 Restoration* 类型中的任何数据,例如 RestorableInt、RestorableString 和 RestorableTextEditingController(我们有很多这样的类型),都将被恢复。如果我们没有涵盖您希望恢复的所有类型,您可以通过扩展 RestorableProperty<T> 创建您自己的类型。

为了对状态恢复进行自动化测试,我们已向 WidgetTester 添加了一个新的 restartAndRestore API。为了手动测试,最简单的方法是在 Android 设备上启动启用了状态恢复的 Flutter 应用,在 Android 的开发者设置中启用“不保留活动”,运行 Flutter 应用,将其置于后台,然后返回到它。此时,Android 将终止并恢复您的应用,因此您可以查看一切是否按预期工作。

虽然我们很高兴将此状态恢复的预览版本交给您,但仍有更多工作要做。例如,状态恢复不仅适用于 Android,iOS 应用也可以从中受益。此外,我们正在忙于更新我们自己的 Widget,以在恢复期间保持其状态。我们已经在 Scrollable 类(例如 ListView 和 SingleChildScrollView)(记住用户的滚动位置)和 TextField(恢复用户输入的文本)中提供了支持,并计划将其扩展到其他 Widget。

但是,我们尚未添加的关键恢复支持,也是使它成为预览版本的原因是导航(1.0 或 2.0)。这意味着用户在应用中的位置将不会被恢复。该功能将很快在测试版中推出,并在 Flutter 的下一个稳定版本中推出。

预览:为无与伦比的输入和显示频率提供流畅的滚动效果

Flutter 团队与我们内部的 Google 合作伙伴合作,在输入频率和显示频率不相同的情况下大大改善了滚动的性能。例如,Pixel 4 的输入频率为 120hz,而显示频率为 90hz。这种不匹配会导致滚动时出现性能损失。使用新的 resamplingEnabled 标志,您可以利用我们在 Flutter 中完成的性能工作来解决此问题:

根据所涉及的频率差异,通过启用此标志,您可能会看到滚动时的卡顿减少高达 97%。当我们确定这是最佳体验时,我们计划在未来的版本中默认启用此标志。

一个新的统一 Dart 开发者工具

与往常一样,更新 Flutter 不仅仅意味着引擎和框架,还包括工具。Flutter 1.22 包括新的 Dart 版本(2.10),并且有一个新的 dart CLI 工具,您可能会发现它也很有用。

Dart 历史上拥有许多较小的开发者工具(例如用于格式化的 dartfmt 和用于代码分析的 dartanalyzer)。在 Dart 2.10 中新增了一个统一的 dart 开发者工具,它与 flutter 工具非常相似。

Screenshot of the output of `dart help`.

从今天的 Flutter 1.22 SDK 开始,您会发现 <flutter-sdk>/bin 文件夹(您可能将其放在 PATH 中)包含 flutter 和 dart 命令。有关更多详细信息,请参阅 Dart 2.10 部落格文章

应用大小分析工具

作为 Flutter 1.22 的一部分发布的工具包含一个新的输出大小分析实用程序。此工具帮助诊断 Flutter 您的应用的大小分解随着时间的推移是否发生了变化。

您可以使用此工具通过传递一个--analyze-size 标志到以下任何命令来收集分析所需的数据:

  • flutter build apk
  • flutter build appbundle
  • flutter build ios
  • flutter build linux
  • flutter build macos
  • flutter build windows

在构建 Flutter 输出工件时使用此标志将打印工件大小和组成的摘要。这包括原生代码、资产,甚至编译的 Dart 代码的包级别分解。

Flutter Gallery 发布的 APK 的示例分解

此摘要对于快速识别应用程序包大小使用情况中的热点很有用。此外,收集的数据还可用作 Dart DevTools 中的 JSON 文件,它允许您进一步探索应用的内容,查明大小问题,并通过遵循 flutter.dev 上的说明 查看两个不同 JSON 文件之间的更改。加载 JSON 文件后,您将拥有一个界面,该界面将为您提供应用大小的树状图视图。

Dart DevTools 中的示例 APK 分解

有关使用应用大小工具可以执行的操作的更多详细信息,请阅读 flutter.dev 上的使用应用大小工具文档

预览:DevTools 中更新的网络页面

此版本中的另一个 DevTools 预览功能是能够在 网络 选项卡中查看 HTTP 和 HTTPS 响应正文。

若要启用此功能,请确保您通过 flutter channel devflutter channel upgrade 位于 Flutter dev 频道。

此外,对于有大量网络流量的应用,我们提供搜索和筛选功能。

有关 网络 选项卡文档,请参阅 flutter.dev 上的使用网络视图

IntelliJ 中托管的 DevTools Inspector 选项卡

一段时间以来,我们一直在维护我们 Flutter 某些工具的两个副本,例如 IntelliJ 中的 Inspector 窗格和 Dart DevTools 中的 Inspector 选项卡。这不仅会降低我们的速度,因为我们必须维护两个代码库,而且某些功能(例如布局资源管理器)尚未加入 IntelliJ Plugin。因此,为了解决这两个问题,我们启用了直接在 IntelliJ 中托管来自 Dart DevTools 的 Inspector 选项卡的功能。

请注意布局资源管理器的添加,您可以在代码旁边使用它。若要打开此选项,请转到 首选项 > 语言和框架 > Flutter > 启用嵌入式 DevTools Inspector

Visual Studio Code 中改进的输出链接

所有 Flutter 开发者都会遇到的一个常规操作是从终端或堆栈跟踪中的错误输出开始。在 Visual Studio Code 的 Flutter 扩展的最新版本中,这些链接现在将被正确地解析,以启用从输出中的链接直接进行跳转。

这看起来似乎是一个小事情,但对这一功能的初步反馈已经非常积极。

与往常一样,这里列出的工具更改太多,但我建议您查看以下公告以获取详细信息:

客户案例:EasyA

EasyA 是一款订阅应用,旨在让学龄学生通过即时消息与优秀的导师联系,并使用 Flutter 编写。最近它被 Apple 评为 Apple 的“今日应用”

“当学校今年早些时候开始上网时,我们知道我们需要快速推出我们的辅导应用来帮助学生。使用 Flutter 进行开发的超快速度意味着我们能够为 iOS 和 Android 实现获奖设计,并发布到网页——恰逢封锁期间!通常情况下,这是不可能实现的。但由于 Flutter 允许我们同时定位所有三个平台,因此我们能够高效地共享代码,并充分利用我们的小型开发者团队。”
— 菲利普·郭,[EasyA](https://easya.io/) 的联合创始人

重大更改

与往常一样,我们一直努力将重大更改的数量降至最低。以下是 Flutter 1.22 版本的列表。

总结

Flutter 1.22 稳定版本可能是在上一个版本之后快速发布的,但它包含了如此多的优秀内容,以至于这篇文章无法全部提及。我们希望此版本能帮助您为 iOS 和 Android 构建出色的应用,我们迫不及待地想看看您将会实现什么!感谢您的支持——我们为你们构建 Flutter。


宣布 Flutter 1.22 最初发布在 Flutter 上的 Medium,人们在那里通过突出显示和回应这个故事,继续进行讨论。

http://creativecommons.org/licenses/by/4.0/

【文章翻譯】Learning Flutter’s new Navigation and Routing system

【文章內容使用 Gemini 1.5 Pro 自動產生】

學習 Flutter 的全新導航和路由系統

本文說明 Flutter 的全新 Navigator 和 Router API 如何運作。如果您關注 Flutter 的公開 設計文件,您可能已經看到這些新功能被稱為 Navigator 2.0 和 Router。我們將探討這些 API 如何讓您更精細地控制應用程式中的螢幕,以及如何使用它們來解析路由。

這些新的 API 並不是重大變更,它們只是添加了一個新的 聲明式 API。在 Navigator 2.0 之前,很難推送或彈出多個頁面,或移除當前頁面下方的一個頁面。但是,如果您對當前 Navigator 的運作方式感到滿意,您可以繼續以相同的方式(命令式)使用它。

Router 提供了從底層平台處理路由和顯示適當頁面的能力。在本文中,Router 被配置為解析瀏覽器 URL 以顯示適當的頁面。

本文將幫助您選擇最適合您的應用程式的 Navigator 模式,並說明如何使用 Navigator 2.0 來解析瀏覽器 URL 並完全控制活動頁面堆疊。本文中的練習展示了如何構建一個應用程式,該應用程式可以處理來自平台的進站路由,並管理應用程式的頁面。以下 GIF 顯示了示例應用程式的運作方式:

如果您使用 Flutter,您可能正在使用 Navigator,並且熟悉以下概念:

  • Navigator - 一個管理 Route 物件堆疊的 Widget。
  • Route - 由 Navigator 管理的一個物件,表示一個螢幕,通常由 MaterialPageRoute 等類別實作。

在 Navigator 2.0 之前,Route 被推送和彈出到 Navigator 的堆疊上,使用 命名路由匿名路由。接下來的幾節將簡要回顧這兩種方法。

匿名路由

大多數行動應用程式會在彼此之上顯示螢幕,就像一個堆疊一樣。在 Flutter 中,使用 Navigator 就可以輕鬆實現這一點。

MaterialApp 和 CupertinoApp 在幕後都已使用 Navigator。您可以使用 Navigator.of() 存取 Navigator,或使用 Navigator.push() 顯示一個新的螢幕,並使用 Navigator.pop() 返回上一個螢幕:

當呼叫 push() 時,DetailScreen Widget 會被放置在 HomeScreen Widget 的頂部,如下所示:

上一個螢幕 (HomeScreen) 仍然是 Widget 樹的一部分,因此與其關聯的任何 State 物件都會在 DetailScreen 可見時保留下來。

命名路由

Flutter 也支援命名路由,這些路由在 MaterialApp 或 CupertinoApp 的 routes 參數中定義:

這些路由必須預先定義。雖然您可以 將參數傳遞給命名路由,但您無法從路由本身解析參數。例如,如果應用程式在網頁上運行,您無法從像 /details/:id 這樣的路由中解析 ID。

使用 onGenerateRoute 的進階命名路由

處理命名路由的更靈活方法是使用 onGenerateRoute。此 API 使您能夠處理所有路徑:

以下是完整的示例:

這裡,settings 是 RouteSettings 的一個實例。name 和 arguments 字段是呼叫 Navigator.pushNamed 時提供的數值,或者設定為 initialRoute 的數值。

Navigator 2.0 API 為框架添加了新的類別,以便使應用程式的螢幕成為應用程式狀態的函數,並提供從底層平台(例如 Web URL)解析路由的能力。以下是新功能的概述:

  • Page - 一個不可變的物件,用於設定導航器的歷史堆疊。
  • Router - 配置 Navigator 要顯示的頁面列表。通常,這個頁面列表會根據底層平台或應用程式狀態的變化而變化。
  • RouteInformationParser,它會從 RouteInformationProvider 中獲取 RouteInformation,並將其解析為一個使用者定義的資料類型。
  • RouterDelegate - 定義 Router 如何了解應用程式狀態變化以及如何對它們做出響應的應用程式特定行為。它的工作是監聽 RouteInformationParser 和應用程式狀態,並使用當前的 Page 列表構建 Navigator。
  • BackButtonDispatcher - 向 Router 報告後退按鈕按下。

下圖顯示了 RouterDelegate 如何與 Router、RouteInformationParser 和應用程式的狀態交互:

以下是這些部分如何交互的示例:

  1. 當平台發出一個新的路由(例如,「books/2」)時,RouteInformationParser 會將其轉換為一個抽象資料類型 T,您可以在您的應用程式中定義這個資料類型(例如,一個名為 BooksRoutePath 的類別)。
  2. RouterDelegate 的 setNewRoutePath 方法會使用這個資料類型被呼叫,並且必須更新應用程式狀態以反映變化(例如,透過設定 selectedBookId)並呼叫 notifyListeners。
  3. 當呼叫 notifyListeners 時,它會告訴 Router 重新建立 RouterDelegate(使用其 build() 方法)。
  4. RouterDelegate.build() 返回一個新的 Navigator,其頁面現在反映了應用程式狀態的變化(例如,selectedBookId)。

本節將引導您完成使用 Navigator 2.0 API 的練習。我們將最終獲得一個應用程式,該應用程式可以與 URL 列保持同步,並處理來自應用程式和瀏覽器的後退按鈕按下,如以下 GIF 所示:

若要繼續操作,請 切換到 master channel使用 Web 支援建立一個新的 Flutter 專案,並將 lib/main.dart 的內容替換為以下內容:

頁面

Navigator 在其建構函數中有一個新的 pages 參數。如果 Page 物件列表發生變化,Navigator 會更新路由堆疊以匹配。若要了解這如何運作,我們將構建一個顯示書籍列表的應用程式。

在 _BooksAppState 中,保留兩個狀態資訊:書籍列表和選中的書籍:

然後在 _BooksAppState 中,返回一個帶有 Page 物件列表的 Navigator:

由於這個應用程式有兩個螢幕,一個是書籍列表,另一個是顯示詳細資訊的螢幕,因此如果選中了書籍,請添加第二個(詳細資訊)頁面(使用 集合中的 if):

請注意,頁面的鍵由書籍物件的數值定義。這告訴 Navigator,當書籍物件不同時,這個 MaterialPage 物件與另一個物件不同。如果沒有唯一的鍵,框架無法確定何時顯示不同 Page 之間的轉場動畫。

注意:如果您願意,您也可以擴展 Page 以自訂行為。例如,此頁面添加了一個自訂轉場動畫:

最後,在沒有提供 onPopPage 回撥的情況下提供 pages 參數是一個錯誤。此函數在呼叫 Navigator.pop() 時被呼叫。它應該用於更新狀態(決定頁面列表的狀態),並且必須呼叫路由上的 didPop 來確定彈出是否成功:

在更新應用程式狀態之前檢查 didPop 是否失敗非常重要。

使用 setState 會通知框架呼叫 build() 方法,該方法在 _selectedBook 為 null 時返回一個帶有單個頁面的列表。

以下是完整的示例:

就目前而言,這個應用程式只允許我們以聲明式的方式定義頁面堆疊。我們無法處理平台的後退按鈕,並且瀏覽器的 URL 在我們導航時不會改變。

Router

到目前為止,應用程式可以顯示不同的頁面,但它無法處理來自底層平台的路由,例如,如果使用者在瀏覽器中更新 URL。

本節將說明如何實作 RouteInformationParser、RouterDelegate 並更新應用程式狀態。設定好後,應用程式將與瀏覽器的 URL 保持同步。

資料類型

RouteInformationParser 會將路由資訊解析為一個使用者定義的資料類型,因此我們將首先定義它:

在這個應用程式中,應用程式中的所有路由都可以使用單個類別來表示。相反,您可能選擇使用實作超類別的不同類別,或以其他方式管理路由資訊。

RouterDelegate

接下來,添加一個擴展 RouterDelegate 的類別:

在 RouterDelegate 上定義的泛型類型是 BookRoutePath,它包含決定要顯示哪些頁面所需的所有狀態。

我們需要將一些邏輯從 _BooksAppState 移動到 BookRouterDelegate,並建立一個 GlobalKey。在這個示例中,應用程式狀態直接儲存在 RouterDelegate 上,但也可以分開到另一個類別中。

為了在 URL 中顯示正確的路徑,我們需要根據應用程式的當前狀態返回一個 BookRoutePath:

接下來,RouterDelegate 中的 build 方法需要返回一個 Navigator:

onPopPage 回撥現在使用 notifyListeners 而不是 setState,因為這個類別現在是 ChangeNotifier,而不是 Widget。當 RouterDelegate 通知其監聽器時,Router Widget 也會被通知 RouterDelegate 的 currentConfiguration 已更改,並且需要再次呼叫其 build 方法以構建一個新的 Navigator。

_handleBookTapped 方法也需要使用 notifyListeners 而不是 setState:

當一個新的路由被推送到應用程式時,Router 會呼叫 setNewRoutePath,這讓我們的應用程式有機會根據路由的變化來更新應用程式狀態:

RouteInformationParser

RouteInformationParser 提供了一個鉤子來解析進站路由 (RouteInformation) 並將其轉換為使用者定義的類型 (BookRoutePath)。使用 Uri 類別來處理解析:

此實作特定於此應用程式,而不是通用的路由解析解決方案。稍後將詳細說明。

若要使用這些新的類別,我們使用新的 MaterialApp.router 建構函數,並傳遞我們自定義的實作:

以下是完整的示例:

在 Chrome 中運行此示例現在會顯示路由的導航方式,並在手動編輯 URL 時導航到正確的頁面。

TransitionDelegate

您可以提供 TransitionDelegate 的自定義實作,它會自訂當頁面列表發生變化時路由在螢幕上出現(或從螢幕中移除)的方式。如果您需要自訂此功能,請繼續閱讀,但如果您對預設行為感到滿意,您可以跳過本節。

為 Navigator 提供一個自訂 TransitionDelegate,它定義了所需的行為:

例如,以下實作會禁用所有轉場動畫:

此自定義實作會覆蓋 resolve(),它負責將各種路由標記為推送、彈出、添加、完成或移除:

  • markForPush - 使用動畫轉場顯示路由
  • markForAdd - 使用 動畫轉場顯示路由
  • markForPop - 使用動畫轉場移除路由,並使用結果完成它。在這個上下文中,「完成」意味著結果物件會被傳遞給 AppRouterDelegate 上的 onPopPage 回撥。
  • markForComplete - 使用無轉場移除路由,並使用結果完成它
  • markForRemove - 使用無動畫轉場移除路由,並且不完成。

此類別只會影響 聲明式 API,這就是為什麼 後退 按鈕仍然顯示轉場動畫的原因。

此示例如何運作: 此示例會查看新的路由和即將離開螢幕的路由。它會遍歷 newPageRouteHistory 中的所有物件,並使用 markForAdd 將它們標記為無轉場動畫地添加。接下來,它會遍歷 locationToExitingPageRoute 映射的數值。如果它找到一個被標記為 isWaitingForExitingDecision 的路由,那麼它會呼叫 markForRemove 來指示該路由應該使用無轉場和無完成的方式移除。

以下是完整的示例(Gist)

嵌套路由

這個更大的演示展示了如何在另一個 Router 中添加 Router。許多應用程式需要為 BottomAppBar 中的目標設定路由,以及為其上方的視圖堆疊設定路由,這 需要兩個 Navigator。若要做到這一點,應用程式會使用一個應用程式狀態物件來儲存應用程式特定的導航狀態(選中的菜單索引和選中的書籍物件)。此示例還展示了如何配置哪個 Router 處理後退按鈕。

嵌套路由示例(Gist)

接下來要做什麼

本文探討了如何為特定應用程式使用這些 API,但也可以用於構建一個更高階的 API 套件。我們希望您能加入我們,探索建立在這些功能之上的更高階 API 能夠為使用者做些什麼。


學習 Flutter 的全新導航和路由系統 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Integration Testing with flutter_driver

【文章內容使用 Gemini 1.5 Pro 自動產生】

使用 flutter_driver 進行整合測試

單元測試確保應用程式的個別部分按預期運作,但整個應用程式呢?這就是整合測試發揮作用的地方。

Flutter driver

如果您從未在 Flutter(或任何地方)進行過整合測試,別擔心!在 Flutter 中為您的應用程式添加整合測試是一項簡單的任務。有一組非常有用的文章將引導您。

  1. 整合測試簡介:這個東西是什麼,我該如何設定?
  2. 處理捲動:我該如何處理捲動到某個東西的常見模式?

到達這一點可以確保您的應用程式可以正常運行、可以到達特定螢幕、特定操作會產生一些結果,等等。您創建的測試取決於您的需求。您可以使用一個測試打開應用程式的所有螢幕,或者使用測試完成特定使用者旅程。

但我們可以使用 flutter_driver 進一步進行效能測試。

效能分析

只需將您的測試用 flutterDriver.traceAction 包裹起來,就會在您的測試運行時記錄您的應用程式的效能。此輸出資料(以 JSON 格式)可以在持續整合 (CI) 測試中使用,以確保例如某些指標保持在特定臨界值之上或之下。該資料還可以用於除錯效能問題。有關如何進行效能分析的詳細資訊,請參閱整合測試文章,效能分析

作為參考,Flutter Gallery 有個 整合測試,會瀏覽所有演示並為其中的一部分捕獲效能指標。

螢幕截圖測試

螢幕截圖測試是一種渲染 UI、捕獲輸出螢幕截圖,然後將結果與預期圖片進行比較。為您的應用程式添加螢幕截圖測試的一個簡單方法是使用 flutterDriver.screenshot。若要了解更多資訊並查看實際的程式碼範例,請參閱 Medium 文章,使用 Flutter Driver 測試 Flutter UI,作者是社群成員 Darshan Kawar。

此方法可以輕鬆整合到您的持續整合測試設定中,以防止 UI 回歸。Flutter Gallery 有幾個 螢幕截圖測試 和一個 GitHub 工作流程設定,用於自動測試進來的 PR。

a11y 測試

a11y 或無障礙測試是一種可用性測試,用於確保應用程式可以被有障礙的人使用,例如視力障礙、聽力障礙、肢體障礙,等等。使用 flutterDriver.getSemanticsId 驗證語義標籤,例如,驗證所有圖片是否都有語義標籤。

在 Darshan Kawar 的另一篇很棒的文章中,了解更多關於無障礙測試的資訊,在 Flutter 中開發和測試無障礙應用程式

i18n 測試

i18n 或國際化測試是測試應用程式是否可以在不同的語言和地區使用而無需任何變更的過程。

根據您的本地化程式碼的設定方式,您可以使用本地化的委派來更改語言環境。

ExampleAppLocalizationsDelegate.load(Locale('fr'));

或者,在使用 MaterialApp 時,只需允許覆蓋應用程式的語言環境,以便在不同的語言環境中運行。

在啟動應用程式的驅動程式檔案中,設定語言環境。

額外提示

想知道頁面上是否存在某個元素嗎?

您可以選擇任何適合您的應用程式的超時時間。

結語

到目前為止,您應該對 flutter_driver 的功能有了很好的了解。您可以結合方法以滿足您的需求;例如,使用不同的語言環境執行螢幕截圖測試。如果我們遺漏了什麼,請在評論中告訴我們!若要了解更多資訊,請參閱 flutter_driver API 文件。

關於作者:Pierre-Louis 是滑鐵盧大學的應屆大學畢業生。他住在慕尼黑,是維護 Flutter Material 函式庫的團隊中的一員。他對 UI、UX 和應用程式開發非常感興趣。您可以在 LinkedIn GitHub 上與他聯繫。


使用 flutter_driver 進行整合測試 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Announcing Flutter Windows Alpha

【文章內容使用 Gemini 1.5 Pro 自動產生】

宣布 Flutter Windows Alpha 版本

我們的使命是為開發人員提供一個開源、高生產力的框架,用於在任何平台上構建美觀的原生應用程式。到目前為止,我們已經為 Android 和 iOS 發佈了生產級別的支援,擁有 8 個穩定版本,僅 Google Play 商店就發佈了超過 100,000 個應用程式。我們繼續擴大我們的關注範圍,包括其他平台,包括網頁、macOS 和 Linux。今天,我們很高興宣布 Flutter 的另一個目標是為 Windows 發佈 Flutter 支援的 alpha 版本。

Windows 仍然是桌面和筆記型電腦設備的熱門選擇,微軟報告稱 超過 10 億台設備運行 Windows 10。我們自己的統計數據顯示,超過一半的 Flutter 開發人員使用 Windows,因此它自然成為 Flutter 的目標。原生桌面支援為 Flutter 開啟了各種令人興奮的可能性,包括改進的開發人員工具、降低新使用者的摩擦,當然還有可以從單個程式碼庫到達使用者可能擁有的任何設備的應用程式。

將 Windows 加入 Flutter

如我們的 架構概述 中所述,Flutter 是一個跨平台 UI 工具包,旨在允許在 iOS 和 Android 等操作系統之間重複使用程式碼,同時也允許應用程式直接與底層平台服務進行互動。目標是讓開發人員能夠交付高效能的應用程式,這些應用程式在不同的平台上感覺自然,在存在差異的地方擁抱差異,同時盡可能共享程式碼。Flutter 的核心是引擎,它支援支援所有 Flutter 應用程式所必需的基本元素。引擎負責在需要繪製新畫面時對合成場景進行光柵化。它提供了 Flutter 核心 API 的低階實作,包括圖形、文字佈局、檔案和網路 I/O、輔助功能支援、外掛架構,以及 Dart 執行時和編譯工具鏈。

我們添加到 Flutter 的每個新平台都會使用新的服務擴展核心框架,以便它在該平台上發光。我們從 Android 和 iOS 開始,採用 Material Design 以及基於觸控的、以行動為中心的使用者介面,該介面旨在讓兩個行動平台上的像素完美。為網頁、Windows、macOS 和 Linux 添加對桌面外觀尺寸的支援帶來了全新的服務集,包括對鍵盤、滑鼠、滑鼠滾輪和控制器(輸入端)的強大支援,以及在網頁和桌面應用程式附帶的較大螢幕尺寸上適應或甚至最有效工作的 Widget。

此外,每個新平台不僅會影響 Flutter 框架和引擎,還會影響許多其他方面:

  • 工具鏈更新:將新目標添加到 CLI 和 IDE 工具(在本例中為 Windows)
  • 外殼程式:支援透過 WM_* 訊息處理來自 Windows 的輸入,以及透過 ANGLE 輸出,ANGLE 使用 Skia 以原生速度渲染到底層 DirectX 表面
  • 執行器:每個專案都有一個外殼程式應用程式,適用於受支援的目標。對於 Windows,它是一個載入您的 Flutter 程式碼並在執行時執行的 Win32/C++ 程式。如果您需要,這是將原生程式碼添加到您的應用程式的好地方。
  • 外掛:外掛是 Dart 程式碼和原生程式碼的混合,適用於外掛支援的每個平台。需要為編譯到 Windows 上的 Flutter 應用程式中的每個外掛添加該原生程式碼。

此 alpha 版本提供了堅實的基礎,我們將在接下來的幾個月內將其穩定下來。透過對 Windows 7 及更高版本的支援,我們希望這能讓具有冒險精神的開發人員擁有一些可以開始使用的東西。

探索一些範例應用程式

若要查看 Flutter 對 Windows 的支援,您可能需要嘗試一些我們建立的範例應用程式,這些應用程式在我們的最新支援下可以在 Windows 上良好運行。第一個是 Flokk 應用程式,它是與 gskinner.com 的設計師和開發人員共同建立的。目標是透過建立一個創新的、美觀的 Flutter 桌面應用程式來證明 Flutter 已經準備好迎接桌面。Flokk 是一個與您的真實 Google 聯繫人資料配合使用的應用程式,同時還顯示了您的聯繫人在 GitHub 和 Twitter 上的活動。

如果您想在 Windows 電腦上使用 Flokk 應用程式,您可以 在 GitHub 上下載最新版本。如果您想了解 gskinner 如何構建這個應用程式,請參閱他們的出色部落格文章:Flokk - 我們如何使用 Flutter 建立桌面應用程式

此外,Flutter Gallery 應用程式(我們用於所有 Flutter 事物的展示應用程式)最近已完全重寫,以添加對桌面外觀尺寸的支援。這讓我們可以檢查它在 網頁 以及 Windows、macOS 和 Linux 上是否運行良好。

Gallery 中的許多研究展示了我們在使用 Flutter 設計自己的 Windows 應用程式時推薦的不同應用程式樣式的想法。當您找到喜歡的內容時,程式碼可在 GitHub 上獲得

開始使用 Flutter for Windows

按照 Windows 安裝說明 安裝 Flutter SDK 以開始。若要以 Windows 桌面為目標,您首先需要 安裝桌面文件中描述的工具。預設情況下,Flutter 假設您正在構建生產軟體,並且沒有配置為開發 Windows 應用程式。但是,這可以輕鬆地從命令行中修復:

1
2
3
$ flutter channel dev
$ flutter upgrade
$ flutter config --enable-windows-desktop

第一個命令將 Flutter 設定為使用實驗性品質的「dev」頻道(而不是預設的「stable」頻道)。這讓您可以使用仍處於 alpha 階段的平台支援,例如 Windows。第二個命令會在該頻道上提取最新的位元。第三個命令會在您的電腦上啟用 Windows 應用程式開發。

設定好之後,每次您使用 Android Studio 或 Visual Studio Code 的擴展支援,或者從命令行建立新的 Flutter 應用程式時,它都會建立一個 windows 子資料夾。

如果您感到好奇,在 Windows 上運行預設應用程式看起來像這樣:

最後,建立應用程式後,構建它會建立一個發佈模式的原生 EXE 檔案以及必要的支援 DLL。在那之後,如果您想嘗試在任何 Windows 10 電腦上運行您的新 Windows 應用程式,甚至那些沒有安裝 Flutter 的電腦,您可以 按照將必要檔案壓縮成壓縮檔的步驟 進行操作。

Windows 的外掛

儘管我們才剛剛達到 alpha 版本,但 Flutter 社群已經開始為 Windows 開發外掛。這裡有一些:

使用這些外掛的好處是,它們中的大多數也支援其他 Flutter 平台,這使您可以將應用程式定位到 Android、iOS、網頁等以及 Windows。此外,儘管 pub.dev(Dart 和 Flutter 的套件管理器)上約三分之一的可用套件是包含平台特定程式碼的外掛,但大多數卻不是。例如,許多品質最高、使用最廣泛的套件 是 Flutter Favorite 程式的一部分,它們中的大多數都在 Windows 上運行。如果您想查看在 Windows 上運行的套件的完整列表,您可以 在 pub.dev 上運行此查詢

與 Windows 互操作

如果您想為 Windows 建立自己的外掛,您可以。在您位於 dev 頻道並為您的電腦啟用 Windows 後,您可以使用以下命令開始:

1
$ flutter create --template plugin --platforms windows hello_plugin

在那之後,您就可以將 Flutter 程式碼添加到外掛專案中的 lib 子資料夾,以及將 Windows 程式碼添加到 Windows 子資料夾中。您將透過 平台通道 在兩個堆疊之間進行通訊,平台通道本質上是 Dart 和 C++ 程式碼之間的訊息傳遞。若要查看此方面的精心製作的範例,請 查看 url_launcher 實作

但是,平台通道並不是您與 Windows 互操作的唯一選擇。如果您願意,可以使用 Dart FFI(外部函數介面) 來載入函式庫並呼叫 C 樣式的 API,例如 Win32 API。與使用平台通道的 url_launcher 不同,path_provider 外掛是使用 FFI 實作的,如您在 GitHub 儲存庫 中看到的那樣。FFI 不需要在 Dart 和 C++ 之間來回切換,它讓您能夠撰寫程式碼來直接匯入您想要的 API。例如,以下是呼叫 MessageBox API 的程式碼:

此程式碼不會像平台通道那樣在兩個執行緒之間轉換時產生額外的開銷。FFI 包括對許多不同類型的 API 的支援,包括 Win32、WinRT 和 COM。但在您開始運作並封裝整個基於 C 的 Windows API 之前,請查看 win32 外掛,它已經在進行這項工作。事實上,path_provider 外掛本身是使用 win32 外掛實作的。有關 win32 外掛的開發方式及其運作方式的詳細資訊,請查看部落格文章:使用 Dart FFI 的 Windows 樂趣

Flutter for Windows 資源

無論您在 Flutter for Windows 之旅中處於哪個階段,您都應該確保 閱讀 flutter.dev 上的桌面文件,其中包括最新的詳細資訊。此外,您還需要嘗試使用 Flutter 用於撰寫以 Windows、macOS 和 Windows 桌面為目標的應用程式的 Codelab,其中包含用於實際情況的程式碼,例如使用 OAuth進行身份驗證、存取 GitHub API 以及使用 GraphQL。或者,若要查看另一個在 Windows 上運行的 Flutter 桌面程式碼的好範例,請 查看 Photo Search 範例

它使用標準的 Windows 檔案開啟對話方塊、樹狀檢視 Widget、分割器 Widget,並將結果與實際的 REST API 整合。

對於其他有用的以桌面為導向的 Widget,我們建議使用 menubar 外掛NavigationRail WidgetDataTable Widget。您可能也對 InteractiveViewer Widget 感興趣,它對滑鼠手勢(用於平移和縮放子 Widget)提供了完整的桌面支援。

另一組有用的 Widget 可以探索的是來自 SyncFusion 的 Widget,SyncFusion 在 Windows 開發社群中已經很有名。他們提供了大量企業級品質的 Widget,用於建立圖表、量規、資料網格等等。

這些 Widget 具有社群和企業許可證,因此您可以為您的專案找到合適的選項。

Flutter for Windows 的實際應用

除了 Windows 的套件和外掛(以及總體上的 Flutter 桌面)之外,Flutter 開發人員也一直在構建很棒的應用程式來以 Windows 為目標,例如來自 Invoice Ninja 的這個實驗性版本:

Invoice Ninja 是一家發票公司,它依賴 Flutter 來帶來收入。他們目前在生產環境中以 Android 和 iOS 為目標,並且為您提供了一個可以嘗試的 基於網頁的演示,但他們也展望著提供桌面版本。

“在 Invoice Ninja,我們過去一直努力僅支援網頁和行動,一度維護了三個獨立的程式碼庫。使用 Flutter,更近一步使用 Flutter 桌面,我們已經能夠使用單個程式碼庫為所有主要平台構建應用程式。我們不僅基本獲得了應用程式的免費桌面版本,而且它的效能也最佳!”
— Hillel Coren,Invoice Ninja 的共同創辦人

如果您對實作在行動和桌面外觀尺寸上運行的實際、產生收入的 Flutter 應用程式感興趣,程式碼可在 GitHub 上獲得

Aartos 是另一家正在構建很棒的產品的公司,包括一個使用 Flutter 撰寫的多平台客戶端 的即時無人機偵測系統。以下是一個早期版本的 Windows 客戶端,它與行動客戶端並排運行:

這兩個版本都以 iOS 和 Windows 為目標,共享完全相同的程式碼庫。

如果您是經驗豐富的 Flutter 開發人員,並且發現自己經常在不同版本的 Flutter 之間切換;例如,一個版本用於發佈您的生產行動應用程式,另一個版本用於嘗試 Windows alpha 版本,那麼您可能會喜歡 Flutter 版本管理器,Flutter 版本管理器現在附帶了 可以下載的 Windows GUI

這個工具是開源的,因此您可以 親眼看看 Leo 如何讓它看起來如此棒。

未來展望

現在我們已經發佈了 alpha 版本,我們的注意力將轉移到完成功能集並穩定產品以供發佈。作為一個開源專案,您可以在我們的 GitHub 網站上追踪我們的貝他版進度,其中包括需要完成的剩餘工作,包括輔助功能、全球化和本地化、增強的鍵盤和文字處理、對命令行參數的支援等等。

除了支援經典的 Win32 API 之外,我們還在嘗試 基於 UWP 的 Flutter 外殼版本,這讓 Flutter 可以到達更多種類的基於 Windows 的設備,包括 Xbox。作為該實驗的一部分,本週我們向 Windows 商店發佈了一個基於 UWP 的 Flutter Gallery 版本

以下螢幕截圖顯示了基於 UWP 的 Flutter Gallery 在 Xbox 上運行:

以下是在運行 Windows 10X 模擬器 的雙螢幕 Windows 設備上運行的相同應用程式:

您可以在 GitHub 上閱讀更多有關 Flutter for UWP 進度

總結

透過此版本,我們將 Flutter 的強大功能帶到了 Windows,它具有用於開發人員生產力的宣告式、可組合、反應式框架,以及 Material 規格的適應性實作,因此您可以讓應用程式以您想要的方式呈現和感覺,以及完整的 Flutter 開發和除錯工具集。完成後,您的應用程式將編譯為原生 64 位程式碼,您可以像任何其他原生應用程式一樣將其封裝並帶到其他 Windows 電腦。最後,您可以使用相同的程式碼庫來建立一個以 Android、iOS、網頁、macOS 和 Linux 為目標的應用程式。

如果您想開始使用 Flutter 建立 Windows 應用程式,我們非常歡迎您的 回饋!如果您想使用您的 Windows 專業知識來 構建熱門外掛的 Windows 實作,或者為 Flutter 建立一些以 Windows 為中心的工具(也許是一個從 flutter build windows 命令的輸出建立 MSIX 的 CLI…),也非常歡迎!

有了 Flutter 對 Windows 的新支援,您打算構建什麼?


宣布 Flutter Windows Alpha 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/

【文章翻譯】Handling Web Gestures in Flutter

【文章內容使用 Gemini 1.5 Pro 自動產生】

在 Flutter 中處理網頁手勢

Flutter 發佈了網頁支援,讓您可以建立動態網站。有了 Flutter 網頁、MacOS、Linux 和 Windows 作業系統(甚至在連接到滑鼠、鍵盤或其他週邊設備的行動裝置上),您現在可以存取平台特定行為,包括焦點支援、鍵盤輸入、自訂滑鼠游標和懸停輸入。但是,您如何在 Flutter 中使用這些平台行為呢?

本文說明如何使用 FocusableActionDetector Widget,它處理鍵盤輸入、焦點輸入、懸停輸入和自訂滑鼠游標。

如何使用 FocusableActionDetector Widget

FocusableActionDetector Widget 允許您處理幾個網頁特定動作,包括:

  1. 鍵盤輸入
  2. 焦點輸入
  3. 懸停輸入
  4. 自訂滑鼠游標

DartPad #1 範例中查看 FocusableActionDetector 的實際應用。

**注意:**本文使用了兩個 DartPad 範例,儘管每個範例都有多個連結。[DartPad #1](https://dartpad.dev/d16345202d0e26d40fe14904657dc24a) 展示了 `FocusableActionDetector` 範例,而 [DartPad #2](https://dartpad.dev/5544c57e20454fe212e3ec5cf10b1f0a) 展示了 `FocusableActionDetector` 的懸停和焦點支援。

鍵盤輸入

Flutter 允許您定義自訂動作來處理鍵盤輸入。假設您想要有一個隱藏的選單,或者您的應用程式必須處理大量的自訂鍵盤輸入。使用 FocusableActionDetector Widget 很容易做到這一點。

首先,加入以下匯入:

1
import 'package:flutter/services.dart';

接下來,從調用 FocusableActionDetector 的類別中,初始化兩個私有變數。這些變數將鍵盤輸入映射到所需的動作:

1
Map<LogicalKeySet, Intent> _shortcutMap;
1
Map<Type, Action<Intent>> _actionMap;

定義一個 FocusableActionDetector Widget。請注意,自訂鍵盤輸入僅在子 Widget 獲得焦點時才會啟用:

1
2
3
4
5
FocusableActionDetector(
actions: _actionMap,
shortcuts: _shortcutMap,
child:
)

此類別將列舉和鍵盤輸入類型映射到 Intent 類別。以下設定是必要的,因為 actionsshortcuts 參數需要一個繼承自 Intent 的類別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class _ShowSecretMessageIntent extends Intent {
const _ShowSecretMessageIntent({@required this.type});
const _ShowSecretMessageIntent.P() : type = _SecretMessageType.P;
const _ShowSecretMessageIntent.L() : type = _SecretMessageType.L;
const _ShowSecretMessageIntent.A() : type = _SecretMessageType.A;
const _ShowSecretMessageIntent.T() : type = _SecretMessageType.T;
const _ShowSecretMessageIntent.F() : type = _SecretMessageType.F;
const _ShowSecretMessageIntent.O() : type = _SecretMessageType.O;
const _ShowSecretMessageIntent.R() : type = _SecretMessageType.R;
const _ShowSecretMessageIntent.M() : type = _SecretMessageType.M;
final _SecretMessageType type;
}

enum _SecretMessageType {
P,
L,
A,
T,
F,
O,
R,
M,
}

initState 方法中,初始化之前定義的映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void initState() {
_shortcutMap = <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.keyP):
const _ShowSecretMessageIntent.P(),
LogicalKeySet(LogicalKeyboardKey.keyL):
const _ShowSecretMessageIntent.L(),
LogicalKeySet(LogicalKeyboardKey.keyA):
const _ShowSecretMessageIntent.A(),
LogicalKeySet(LogicalKeyboardKey.keyT):
const _ShowSecretMessageIntent.T(),
LogicalKeySet(LogicalKeyboardKey.keyF):
const _ShowSecretMessageIntent.F(),
LogicalKeySet(LogicalKeyboardKey.keyO):
const _ShowSecretMessageIntent.O(),
LogicalKeySet(LogicalKeyboardKey.keyR):
const _ShowSecretMessageIntent.R(),
LogicalKeySet(LogicalKeyboardKey.keyM):
const _ShowSecretMessageIntent.M(),
};
_actionMap = <Type, Action<Intent>>{
_ShowSecretMessageIntent: CallbackAction<_ShowSecretMessageIntent>(
onInvoke: _actionHandler,
),
};
}

動作處理程序將鍵盤輸入連結到應用程式中的特定函數。每次按下字元時,都會在應用程式的主要部分加入一個展開的容器。但在您的應用程式中,您可以將其用於更具創意目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void _actionHandler(_ShowSecretMessageIntent intent) {
switch (intent.type) {
case _SecretMessageType.P:
setState(() {
children.add(
Expanded(
child: Container(
color: Colors.orange,
child: Text('Press L'),
),
),
);
});
break;

}
}

焦點輸入

FocusableActionDetector Widget 允許您在指定 Widget 獲得焦點時執行動作呼叫。

焦點樹允許您使用 Tab 鍵在應用程式中瀏覽。這讓有視力障礙的人能夠瀏覽您的網站。

FocusableActionDetector 子 Widget 被選為初始焦點,當其作用域中的其他節點目前沒有焦點時。如果您希望您的自訂鍵盤輸入在子 Widget 是否有焦點都不受影響的情況下運作,則將 autofocus 參數變數設定為 true 很重要。這是因為,預設情況下,此 FocusableActionDetector Widget 將不會獲得焦點,因此您將無法使用自訂鍵盤輸入:

1
2
3
4
FocusableActionDetector(
autofocus: true,
child : …
),

假設您希望子 Widget 在獲得焦點時具有不同的顏色,以識別此 Widget 目前是否有焦點。如果您需要在 Widget 獲得焦點時設定自訂動作,請使用 onShowFocusHighlight 屬性。

DartPad #2 範例展示了如何使用 onShowFocusHighlight 屬性。

焦點是一個進階主題。透過閱讀 Focus 類別的 API 文件,您可以進一步了解焦點。

懸停輸入

FocusableActionDetector Widget 允許您處理懸停輸入。這樣一來,每當游標懸停在特定 Widget 上時,就會發生自訂動作。與 onShowFocusHighlight 屬性類似,onShowHoverHighlight 屬性讓您能夠在滑鼠游標懸停在特定 Widget 上時建立自訂動作。

查看 DartPad #2 範例,它展示了如何實作自訂懸停和焦點動作。

DartPad #2 範例中,每當滑鼠懸停在 Widget 上時,它就會更改 Widget 的顏色。如果您使用 Tab 鍵在應用程式中瀏覽,您會注意到,與目前被懸停的 Widget 相比,獲得焦點的 Widget 會對 Widget 應用不同的陰影。

這意味著您可以在 Widget 獲得焦點或滑鼠游標懸停在該 Widget 上時,執行不同的自訂動作。如果您將滑鼠懸停在同一個 Widget 上並使它獲得焦點,您就可以看出來。該 Widget 會同時對該 Widget 應用兩個陰影。若要了解更多資訊,請查看 FocusableActionDetector 類別的 API 文件。

自訂滑鼠游標

FocusableActionDetector Widget 讓您能夠實作自訂滑鼠游標。例如,如果您希望使用者知道特定 Widget 是可拖曳的,更改滑鼠游標是一個很好的指示,表示您可以執行此操作。FocusableActionDetector Widget 具有 mouseCursor 參數。

1
mouseCursor: SystemMouseCursors.grabbing

DartPad #1 範例展示了 Flutter 中不同類型的滑鼠游標。點擊應用程式列中的任何圖示以嘗試使用。有九種不同的系統預設滑鼠游標:

  1. SystemMouseCursors.basic
  2. SystemMouseCursors.text
  3. SystemMouseCursors.click
  4. SystemMouseCursors.forbidden
  5. SystemMouseCursors.grab
  6. SystemMouseCursors.grabbing
  7. SystemMouseCursors.horizontalDoubleArrow(未在穩定版本中)
  8. SystemMouseCursors.verticalDoubleArrow(未在穩定版本中)
  9. SystemMouseCursors.none

結語

在 Flutter 中,您可以存取網頁特定 API。FocusableActionDetector Widget 讓您能夠在 Flutter 中處理網頁特定功能。若要進一步了解 Flutter 網頁,請查看 Medium 文章 Handling 404: Page not found error in Flutter

關於作者:Jose 最近從大學畢業,現在在 Material 工作,這是一個幫助團隊構建高品質數位體驗的設計系統。Jose 的團隊維護 Flutter 的 material 函式庫。若要了解更多資訊,請訪問 Jose 在 GitHubLinkedInYouTubeInstagram 上的頁面。


在 Flutter 中處理網頁手勢 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Supporting iOS 14 and Xcode 12 with Flutter

【文章內容使用 Gemini 1.5 Pro 自動產生】

使用 Flutter 支援 iOS 14 和 Xcode 12

與往常一樣,我們的目標是讓開發人員在 Flutter 上取得成功,無論您要鎖定的平台為何。自 6 月宣布 iOS 14 以來,我們一直在努力為 Flutter 加入支援此版本的新功能,包括更新 Xcode 12 支援,以及加入支援 iOS 14 的新功能和改進。

如果您的 iOS 14 應用程式使用 TextFieldCupertinoTextFieldTextFormField,您需要確保它使用 Flutter 1.20 或更高版本構建,以確保您的使用者不會受到不必要的剪貼簿通知訊息的困擾,這符合 Apple 更新的剪貼簿政策。

否則,現有的生產應用程式將在您的終端使用者 iOS 14 設備上順利運行。 但是,如果您今天要將開發手機升級到 iOS 14 並希望繼續與 Flutter 一起使用它,您有兩個選擇。

您的第一個選擇是使用穩定頻道上的 Flutter 最新修補程式版本 (1.20.4),它支援將應用程式部署到實體 iOS 14 設備進行開發。不幸的是,還需要另一個更具侵入性(因此風險更大)的變更才能在 iOS 14 上進行除錯和熱重載。我們將此修復推遲到我們下一個穩定版本,我們計劃在下週釋出。

您的第二個選擇是使用我們今天釋出的即將到來的 Flutter 1.22 的 beta 版。此版本包含對 iOS 14 的更全面支援,包括更新的視覺效果、應用程式片段和 Xcode 12 支援。當它發佈到穩定頻道時,我們將分享更多關於其功能集的信息,但在此之前,我們建議您在需要立即支援 iOS 14 的情況下使用此 beta 版本。

若要取得 Flutter 1.22 beta 版,請使用以下命令:

1
2
$ flutter channel beta
$ flutter upgrade

無論哪種情況,您都需要查看 針對 iOS 14 開發頁面,以獲取有關使用 Flutter 鎖定 iOS 14 的有用資訊。如果您在 iOS 14 中遇到任何問題,請 在 Flutter 儲存庫中提交議題。如果您有任何問題,請在 flutter-dev 群組 中發布。


使用 Flutter 支援 iOS 14 和 Xcode 12 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/

【文章翻譯】Exploring collections in Dart

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

探索 Dart 中的集合

如果您曾經調用 add()addAll()map()toList() 來建立列表或映射,您可能需要查看 collection ifcollection forspreads。去年,Dart 在 2.3 版中加入了這些功能

在本文中,我們將研究集合,探索這些新功能,並查看一些有趣的範例。透過掌握這些功能,您可以使您的程式碼更簡潔、更易於閱讀。

集合

首先,我們需要了解什麼是集合。集合 是一個包含其他物件的物件。例如:

  • List:具有長度的有序物件集合(也稱為 陣列
  • Set:唯一物件的無序集合
  • Map:鍵值對的無序集合
  • Queue:可以在兩端添加/移除物件的有序集合
  • SplayTreeMap:基於自平衡二元樹的鍵值對的有序集合

這些類型在 dart:collection 套件中可用。如需更多集合類型,請在 pub.dev 上查看 package:collection

這些集合類型都實作了 Iterable,它提供了一些常用行為,例如在集合中的每個物件上運行函數、獲取第一個物件、確定集合的長度等等。

集合字面量

Dart 支援用於構造三種類型集合的語法:列表字面量 ([])、映射字面量 ({}) 和集合字面量(也是 {})。

以下是一個列表字面量:

1
2
3
4
5
6
7
List<String> getArtists() {
return [
'Picasso',
'Warhol',
'Monet',
];
}

以下是一個映射字面量:

1
2
3
4
5
6
7
Map<String, String> getArtistsByPainting {
return {
'The Old Guitarist': 'Picasso',
'Orange Prince': 'Warhol',
'The Water Lily Pond': 'Monet',
};
}

以下是一個集合字面量,在 Dart 2.3 中加入:

1
2
3
4
5
6
7
Set<String> getArtistsSet() {
return {
'Picasso',
'Warhol',
'Monet',
};
}

如果您想知道為什麼映射和集合可以使用相同的 {} 語法,那是因為 Dart 使用 類型推斷 來區分。類型系統根據參數 a 和 b 的類型確定類型。它通常可以根據內容確定這一點——例如,{1} 顯然是一個 Set,而 {1: 2} 顯然是一個 Map

注意: 使用 {} 預設構造一個映射。要建立一個集合,您可以使用泛型類型註釋:<String>{}。使用兩個泛型類型參數則建立一個映射:<String, String>{}

元素的類型

集合字面量中的每個項目通常是一個值或表達式,但也可以是以下新功能之一:collection ifcollection forspread。所有這些都被稱為 元素

每個元素解包零個或多個項目,並將它們放入周圍的集合中。例如,一個字串字面量(例如「oatmeal」)會產生一個項目,但 collection for 會解包 0 個或多個項目。這些功能也可以以有趣的方式組合,我們將在下面探討。

Spreads

spread 接收一個集合(例如,一個列表),並將其內容放入周圍的集合中:

1
2
3
4
5
6
List<String> combineLists(List<String> a, List<String> b) {
return [
...a,
...b,
];
}

前面的程式碼相當於:

1
2
3
4
5
6
List<String> combineLists(List<String> a, List<String> b) {
var list = [];
list.addAll(a);
list.addAll(b);
return list;
}

您也可以在映射和集合字面量中使用 spreads:

1
2
3
4
5
6
Map<String, String> combineMaps(Map<String, String> a, Map<String, String> b) {
return {
...a,
...b,
};
}
1
2
3
4
5
6
Set<String> combineSets(Set<String> a, Set<String> b) {
return {
...a,
...b,
};
}

在映射和集合中,當發生衝突時,b 的內容會覆蓋 a 中的內容。例如,調用 combineMaps({'foo': 'bar'}, {'foo': 'baz'}) 會產生一個包含 {'foo': 'baz'} 的映射。

Null-aware spreads (…?)

null-aware spread 僅當運算符後的表達式為非 null 時才將內容加入到集合中:

1
2
3
4
5
6
List<String> combineIfExists(List<String> a, List<String> b) {
return [
...?a,
...?b,
];
}
1
2
3
4
void main() {
var result = combineIfExists(['foo'], null);
print(result); // [foo]
}

Collection if

使用 ifelseelse if 關鍵字根據條件將某些內容加入到集合中。以下是一個使用 collection if 的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Article {
String title;
DateTime date;

Article(this.title, this.date);

String toString() {
return [
if (title != null) title,
date.toString(),
].join(', ');
}
}

可以在末尾加入 else 關鍵字:

1
2
3
4
5
String toString() {
return [
if (title != null) title else '(no title)',
].join(', ');
}

請注意逗號的位置。逗號不能在 title 之後,因為 else 是同一個元素的一部分。將 ifelse 保持在一起,在逗號之前,可以將它們與集合中的下一個元素區分開來。

加入 else if 也可以:

1
2
3
4
5
6
7
8
9
10
11
String toString() {
return [
if (title != null) title else '(no title)',
if (date == null)
'(no date)'
else if (date.year == DateTime.now().year)
'this year'
else
'${date.year}',
].join(', ');
}

Collection for

最後,使用 for 關鍵字將序列插入到集合中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Article {
String title;
DateTime date;
List<String> tags;

Article(this.title, this.date, this.tags);

String toString() {
return [
title,
date.toString(),
for (var tag in tags) 'tag: $tag'
].join(', ');
}
}

在此範例中,for 表達式為 tags 列表中的每個項目加入一個字串。就像 Dart 中的普通 for 迴圈一樣,tags 表達式可以是任何 Iterable

Flutter 程式碼中的集合

如果您正在使用 Dart,很有可能您正在使用它來構建 Flutter 應用程式。由於這裡描述的功能是在設計時考慮到 Flutter 的,讓我們來看看一些 Flutter 程式碼。

重構 build() 方法

在 Flutter 中,通常在 build() 方法中構建 Widget 列表:

1
2
3
4
5
6
7
8
9
10
@override
Widget build(BuildContext context) {
var articleWidgets = articles
.map<Widget>((article) => ArticleWidget(article: article))
.toList();

return ListView(
children: articleWidgets,
);
}

可以使用 spread 重寫此程式碼:

1
2
3
4
5
6
7
8
Widget build(BuildContext context) {

return ListView(
children: [
...articles.map((article) => ArticleWidget(article:article))
],
);
}

或者使用 collection for

1
2
3
4
5
6
7
8
Widget build(BuildContext context) {
return ListView(
children: [
for (var article in articles)
ArticleWidget(article: article)
],
);
}

第一個程式碼片段使用 map()Article 類別轉換為 ArticleWidget 物件的集合,然後應用 spread 運算符將它們展開到周圍的列表中。在第二個範例中,collection for 運算符讓您可以更簡潔地表達這一點。

更大的 build() 方法

以下是一個更複雜的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Widget build(BuildContext context) {
var headerStyle = Theme.of(context).textTheme.headline4;

return Column(
children: [
if (article.title != null)
Text(article.title, style: headerStyle),
if (article.date != null)
Text(article.date.toString()),
Text('Tags:'),
for (var tag in article.tags)
Text(tag),
],
);
}

放置 Widget 到 Column 中的邏輯就在讀者可能期望的位置,並且節省了大量程式碼。在這些功能出現之前,實現相同行為最常用的方法是建立一個變數,並使用調用 add() 的普通 if 陳述式。

組合這些功能

這些功能可以以有趣的方式組合,如本節中的範例所示。以下是一些需要注意的事項:

  • 從語法上講,collection ifcollection forspread 是一個 單個元素——即使它最終建立了多個物件。
  • 任何表達式都可以放在 collection ifcollection for 的主體中。
  • 任何 元素 都可以放在 collection ifcollection for 的主體中。

結合使用 if 和 for

以下是一些使用 collection for 內部的 collection if 建立列表的程式碼。在這裡,如果每個文章的日期晚於特定日期,則將其加入到列表中:

1
2
3
4
5
6
7
8
List<Article> recentArticles(List<Article> allArticles) {
var ninetyDaysAgo = DateTime.now().subtract(Duration(days: 90));
return [
for (var article in allArticles)
if (article.date.isAfter(ninetyDaysAgo))
article
];
}

如果您更喜歡 spreads,則返回值可以寫成 ...allArticles.where((article) => article.date.isAfter(ninetyDaysAgo))

將 collection if 和 spreads 結合使用

collection if 接收單個元素,但如果您想包含多個元素,則可以使用 spread

1
2
3
4
5
6
7
8
9
10
Widget build(BuildContext context) {
return Column(
children: [
if (article.date != null) ...[
Icon(Icons.calendar_today),
Text('${article.date}'),
],
],
);
}

將集合功能與 async-await 結合使用

您也可以將非同步調用與集合字面量結合使用。例如,一個常見的模式是使用 Future.wait() 觸發一組非同步調用:

1
2
3
4
5
6
7
8
9
Future<Article> fetchArticle(String id);

Future<List<Article>> fetchArticles() async {
return Future.wait([
fetchArticle('1'),
fetchArticle('2'),
fetchArticle('3'),
]);
}

可以使用 collection for 改進該程式碼:

1
2
3
4
5
6
Future<List<Article>> fetchArticles(List<String> ids) async {
return Future.wait([
for (var id in ids)
fetchArticle(id),
]);
}

也可以在集合字面量中放入 await,儘管它會依次等待每個 Future

1
2
3
4
5
6
7
Future<List<Article>> fetchArticles(List<String> ids) async {
return [
// 一次獲取一個
for (var id in ids)
await fetchArticle(id),
];
}

前面的程式碼會依次等待,因為它相當於以下程式碼:

1
2
3
4
5
6
7
8
Future<List<Article>> fetchArticles() async {
return <Article>[
// 一次獲取一個
await fetchArticle('1'),
await fetchArticle('2'),
await fetchArticle('3'),
];
}

您也可以使用 await for 展開 Stream

1
2
3
4
5
6
7
8
9
10
11
Stream<String> get idStream => Stream.fromIterable(['1','2','3']);
Future<List<String>> gatherIds(Stream<String> ids) async {
return [
await for (var id in ids)
id
];
}

void main() async {
print(await gatherIds(idStream)); // [1, 2, 3]
}

這是 collection ifcollection forspreads 如何與語言的其他部分一起使用的另一個範例。如果您使用過 await for 陳述式,您可能會猜到其行為:它偵聽 Stream 中的新值,並將主體放入周圍的列表中。

進一步探索

希望這些技巧能幫助您編寫更乾淨的 Dart 程式碼。除了這裡提到的之外,還有更多使用這些功能的方法。如果您發現了一個好的技巧,請與社群分享在 Twitter 上提及 @dart_lang。如需更多詳細資訊,請查看使 Dart 成為更好的 UI 語言或 GitHub 上的初始語言提案


探索 Dart 中的集合 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Google Summer of Code 2020 results

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

Google 暑期程式碼計畫 2020 成果

Google Summer of Code

Google 暑期程式碼計畫 (GSoC) 是一個全球性的計畫,旨在讓學生開發者參與開放原始碼軟體開發。Google 贊助學生在暑假期間與一個指導性的開放原始碼組織合作,進行為期 3 個月的程式設計專案。

二月,我們很興奮地宣布 Dart 團隊將成為 2020 年 GSoC 的指導組織。我們最終 指導了 5 個專案,這些專案是從來自世界各地學生的 170 多份申請中選出的。今天,我們很高興與大家分享我們的成果,這些成果將由參與專案的學生們來描述。

使用 package:ffigen 為 Dart-C 互通性產生 dart:ffi 繫結

Prerak Mann

package:ffigen 是一個 FFI 產生器,它透過從 C 標頭檔 (.h) 產生 Dart 繫結,大大減少了使用 dart:ffi 的工作量。

為了理解這個工具的需求,請考慮 LibClang(Clang 的 C 介面)。LibClang 的 API 大約有 8,000 行程式碼 (LOC)。如果您要手動為 LibClang 編寫 Dart 繫結,您將需要將近 4,500 LOC。使用 package:ffigen,您只需傳入一個小於 20 LOC 的 小型 YAML 設定檔,然後就可以產生繫結。

您可以控制要產生的 C 函式,並使用簡單的正規表示式來重新命名巨集、列舉和個別的結構成員。您也可以 產生 巢狀在結構中的陣列成員(dart:ffi 中尚不完全支援)。

請查看 github.com/dart-lang/ffigen 以取得完整的 設定常見問題範例 等等。

使用新的 Flutter 範例學習測試

Abdullah Deshmukh

您已經使用 Flutter 一段時間了,但仍然不知道如何測試 Flutter 應用程式嗎?那麼您可能會對我們建立的範例感興趣,該範例展示了測試 Flutter 應用程式的最佳實踐。您將能夠快速輕鬆地學習在 Flutter 中進行測試。

範例 展示了不同類型的測試,包括:

  • 單元測試
  • Widget 測試
  • 整合測試
  • 效能測試
  • 狀態管理測試

作為額外福利,我們還建立了一個關於如何測試 Flutter 應用程式的程式碼實驗室。您可以在文章 使用新的 Flutter 範例學習測試 中查看專案下完成工作的詳細報告。

使用者可擴展的翻譯檔案格式

Jaime Blasco

intl_translation 套件僅支援 ARB 檔案格式,該格式在 Dart 之外並未廣泛使用。在 GSoC 2020 期間建立的 intl_translation_format 套件建基於 intl_translation,並添加了一個 TranslationFormat 類別,可以支援各種不同的格式。開發人員可以在自己的套件中添加對其他格式的支援,並且只需幾行程式碼即可建立與現有工具整合的提取和產生命令列工具。有了這些,其他格式(即使是專有格式)的現有翻譯可以輕鬆匯入到 Dart 專案中,並且源自 Dart 程式碼的新訊息可以整合到現有的翻譯工具和工作流程中。

作為這些功能的展示,新的套件包含對多種格式的支援。除了 ARB 之外,它還支援 XLIFF v1.2 (iOS)、XLIFF v2.0帶有 ICU 訊息的 JSON實驗性函式庫 展示了如何實作對其他格式的支援,甚至是可以在執行階段更新的翻譯。

行動應用程式和聯合 Plugin 中的平台通道

Ayush Bherwani

在研究了平台通道(用於在 Dart 與原生程式碼(如 Swift 或 Kotlin)之間來回傳送訊息的 Flutter API)之後,我們決定使用兩個不同的範例來介紹這個主題。一個是 簡單的 Android 和 iOS 範例,而另一個是 聯合 Plugin,能夠使用主頻道為 Web 和桌面建構。在這兩個範例之間,程式碼庫不僅展示了平台通道和用於移動資料的編解碼器的詳細資訊,還展示了聯合 Plugin 的 API 以及在 Web 和桌面(Windows 和 Linux)API 上調用原生程式碼的技術。

如果您是一位正在尋找建構 Plugin 或僅存取平台 API 的開發人員,這些範例是開始學習的好地方。有關此專案的更多資訊,請參閱文章 平台通道範例

編寫 package:yaml_edit 以啟用 pub add <package>

Garett Tok Ern Liang

Dart 專案在 pubspec.yaml 檔案中指定套件依賴項。YAML 很好,因為它允許我們編寫註釋,但是以程式設計方式修改 YAML 是一個挑戰。

為了應對這一挑戰,我們編寫了 package:yaml_edit 以輕鬆執行上下文感知的字串操作,從而保留格式和註釋。該套件的測試套件包含超過 40,000 個測試,我們認為我們有一個非常好的解決方案。試用它並 提交錯誤

使用 package:yaml_edit,我們在 pub(Dart 和 Flutter 的套件管理工具)中添加了一個新的子命令 pub add <package>pub add 會自動解析目標套件的最新相容版本,並將其添加到使用者的 pubspec.yaml 中。具體來說,pub add <package> 自動化了以下所有操作的繁瑣過程:

  1. 訪問 https://pub.dev/packages/
  2. 選擇要添加到套件的版本
  3. 修改 pubspec.yaml
  4. 執行 pub get
  5. 根據需要重複步驟 2-4 多次,以獲得與其他依賴項相容的版本

一個類似的移除依賴項的命令 - pub remove <package> - 也正在開發中。請密切關注即將在 pub 中推出的這兩個命令!

非常感謝所有向 Dart 申請 Google 暑期程式碼計畫 2020 的人,以及今年完成專案的學生,以及每週花時間讓這個夏天成為一個很棒的程式碼夏天的導師。我們希望在整個 Dart 生態系統中再次見到你們這些學生作為貢獻者。


Google 暑期程式碼計畫 2020 成果 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Introducing a brand new pub.dev

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

隆重推出全新的 pub.dev

今天,我們推出了全新的 pub.dev 網站。它採用了新的評分指標,讓您可以更好地了解可能有用的套件,並為套件發布者提供更多可操作的回饋,以提高套件品質。為了幫助您找到支援您感興趣的所有平台的套件,我們新增了識別和搜尋多平台套件的功能。總體而言,我們重新設計了使用者介面,目標是提供與我們的 dart.devdartpad.dev 網站一致的愉悅而優雅的體驗。

A screenshot of the page at https://pub.dev
全新的 pub.dev 網站

找到最符合您需求的套件

在過去一年中發布了近 7,000 個新套件,在尋找擴展應用程式功能的套件時,您不乏選擇。但是有這麼多套件可供選擇,您自然希望最受歡迎、品質最高、最常用的套件能夠脫穎而出。為了支援更好的套件搜尋結果,我們圍繞三個維度重新設計了 pub.dev 中的評分系統:

Screenshot of a package summary: 552 likes; 100/110 pub points; 98% popularity
pub 套件的三個評分維度
  1. 點讚數: 衡量有多少開發者點讚了一個套件。這提供了一個對套件整體評價的原始指標。
  2. Pub 分數: 一種新的品質衡量標準。這包括品質的幾個維度,例如程式碼風格、平台支援和可維護性。詳情請見下文。
  3. 熱門度: 衡量有多少開發者使用一個套件。這反映了过去六十天內依賴該套件的應用程式數量。標準化後的評分範圍從 100%(最常用的套件)到 0%(最少使用的套件),但我們正在研究是否可以在未來版本中提供絕對使用計數。

我們希望這些新的維度可以幫助正在尋找依賴套件的套件使用者,以及致力於發布高品質套件的套件發布者。

使用 Pub 分數衡量品質

點讚數和熱門度提供了社群提供的、主觀的衡量標準,Pub 分數的目標是提供一個可衡量的、客觀品質的近似值。這並不容易;開發者以各種方式感知品質,但我們仍然相信大多數開發者都在尋找我們可以衡量的核心特徵。以前的 pub.dev 評分模型基於對問題的扣分。新的評分模型扭轉了這種方法,而是根據五個類別中的特徵授予分數:

Screenshot: Pub points overview for a package
Pub 分數概述,包含類別和檢查,以及獲得的分數
  1. 遵循 Dart 檔案規範: 遵循 pubspec、readme 和 changelog 檔案的最佳實務
  2. 提供文件: 提供 API 文件和說明性範例
  3. 支援多個平台: 支援盡可能多的 Dart 和 Flutter 平台
  4. 通過靜態分析: 提供無錯誤、警告和 lint 的程式碼
  5. 支援最新的相依: 具有支援最新 Dart 和 Flutter SDK 以及最新套件相依的相依限制

我們預計隨著時間的推移,會為 Pub 分數新增更多檢查和類別,提高套件可以獲得的分數上限。例如,我們計劃在該功能推出時,為支援 空安全 新增一個分數。我們正在研究如何衡量可維護性(問題和拉取請求的回應速度)、可測試性(自動化測試覆蓋率)和社群友好度(貢獻者、維護者數量等)。如果您對一個好的衡量標準有任何想法,我們很樂意 聽取您的意見

支援多平台應用程式

Dart 和 Flutter 都擅長建立針對多個平台的應用程式。在 pub.dev 上,我們為行動(iOS 和 Android)和 Web 平台提供了清晰的平台標籤,以及這些平台的搜尋篩選器:

Screenshot: Search results for Flutter packages that support both Android and Web
搜尋中的平台篩選器

為了確保您的套件標記了正確的平台支援,請遵循 pub.dev 評分說明頁面 上的指南。

當桌面平台(macOS、Windows 和 Linux)發展到更最終的狀態時,我們將新增類似的支援。目前,您可以在「進階」搜尋選項中找到這些平台的篩選器:

Screenshot: Search results for Flutter packages that support Android, iOS, and macOS platforms
進階篩選器中的桌面平台

優雅而令人愉悅的設計

Dart pub.dev 團隊與我們的內部創意團隊 Google Developer Studio 合作,Jeremy Sie 擔任主要設計師。除了功能更新之外,團隊還仔細研究了視覺設計,分享了他們對該專案的想法。為了提供一致的、易於存取的、以使用者為中心的體驗,我們根據 Google 的 Material Design 系統 進行了一系列改進。我們的目標是建立一個輕量級且簡化的介面,為套件發布者和使用者提供清晰簡潔的工作流程。

更新後的設計採用了 Material 元件,例如卡片和晶片,可以清晰地顯示套件詳細資訊,並讓單個套件的功能更加透明。我們更新了排版系統,並改進了整個網站的 字體比例,使搜尋結果更加清晰,並改進了套件資訊的顯示方式。套件分數等元件也經過重新設計,使其更加清晰,並以一種使評分過程更加透明且更容易理解的方式進行組織。

Screenshot: Package search results
搜尋顯示所有三個 pub.dev 分數

我們還對首頁進行了更改。我們知道快速搜尋套件的能力是網站的核心部分,但我們希望透過顯示最有用的工具和函式庫來改進體驗。我們在前面加入了「Flutter 精選」和「最熱門套件」等區塊,幫助您快速瀏覽一些最好的套件。我們仔細研究了調色板和藝術方向,加入了插圖,並使程式碼範例等元件與 dart.devdartpad.dev 在視覺上保持一致。

立即試用 pub.dev

我們邀請您立即試用新的 pub.dev 瀏覽和搜尋體驗,並使用新的評分維度和 Pub 分數報告。

如果您是套件開發者,我們鼓勵您查看套件的 Pub 分數,尋找改進套件的機會。有關評分系統如何運作的更多詳細資訊,請參閱 pub.dev 評分說明

與往常一樣,我們喜歡回饋。請在下方留言,或在 pub.dev 問題追蹤器 中提交問題。

作者:Michael Thomsen(Dart 產品經理)和 Jeremy Sie(Google Developer Studio 視覺設計師)


隆重推出全新的 pub.dev 最初發佈於 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/