RainVisitor Blog

RainVisitor

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://dart.dev/blog/announcing-dart-3-14

宣布 Dart 3.14

藉由自動化 SDK 遷移、更快的 WebAssembly 和重新設計的 DartPad,讓 Dart 工作流程臻於完美。

Chef Dash pulling a freshly baked pie with the Dart logo from an oven next to glowing 3.14 numbers.
宣布 Dart 3.14

我們很高興宣布 Dart 3.14!

與我們的 pi 版本相符,Dart 3.14 讓開發人員工作流程臻於完美,從在瀏覽器中嘗試想法到建構跨平台應用程式以及升級您的套件以迎接下一個版本。您可以在重新設計的 DartPad 中測試想法和 pub.dev 套件,使用 dart migrate 自動在 SDK 版本之間遷移程式碼庫,使用硬體 SIMD 提升 WebAssembly 效能,在瀏覽器外執行 Wasm 模組,並透過穩定的 FFIgen 和 JNIgen 支援呼叫原生平台 API。

所以 更新 Dart 或執行 flutter upgrade 並跟隨我們探索 Dart 3.14 中的新功能!當您準備好查看 UI 框架中的新功能時,請查看配套的 Flutter 3.50 新功能 部落格文章。

工具更新

使用 dart migrate 工具自動化套件升級

將 Dart 套件更新到較新的 SDK 版本通常涉及提高其 SDK 限制並更新程式碼以符合新的樣式建議或防止與即將到來的版本發生中斷。然而,弄清楚要啟用哪些 lint 或每個版本需要哪些程式碼變更可能很棘手。

為了消除跨版本升級的猜測,新的 dart migrate 工具透過一次將套件向前推進一個版本來自動化此工作流程。對於每個版本,它將:

  1. 準備您的套件,方法是應用需要在版本升級之前執行的 dart fix 修正。
  2. 提高 pubspec.yaml 中 SDK 限制的下限。

一旦您的套件在新版本上,您可以選擇透過應用使用新功能或符合新樣式建議的修正來清理它。

例如,在 SDK 限制為 ^3.12.0 的套件中:

1
2
3
4
5
6
7
class WeakPasswordException implements Exception {}

void checkPassword(final String password, final int minLength) {
if (password.length < minLength) {
throw WeakPasswordException();
}
}

該工具透過從參數中刪除 final 來準備程式碼,這在 Dart 3.13 中是一個編譯時錯誤。它提高了 pubspec 中的 SDK 限制,然後透過採用空類別的新分號主體語法來清理它:

1
2
3
4
5
6
7
class WeakPasswordException implements Exception;

void checkPassword(String password, int minLength) {
if (password.length < minLength) {
throw WeakPasswordException();
}
}

完成執行後,migrate 工具會報告每個步驟的編輯摘要,以及套件仍可應用的可選清理修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my_package:
3.12.0 -> 3.13.0:
Preparatory changes:
2 changes made in 1 file.

my_package/lib/password.dart
avoid_final_parameters • 2 changes

SDK constraint:
Bumped ^3.12.0 -> ^3.13.0

Optional cleanup fixes can modernize your code further:

my_package: 1 change in 1 file
empty_container_bodies • 1 change

To preview them, run 'dart pub get' and then 'dart migrate --dry-run --step=cleanup' on the same packages.

若要預覽套件的變更,請使用 --dry-run 旗標:

1
dart migrate --dry-run

若要應用建議的變更,請使用 --apply 旗標:

1
dart migrate --apply

對於可能希望在提高 SDK 限制之前或之後審查修正的大型程式碼庫,您可以使用 --step 旗標執行特定步驟:

1
2
3
4
5
# 只執行準備性修正,不提高限制
dart migrate --apply --step=prepare

# 應用準備性修正後,提高限制並應用清理修正
dart migrate --apply --step=bump,cleanup

預設情況下,執行該工具會將套件版本提高一個次要版本。若要在一次運行中遷移多個版本,請使用 --target-sdk 旗標:

1
dart migrate --apply --target-sdk=3.14.0

深入了解: Dart migrate 文件

Analysis Server 效能改進

除了更流暢的 SDK 升級之外,隨著您的工作空間不斷增長,保持 IDE 的響應速度並加速 AI 編碼代理的工作流程也是我們工具團隊的首要任務。Dart Analysis Server 已有多項效能改進:

  • 更快的檔案發現: 加快了 查找參考 等請求,尤其是在工作空間包含許多 Analysis Contexts 時。
  • 較少阻塞的任務: 以前會阻塞其他操作(例如程式碼格式化)的任務現在阻塞較少。
  • 更快的伺服器啟動: 減少啟動時間,包括分析伺服器因套件配置和分析選項的變更而重新啟動其分析時。
  • 更快的 documentColor 請求: 加快了 documentColor 請求,這在編輯檔案時解除了許多其他分析伺服器請求的阻塞。
  • 範圍分析器 Plugin 根: 將 Plugin 分析根限制為 Plugin 上下文根,這使得分析器 Plugin 分析速度更快。

Pub 更新

ETag 快取支援以加快依賴解析

在 dart pub 中,熱快取依賴解析現在更快,並且在中型到大型專案上使用的頻寬更少。當使用 dart pub get、upgrade 或 add 解析依賴時,pub 會查詢 pub.dev 以獲取每個套件的版本列表,以檢查新發布的版本、撤回和安全建議,這以前意味著即使沒有任何變化也需要重新下載數 MB 的套件中繼資料。

現在,dart pub 將伺服器的 ETag 與快取的套件列表一起儲存在您的本地 pub 快取中,並在透過熱快取解析時發送條件式 If-None-Match 請求。當套件列表未更改時,pub.dev 會回應 304 Not Modified 和空主體,並且 pub 會直接重用快取的列表而無需重新下載。在一個包含 144 個依賴項的 Flutter 專案中,這消除了約 1.5 MB 的重複版本列表下載,加快了熱快取解析速度,尤其是在速度較慢或延遲較高的連接上。

重新設計的 DartPad beta

我們很高興能分享重新設計的 DartPad 的 beta 版本,它讓您在瀏覽器中更容易、更快速地試驗 Dart 和 Flutter。

它建立在 package:dartpad 之上,該套件將 Dart 編譯器、分析器和 pub 用戶端編譯為 WebAssembly 並在 Web Worker 中本地執行。這意味著 DartPad 現在直接在瀏覽器中分析和編譯您的程式碼,因此您無需等待遠端伺服器即可獲得更快的意見回饋。它還具有完整的語言伺服器協定 (LSP) 支援:您可以獲得與 IDE 中相同的程式碼完成、懸停文件、跳轉到定義和快速修復。

這個新技術堆疊的一個令人興奮的功能是它允許您從 pub.dev 引入套件,因此您不再受限於手動挑選的允許列表。引入您在真實專案中會使用的函式庫並立即嘗試它們。此外,DartPad 現在支援多個檔案,因此您可以像在真實應用程式中一樣組織 Widget 和資料模型。

新的 DartPad 展示了 Dart 作為一種全棧語言的多功能性,它可以在您的機器、伺服器或網頁瀏覽器中運行。因為這些核心工具本身就是用 Dart 撰寫的,將它們編譯成 WebAssembly 使得可以直接在瀏覽器中執行整個工作流程。

試試看: dartpad.dev

Web 平台更新

瀏覽器和 Wasm 運行時的標準化 WebAssembly 例外

Dart WebAssembly 應用程式現在可以開箱即用地在更多瀏覽器和非瀏覽器 Wasm 環境中執行。在瀏覽器中,dart2wasm 現在預設發出最終化的 WebAssembly try_table 例外處理指令,並解決了 Safari 26.5 及更早版本中的串流編譯問題,從而在 Chrome 137+、Safari 26.2+ 和 Firefox 147+ 中啟用 WebAssembly。

在瀏覽器之外,採用 try_table 消除了編譯器對 dart compile wasm --standalone 中 JavaScript 例外包裝器的最後依賴(由社群成員 Simon Binder 貢獻)。這會產生標準的 .wasm 二進位檔,可以直接在 Wasm 運行時(例如 Wasmtime)中執行,而無需 JavaScript 主機,這使得在邊緣工作者、插件系統和伺服器端 Wasm 環境中執行 Dart 變得更加容易。若要嘗試 WASI 和 WebAssembly 元件模型,請在 GitHub 上探索 Simon Binder 的社群 wasm_tools 套件。

深入了解: WebAssembly 文件

硬體 128 位元 WebAssembly SIMD

除了更廣泛的運行時支援之外,WebAssembly 的效能在此版本中也獲得了顯著提升。歸功於社群成員 Modestas Valauskas 的 60 多項 SDK 貢獻,dart2wasm 現在將 Int32x4、Float32x4、Float64x2 及其類型化資料列表視圖直接編譯為 硬體 128 位元 WebAssembly SIMD (v128) 指令,以及在原生平台上的 dart:typed_data 中擴展和優化的 Int32x4 操作,在原生和 WebAssembly SIMD 基準測試中提供了高達 4 倍的速度提升。

深入了解: dart:typed_data 函式庫文件

移除舊版 Web 函式庫,使用 --no-deprecated-js-interop

隨著 WebAssembly 採用率的增長以及我們持續將 Dart Web 平台現代化,舊版 Web 和 JavaScript 互操作函式庫(dart:html、dart:js、dart:js_util、package:js 和相關的 dart:* 瀏覽器函式庫)已棄用,並預計在即將發布的版本中從 Dart SDK 中移除。雖然我們知道遷移需要時間,但遷移到 package:web 和 dart:js_interop 為 Dart 在 Web 上解鎖了更健壯、標準化的未來,包括與 WebAssembly 的完全相容性。

為幫助您提早準備程式碼,dart compile js (dart2js) 和 ddc 現在支援 --no-deprecated-js-interop,這會在編譯時拒絕直接或間接匯入這些函式庫,並將其 dart.library.* 環境條件在條件匯入中設定為 false。

今天在您的套件和應用程式上執行 dart compile js --no-deprecated-js-interop (或在 Flutter 3.50 中執行 flutter build web --no-deprecated-js-interop),並將剩餘的使用方式遷移到 dart:js_interop 和 package:web。

深入了解: JS interop 文件 和 遷移到 package:web

核心引擎和執行時更新

dart:typed_data 中的快速範圍比較

在效能關鍵程式碼中比較位元組緩衝區的切片不再需要分配 sublistView 並逐元素迭代(例如,使用 package:collection 的 ListEquality)。dart:typed_data 現在在所有整數類型資料列表(Uint8List、Int8List、Uint8ClampedList、Uint16List、Int16List、Uint32List、Int32List、Uint64List、Int64List)和 ByteData 上包含一個 rangeEquals 擴展方法:

1
2
3
4
5
6
7
8
9
10
11
import 'dart:typed_data';

void main() {
final packet = Uint8List.fromList([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A]);
final pngMagic = Uint8List.fromList([0x89, 0x50, 0x4E, 0x47]);

// 將 packet[0..4] 與 pngMagic[0..4] 進行比較,無需分配視圖:
if (packet.rangeEquals(0, 4, pngMagic)) {
print('PNG header matched');
}
}

在底層,rangeEquals 在 Dart VM 上會轉換為 memcmp,在 dart2wasm 上會直接載入 Wasm 陣列(ByteData 會進行 32 位元區塊處理),在 JS 上會進行緊湊的類型化陣列循環,為您提供所有目標上的快速、無記憶體分配的比較。

深入了解: dart:typed_data 函式庫文件

直接原生互操作性

除了核心運行時之外,從 Dart 呼叫原生平台函式庫現在已可投入生產。透過 Dart 3.14,用於 Objective-C 的 FFIgen 和 JNIgen 都穩定了!

您可以使用 FFIgen 和 JNIgen 自動產生慣用、類型安全的 Dart 綁定,並直接從 Dart 呼叫 Objective-C、Java 和 Kotlin API,而不是撰寫手動平台通道樣板程式碼。達到穩定性是多年設計、迭代和回饋的結果,重點在於減少建構 Dart 套件和 Flutter 插件的摩擦,我們將很快在 Flutter 部落格 上深入探討如何為您的用例選擇正確的互操作方法。

深入了解: 原生互操作性文件

展望未來:語言發展藍圖

團隊在 this 升級和靜態擴展方面持續取得進展。請繼續關注,在即將發布的版本中了解更多資訊!

深入了解: Dart 語言漏斗

社群

Dart 的結構化 RFC 流程

社群貢獻者在每個 Dart 版本中都扮演著重要角色,包括 Dart 3.14 中獨立的 WebAssembly 和硬體 SIMD 改進。隨著 Dart 生態系統持續發展,我們希望讓追蹤和貢獻重大專案決策變得更加容易。Dart 專案正在採用一種新的結構化方法來提案和評估需要團隊共識的重大變更:此類變更現在需要撰寫一份「請求評論 (RFC)」。

此流程可供團隊成員和社群貢獻者用於徵求 Dart 團隊和 Dart 專案領導的意見回饋和正式批准。它還旨在透過將設計討論和決策成果整合到一個地方來清晰地展示團隊正在處理的內容。

深入了解: Dart RFC 儲存庫

體驗 Dart 3.14

我們的 pi 版本讓 Dart 臻於完美,將您的工作流程從在瀏覽器中使用 DartPad 測試 pub.dev 套件,到發布高效能的 WebAssembly 和原生應用程式,再到使用 dart migrate 自動化版本升級。在底層,Dart 自己的編譯器、分析器和 pub 用戶端現在會編譯成 WebAssembly,以便在瀏覽器中執行 Dart 本身。

這個完美的循環也延伸到 Dart 社群。Dart 3.14 中的主要亮點直接由開源貢獻者推動,我們新的 RFC 流程為大型工程變更的設計、審查和在整個 Dart 專案中的共享帶來了更高的透明度。感謝所有在此版本中貢獻程式碼、提交問題和分享回饋的人!

準備好深入探索了嗎?運行 flutter upgrade 或 下載最新的 Dart SDK,試用新的 DartPad beta 預覽版,並在我們的 GitHub 問題追蹤器 上分享您的回饋。感謝您與我們一同建構!

深入了解: Dart SDK 變更日誌

更多來自 Dart 的內容

Announcing Dart 3.13

宣布 Dart 3.13

為 Dart 程式碼庫、工具和平台帶來簡潔和簡單。

Connie Ooi Connie Ooi
2026 年 8 月 12 日 · 9 分鐘閱讀
Announcing Dart 3.12

宣布 Dart 3.12

在 Google I/O 2026 上為開發人員生產力注入超能力

Connie Ooi Connie Ooi
2026 年 5 月 20 日 · 7 分鐘閱讀

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/whats-new-in-flutter-3-50

Flutter 3.50 有什麼新功能

投資 Flutter 的下一個十年

Flutter 3.50 已全面就緒,並與 Dart 3.14 同步推出。此版本帶來了一系列更新,展示了 Flutter 的下一步發展方向。直接原生互通現已穩定,讓您可以在 Dart 和原生平台 API 之間進行呼叫。Safari 和 Firefox 上的 WebAssembly 速度更快,加上瀏覽器原生文字佈局,為 Flutter 網頁應用程式帶來了顯著的速度提升。我們還擴展了用於自訂硬體的平台嵌入器 API,現在獨立的 material_ui 和 cupertino_ui 套件是新專案的預設設定。

一如既往,如果沒有 Flutter 社群以及您在框架和生態系統中的所有貢獻,我們將無法完成這一切。僅在此發佈週期中,就有 188 位貢獻者,其中包括 69 位首次貢獻者,提交了超過 1,200 次提交。因此,在您的終端機中執行 flutter upgrade 以獲取最新版本,或繼續閱讀以探索新功能。

直接原生互通性

去年,我們分享了 直接原生互通性 的願景,以消除傳統平台通道的序列化開銷、基於字串的管道和強制性非同步延遲。在 Flutter 3.50 中,這項多年的投資達到了生產就緒狀態:適用於 Objective-C 的 FFIgen 和適用於 Java 和 Kotlin 的 JNIgen 加入了 FFIgen 長期以來的 C 語言支援,成為穩定版本。您還可以預覽 Pigeon 的新原生互通模式,該模式使用兩個生成器在底層建立型別安全的繫結。

對於任何原生整合,您現在可以跳過平台通道訊息傳遞,並在 Dart 和平台程式碼之間直接呼叫,包括同步呼叫或從背景隔離區呼叫。使用 FFIgen 繫結 C 和 Objective-C API,或使用 JNIgen 繫結 Android 系統 API 和 Flutter 和 Dart 應用程式中的自訂 Java 或 Kotlin 函式庫。在建構多平台外掛程式時,Pigeon 的原生互通模式會自動處理 iOS、macOS 和 Android 上的資料轉換和原生生命週期。

Diagram comparing traditional MethodChannels serialization with direct generated JNI and FFI host API calls.
直接原生互通性跳過了平台通道序列化,透過 FFI 和 JNI 直接呼叫原生 API。

感謝所有套件作者、貢獻者和早期存取計畫參與者,特別是 Jon Holtan、Rody Davis、Dominik Roszkowski、Alexander Thiele 和 Morgan McKenzie。這些社群成員測試了早期版本、審查了文件並幫助塑造了這些 API。請持續關注有關直接原生互通的專門文章。

了解更多:原生互通文件

網路平台

在網路上,Flutter 3.50 將更快的跨瀏覽器 WebAssembly 執行與內建資產快取清除、現代 JS 互通性檢查以及更小、瀏覽器原生的文字渲染結合在一起。

Safari 和 Firefox 上的 WebAssembly

執行 flutter build web --wasm 會將您的應用程式編譯為 WebAssembly 和 JavaScript,在舊版瀏覽器上會自動回退到 JavaScript。基於 Chromium 的瀏覽器(Chrome 和 Edge 137+)預設已經執行 WebAssembly。在 Flutter 3.50 中,**Safari 26.2+**(macOS 和 iOS)和 Firefox 147+ 在重度 Widget 基準測試中的影格率是 JavaScript 的兩倍以上,您今天就可以在 web/flutter_bootstrap.js 中選擇啟用,它們將在未來的穩定版本中預設啟用:

1
2
3
4
5
6
7
8
9
10
11
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
wasmAllowList: {
blink: true,
webkit: true,
gecko: true,
},
},
});

了解更多:WebAssembly (Wasm) 支援

內建的網頁內容雜湊

部署更新後的 Flutter 網頁應用程式可能會導致回訪的使用者在瀏覽器中快取過時的 JavaScript 或不匹配的資產。將 --web-content-hash 傳遞給 flutter build web 可以解決此問題,它會將內容雜湊嵌入到您編譯的 JavaScript、WebAssembly 和 build/web/assets/** 檔名中,並自動更新 flutter_bootstrap.js。這讓您可以快取雜湊的應用程式套件和資產長達一年(Cache-Control: public, max-age=31536000, immutable),同時將 index.html 和 flutter_bootstrap.js 保留在 no-cache 上,以便每次部署都能乾淨地載入。此標誌還會生成 build/web/precache_manifest.json 用於自訂服務工作者和 Workbox 管道,取代已棄用的 flutter_service_worker.js 標記。

了解更多:網頁內容雜湊和 HTTP 快取

稽核網頁上傳統的 JS 互通性

Flutter 網頁應用程式和外掛程式使用 JS 互通函式庫來存取瀏覽器功能,例如本機儲存或包裝第三方 JavaScript SDK。Dart 舊的純 JS 函式庫(dart:html、dart:js、dart:js_util 和 package:js)現在已棄用,改用 dart:js_interop 和 package:web。這些現代替代方案適用於 JavaScript 和 WebAssembly 建置,而傳統函式庫將在未來的 Dart 版本中移除。將 --no-deprecated-js-interop 傳遞給 flutter build web、flutter run 或 flutter test --platform chrome 以捕捉過時的 JS 匯入。如果您的應用程式或其任何套件仍然依賴傳統瀏覽器函式庫,這會在早期建置失敗,幫助您為 WebAssembly 做準備。

了解更多:使用相容的 JS 互通函式庫 和 宣布 Dart 3.14

原生瀏覽器文字佈局預覽

與 Chrome 團隊合作,在 Chrome 的實驗性 Enhanced TextMetrics API 上構建,WebParagraph 讓瀏覽器直接在畫布上塑造作業系統字體,而不是捆綁 C++ 字體解析器。將文字佈局委派給瀏覽器可以縮小 CanvasKit 下載(從 5.46 MB 縮小到約 3.75 MB),消除後備字體的網路請求,防止國際腳本中缺少字元方塊,並渲染原生彩色表情符號。

了解更多:WebParagraph 實驗預覽 (#172561)

可插入平台

繼我們在 Google I/O 上宣布可插入平台 之後,我們持續讓 Flutter 更容易移植到新的作業系統和硬體。在 Flutter 3.50 中,我們填補了底層 Impeller 和 Vulkan 嵌入器 API 中的兩個關鍵空白。社群貢獻者 xiaowei-guan 增加了 Vulkan 外部紋理支援,因此自訂嵌入器可以 直接將紋理傳遞給引擎,以便在 Impeller 和 Skia 後端進行採樣。自訂嵌入器現在可以呼叫 FlutterEngineNotifyLowMemoryWarning,以便在記憶體壓力下立即 清除 Impeller 的離螢幕渲染目標快取。

解耦設計套件

在 Flutter 3.47 中推出獨立的 material_ui 和 cupertino_ui 1.0 版本之後,Flutter 3.50 正式棄用了耦合的 package:flutter/material.dart 和 package:flutter/cupertino.dart 函式庫,以及它們在 package:flutter_localizations 中的本地化委派。使用 flutter create 建立的新專案現在預設匯入 package:material_ui。

Diagram showing migration from bundled SDK material.dart and cupertino.dart libraries to standalone material_ui and cupertino_ui packages on pub.dev using dart fix --apply --code=migrate_design_widgets.
使用 dart fix 從捆綁的 SDK 設計函式庫遷移到獨立的 material_ui 和 cupertino_ui 套件。

執行 dart fix --apply --code=migrate_design_widgets 自動更新現有匯入和本地化委派。

了解更多:Material 和 Cupertino 遷移指南 和 解耦 Material 和 Cupertino

框架

在核心框架中,社群和團隊的貢獻加入了 sliver 裁剪 Widget、更穩定的文字選取、邏輯圖片快取、無障礙功能改進和桌面多視窗更新。

捲軸和 sliver

社群貢獻者在 Flutter 3.50 中為自訂捲軸視圖提供了出色的升級。 Emmanuel Lefebvre Dibon 加入了 SliverClipRect 和 SliverClipRRect,並帶有 clipOverlap 選項,可以在不切斷固定標頭的情況下圓角或裁剪捲軸部分。 Mairramer 為 ListView.separated 加入了 itemExtentBuilder,因此帶有分隔線的列表可以按需調整可變高度行的大小,而無需建立離螢幕 Widget。Mairramer 還引入了 ScrollPhysics.shouldUpdate,以便應用程式可以在設定變更時動態更新捲軸行為。 Soroush Yousefpour 更新了 RawScrollbar.mouseCursor,僅在捲軸條拇指和軌道上方變更滑鼠指標,保持附近內容的標準游標。框架修復現在停止 Scrollable.ensureVisible 在顯示固定標頭所需的確切偏移量處,而不是捲軸過去。SliverFillRemaining 現在在捲軸視圖的末尾準確計算過度捲軸拉伸偏移量。最後,TreeSliver 清晰地裁剪擴展和折疊分支,因此動畫子行永遠不會與鄰近項目重疊。

Animated demo of SliverClipRRect clipping scrolling sliver content to rounded corners.
SliverClipRRect 在不切斷固定標頭的情況下圓角和裁剪捲軸部分。

文字輸入與選取

現在,在行動裝置、網頁和桌面平台上,文字編輯和選取都明顯更加穩定。 Alex Li 為 TextInputType.numberWithOptions 加入了 password 選項,以便 Android 應用程式可以開啟安全的數字鍵盤進行 PIN 碼輸入。Android 應用程式還可以使用 AutofillHints.emailOTPCode 自動填寫電子郵件中的一次性驗證碼。新的 Hyphens 列舉可讓您控制當文字換行時是否可見軟連字號。 Nicodemus Lin 阻止了當使用者在多行文字中拖曳頂部選取手柄時螢幕跳動。 Nickolas de Luca Alberton 修正了置中和右對齊單行文字欄位中的上下箭頭鍵游標移動。在 PageView 內拖曳選取文字不再會意外翻轉到鄰近頁面。選取手柄在混合從左到右和從右到左的文字時也保持指向選取的文字。

圖像與繪圖 API

社群貢獻者也完善了 Flutter 的圖片載入和快取 API。 Kishan Rathore 為 DecorationImage 加入了 placeholder ImageProvider,以便容器背景在載入時可以顯示備用圖片。 Anas Saifi 加入了 FadeInImageTransition.fadeInOver,用於在不出現空白閃爍的情況下,在不透明的佔位符上平滑淡入圖片。 Koji Wakamiya 在 ResizeImage 上加入 useLogicalSize,並在 Image 上加入 useLogicalCacheSize,以在不需要 BuildContext 的情況下將快取圖片縮放到螢幕密度。 Ishaq Hassan 在 RawImage 上公開了 blendMode,因此自訂 Widget 可以在繪製圖片時直接應用顏色混合效果。自訂 BoxBorder 形狀現在可以在動畫期間在狀態之間平滑過渡。使用 HSVColor.lerp 和 HSLColor.lerp 的顏色動畫現在會沿著色輪的最短路徑移動,避免了意外的顏色偏移。

Animated demo of DecorationImage displaying blurred placeholder cards before loading crisp full-resolution images.
DecorationImage.placeholder 在容器背景圖片載入時顯示備用圖片。

無障礙功能與導覽

Flutter 3.50 改善了跨平台的鍵盤導航、螢幕閱讀器支援和對話框自訂。自訂滑塊 Widget 現在可以使用 SemanticsRole.slider,以便螢幕閱讀器準確地宣布其值和控制項。應用程式可以檢查 MediaQueryData.reduceMotion 以檢測使用者的「減少動態」設定,並將滑動動畫替換為輕柔的淡入淡出效果。按下 Escape 鍵現在會關閉打開的 Tooltip 和 RawTooltip 疊加層,而無需移動鍵盤焦點,符合網路無障礙標準。在網路上,VoiceOver 和 WidgetsApp.router 修正可使螢幕閱讀器和鍵盤焦點在疊加層內保持穩定,而不會意外重設。 Ishaq Hassan 在 FocusableActionDetector 上新增的 skipTraversal 選項可讓自訂互動式 Widget 選擇退出 Tab 鍵導航。對於對話框, Mairramer 在 showDialog 上新增的 barrierBuilder 回調可讓您自訂模態背景,例如新增背景模糊。最後,Navigator.popUntilWithResult 現在會將其結果返回到最後彈出的路由,即使目的地路由具有本機歷史記錄條目。

桌面視窗化

與 Canonical 合作,並感謝維護者 Robert Ancell、Matthew Kosarek 和 Matej Knopp,Flutter 的實驗性多視窗桌面 API 在 Linux、macOS 和 Windows 上持續成熟。在 Linux 上,應用程式可以使用 WindowControllerLinux 建立自訂標題列、透明視窗和原生拖曳移動或調整大小控制項。在 macOS 上,視窗和對話框可以在出現之前調整自身大小以適應其內容,從而防止在啟動時出現可見的調整大小閃爍。在所有三個桌面平台中,RegularWindow 現在被命名為 Window 以簡化多視窗程式碼。其 shrinkWrap 屬性也取代了 sizedToContent 以符合 Flutter 熟悉的佈局命名。最後,WindowManager 現在位於 WidgetsApp 之上,因此輔助視窗可以執行自己的獨立 UI 和導航狀態。

Screenshot of the Flutter Multi-Window Reference Application on macOS showing a main window and secondary dialog window.
macOS 上執行的多視窗桌面參考應用程式。

引擎

在內部,Flutter 3.50 擴展了 Impeller 的硬體加速原始渲染,改善了行動 GPU 的穩定性,並提升了 Windows 和 Linux 的圖形保真度。

Impeller 渲染

Impeller 適用於 macOS、Linux 和 Windows 的符號距離場(SDF)渲染器直接在 GPU 上繪製清晰的邊界、形狀和漸層,而不是在 CPU 上計算複雜的幾何圖形。這降低了影格渲染時間並保持捲動和動畫流暢,即使在充滿卡片、邊框和陰影的螢幕上也是如此。Flutter 3.50 將此快速路徑擴展到線條、細邊框、圓角和漸層,同時加速 macOS 漸層和重複的文字陰影。對於自訂 3D 場景和 2D 遊戲,Flutter GPU 增加了對 2D 紋理陣列的支援。遊戲和圖形開發人員現在可以將精靈圖集、圖塊地圖和地形層批次處理成更少的繪圖呼叫,而不會在圖塊之間出現視覺縫隙。當著色器管道配置錯誤時,Flutter GPU 還會報告更清晰的 Dart 錯誤訊息,幫助您更快地捕捉和修復圖形錯誤。

Side-by-side zoomed pixel comparison showing smooth anti-aliased diagonal edges rendered with Impeller SDF versus stepped edges with MSAA.
Impeller 的 Signed Distance Field (SDF) 渲染器直接在 GPU 上繪製清晰、抗鋸齒的邊緣。

社群貢獻者也帶來了高影響力的 OpenGL 啟動和 GPU 穩定性修復。 xiaowei-guan 將 OpenGL 著色器設定移至背景執行緒,以便應用程式更快地顯示其第一幀。 seunggi 解決了載入紋理時的 Mali GPU 崩潰問題,防止受影響的 Android 設備上應用程式意外退出。 krll-kov 修正了舊設備上的 OpenGL ES 2 紋理渲染,並在動畫圖片中保持了跨幀的透明度。Flutter 也會在 B 系列 PowerVR GPU 上自動從 Vulkan 回退到 OpenGL,以防止渲染故障。

桌面圖形

在 Windows 和 Linux 上,桌面渲染獲得了關鍵的保真度和效能升級。Windows 引擎現在預設請求 OpenGL ES 3,同時保留自動 OpenGL ES 2 回退支援,解鎖了現代圖形功能,而不會丟棄舊的 PC。Windows 應用程式現在可以從包含非 ASCII 字元的路徑(例如國際使用者名稱或資料夾名稱)載入資產和檔案。Windows 還以改進的對比度渲染更清晰的文字,並跳過每個畫面額外的全螢幕複製以減少 GPU 開銷。在 Windows 和 Linux 上,改進的抗鋸齒平滑了分層 UI 元素和自訂形狀上的鋸齒邊緣。

iOS 和 macOS

作為 Xcode 27 和 iOS 27 發佈以來的第一個穩定版本,Flutter 3.50 減少了 iOS 觸控延遲,平滑了實體設備除錯,並持續從 CocoaPods 和 Intel Mac 遷移。

更快的 iOS 觸控反應

由社群成員 Matej Knopp 貢獻,對引擎如何安排 iOS 觸控事件後的幀生成方式進行了重大改造,將觸控事件立即傳遞給框架,以便幀在相同的顯示刷新中建立,而不是等待下一個 vsync。結果是輸入延遲減少了一個完整的幀(60 Hz iOS 設備上快 16 毫秒,120 Hz ProMotion 顯示器上快 8 毫秒),透過對追溯到 2015 年 iPhone 6s 的硬體進行廣泛基準測試進行驗證。我們還有更多工作正在進行中,以在即將推出的版本中進一步降低 iOS 輸入延遲。請持續關注。

Side-by-side comparison showing faster iOS touch response in Flutter 3.50.
Flutter 3.50 中更快的 iOS 觸控排程將輸入延遲減少了一個完整的幀。

了解更多:iOS 觸控輸入延遲基準測試 (#191199)

在 iOS 27 上除錯

在新的配對或新升級的 iPhone 上運行應用程式不再會停留在白屏上,直到您開啟 Xcode 一次。Flutter CLI 現在會自動準備設備,並在準備失敗時告訴您該怎麼做。Flutter 3.50 還解決了在 iOS 27 設備上運行 flutter run 時間歇性啟動崩潰的問題。使用 Xcode 27 進行建置,自 iOS 17 以來首次在實體 iPhone 和 iPad 上恢復了 flutter screenshot。您現在可以在除錯期間直接從終端機捕獲正在運行應用程式的螢幕截圖。

Swift Package Manager 過渡

隨著 CocoaPods trunk 將於 2026 年 12 月變為唯讀,Flutter 的臨時 CocoaPods 後備方案正在逐漸停止。從 Flutter 3.50 開始,停用 Swift Package Manager 會在每個 Flutter CLI 命令上觸發警告。在未來的版本中,我們將移除選擇退出標誌,最終也將移除 CocoaPods 後備方案本身。如果您之前為了解決建置問題而停用了 Swift Package Manager,請立即重新啟用它以驗證您的設定。

了解更多:Swift Package Manager for plugin authors

Apple Silicon macOS 建置

Flutter 不再在 Intel Mac 硬體上進行測試,並且當您在 Intel Mac 上建置時,CLI 現在會發出一次警告。當未來版本移除 Intel Mac 主機支援時,為 iOS、macOS 或 Android 建置將需要 Apple Silicon Mac。因為 Xcode 27 不再要求 macOS 應用程式捆綁 Intel 切片,您現在可以透過執行 flutter config --enable-macos-arm64-only 來建置僅限 Apple Silicon 的 macOS 應用程式,它將在即將推出的版本中成為預設設定。

了解更多:macOS Intel 棄用策略

Android

在 Android 上,Flutter 3.50 強化了發佈模式 Intent 安全性,擴展了平台視圖的混合合成++,並解鎖了對 Android Gradle Plugin 9 新 DSL 的選擇加入支援。

Intent 安全強化

以前,使用 Flutter 建置的 Android 應用程式接受來自使用者設備上安裝的任何應用程式的 runtime Intent 設定額外參數。Flutter 3.50 在發佈版本中解決了這個漏洞,同時保持標準的深度連結完整:發佈版本會忽略來自外部 Intent 額外參數的 route、dart_entrypoint 和 cached_engine_id 值,並拒絕透過 Intent 額外參數提供或與預建發佈 APK ( --use-application-binary) 配對的 runtime 引擎標誌(例如 --dart-flags)。偵錯版本會記錄被忽略額外參數的警告,而針對預建二進位檔測試自訂引擎標誌現在需要設定檔模式或全新的建置。

了解更多:更嚴格的 Android Intent 應用程式進入點驗證 和 限制發佈模式下的 Android 引擎標誌

混合合成++

Hybrid Composition++ (HCPP) 是 Flutter 最新的平台視圖架構,用於嵌入原生 Android 視圖,例如地圖和網頁視圖,而不會像舊的合成模式那樣出現捲動撕裂或文字輸入故障。透過將合成直接交給 Android 作業系統,HCPP 提供了更流暢的捲動。Flutter 3.50 透過 AndroidViewSurface 上的無緩衝動態事件分派和 RenderAndroidView 中的佈局排序修復進一步改進了它。社群貢獻者 Amaan Ansari 還修復了 FlutterEngine 壽命比其 Activity 長時發生的崩潰問題。

HCPP 自 Flutter 3.44 穩定版以來就已可用,並且截至 3.49.0-0.1.pre,現在已在 beta 頻道上預設啟用。它將在未來的版本中成為穩定的預設值。請記住,HCPP 需要 Android 14 (API 34) 或更高版本以及在 Vulkan 上運行的 Impeller;在不符合這兩個要求的設備上,Flutter 會自動回退到您現有的平台視圖模式。在支援的 API 34+ Vulkan 設備上,執行 flutter config --enable-hcpp 並在偵錯模式下觀察 logcat,以便在平台視圖掛載時看到 Using HCPP platform view rendering strategy。

了解更多:使用平台視圖託管原生 Android 視圖

外掛程式內建 Kotlin

正如 Flutter 3.44 有什麼新功能 中介紹的,包含 Android 程式碼的 Flutter Plugin 必須直接遷移到 Android Gradle Plugin 的內建 Kotlin。仍然手動應用 Kotlin Gradle Plugin (KGP) 的 Plugin,即使是在 AGP 版本防護下,將來也會獲得較低的 pub.dev 分數,並且在 AGP 10 下將無法建置。請更新您的 Plugin 的 Gradle 配置,在 pubspec.yaml 中將您的最小 SDK 限制設定為 flutter: ">=3.44.0",並在您的 CHANGELOG.md 中註明此變更。

了解更多:Plugin 作者的內建 Kotlin 遷移指南

AGP 9 新 DSL 支援

在 Flutter 3.47 和 Flutter 3.50 之間,我們根據現代公共 Android Gradle Plugin 9 API 而不是傳統 BaseExtension 內部結構重寫了 Flutter Gradle Plugin。我們還將 flutter/packages 中的所有第一方 Plugin 遷移以支援其支援的 AGP 版本中的內建 Kotlin。在 Flutter 3.50 穩定版上,一旦您專案中的每個 Plugin 都支援內建 Kotlin,您就可以選擇將您的應用程式加入 AGP 9 的新 DSL。

了解更多:應用程式開發人員的內建 Kotlin 遷移指南

更新 Android 工具鏈預設值

由 Flutter 3.50 生成的新 Android 專案使用 Android Gradle Plugin (AGP) 9.3.1 和 Gradle 9.5.0,並已針對 Kotlin Gradle Plugin 2.4.20 和至少 Java 17 進行驗證。當您的專案使用 AGP 的內建 Kotlin 時,Flutter 不再列印 Kotlin 版本警告,因為 AGP 直接管理 Kotlin 相容性。此版本中暴露給您的 Gradle 建置檔案的預設 SDK 變數是 flutter.compileSdkVersion API 36、flutter.targetSdkVersion API 36 和 flutter.minSdkVersion API 24。

開發者體驗

Flutter 3.50 的日常工作流程更新帶來了官方 AI 代理外掛、可重複使用的套件技能,以及 Dart Analysis Server 中更快的冷重新命名重構。

AI 編碼外掛和套件技能

Flutter 3.50 擴展了對使用 AI 編碼助手進行建置的團隊的第一方工具。官方 Flutter 代理外掛程式捆綁了任務導向的代理技能、Dart 和 Flutter MCP 伺服器配置、專門的代理,以及持久的代理規則,包括一個主動的熱重新載入規則 (flutter-hot-reload),它會在 lib/ 下編輯後透過 MCP 伺服器觸發 hot_reload 或 hot_restart。

套件作者也可以直接在其 pub.dev 套件的頂層 skills/ 目錄中發布代理技能,以便應用程式開發人員可以使用 dart run skills@ get 從其套件相依性中發現和安裝技能。在發布之前,作者可以在 CI 中執行 package:skills_lint,以根據 Agent Skills 規範檢查 SKILL.md 的前置內容、目錄結構和命名約定。

了解更多:開始使用 AI 進行開發 和 套件技能

分析伺服器和 DevTools

在日常編輯器工作流程中,Dart 分析伺服器在填充搜尋索引時,會消除跨分析驅動程式的共享套件目錄發現的重複,從而加速多套件工作空間上的冷重新命名請求(在 16 個上下文的 DevTools 工作空間上快 57%,從 2.0s 降至 0.9s)。此外,Flutter DevTools 現在使用獨立的 material_ui 和 cupertino_ui 套件。

AI 和生態系統

在更廣泛的生態系統中,生產 AI 套件達到了重要的里程碑,同時有 100 萬個 Google Play 應用程式和一個開放的 Flutter RFC 流程。

Genkit Dart 1.0

Genkit Dart 達到了其穩定的 1.0 版本,用於在 Dart 和 Flutter 中建立全棧、多模型代理應用程式,具有端到端型別安全、串流流程、工具呼叫和 OpenTelemetry 追蹤。

Animated demo of the Cymbal Bistro Flutter app using Genkit Dart and A2UI to stream an interactive restaurant reservation card and invoke a booking tool.
Genkit Dart 1.0 在 Flutter 應用程式中串流互動式 A2UI 預訂流程和工具呼叫。

了解更多:Genkit Dart 文件

搭載 MediaPipe 的裝置端 AI

Google AI Edge 在 pub.dev 上推出的全新 MediaPipe 套件,為 Flutter 應用程式帶來了 20 項裝置端的視覺、音訊和文字任務,這些任務由 LiteRT 和 LiteRT-LM 提供支援。直接在裝置上運行這些模型可以避免網路延遲和雲端推斷成本。

了解更多:適用於 Flutter 的 MediaPipe Tasks 圖庫

一百萬個 Play Store 應用程式

Flutter 社群最近跨越了一個重要的里程碑:超過一百萬個 Flutter 應用程式在 Google Play 上發布。感謝每一位使用 Flutter 進行建置、發布和成長的開發人員和團隊。

套件生態系統亮點

在更廣泛的套件生態系統中,在我們的基準測試中,捲軸列表內重複使用內嵌 AdMob 橫幅廣告在 iOS 上帶來了 23% 的效能提升。我們還想強調兩個在此週期中發布的出色社群套件:Terradart 用於在純 Dart 中定義雲端基礎設施,以及 vyuh_diagram_flutter 用於在 Flutter 中以程式設計方式建立互動式節點圖。

了解更多:AdMob 橫幅廣告回收指南

客戶案例

世界各地的團隊繼續使用 Flutter 發佈雄心勃勃的生產應用程式。 Holafly 在三個月內將其行動應用程式統一到一個 Flutter 單一儲存庫中,為超過 200 萬月活躍使用者和 2500 萬 eSIM 提供服務。 Knowunity 將 Flutter 與 Go 和 Gemini 結合,支援 800 萬至 1000 萬月活躍學生。 Buzz 在 iOS 和 Android 上使用 Flutter 驅動一個開源工作區,讓人們和 AI 代理並肩協作。Google AI Edge 團隊使用 Flutter 建置了 Eloquent 和 Foresight 應用程式,將裝置端 AI 推斷帶給行動和桌面上的使用者。

Flutter 的商業價值

為了幫助工程主管評估 Flutter 未來的發展藍圖,我們的新 2026 白皮書分享了有關工程生產力、開發速度和商業投資回報的數據驅動見解。

了解更多:Flutter 的商業價值

宣布 Flutter RFCs

Flutter 正在採用公開的 Request for Comments (RFC) 流程來進行技術設計和決策。RFC 提供了一個協作、透明的空間,可以在早期分享想法、探索權衡、與子系統維護者保持一致,並在編寫生產程式碼之前建立架構共識。

了解更多:Flutter 設計文件 和 flutter/rfc

棄用與重大變更

Flutter 3.50 正式棄用了耦合的 package:flutter/material.dart 和 package:flutter/cupertino.dart 函式庫,以及它們的 flutter_localizations 委派。Android 發布版本也強制執行更嚴格的 Intent 驗證,並阻止偵錯引擎標誌,以及四個 Widget 行為更新。在 package:material_ui 中,DropdownButtonFormField 新增了一個明確的 enabled 屬性,因此您可以直接停用下拉式選單,而不是將 null 傳遞給 onChanged 回調。包含軟連字號的文字現在會在文字換行時自動顯示可見的連字號。右到左文字中的內聯 WidgetSpan 元素現在會以自然的閱讀順序顯示,而不是意外反轉。在行動瀏覽器上,當設定 viewport-fit=cover 時,SafeArea 現在會考慮設備缺口和主頁指示器,使 UI 不會被螢幕切口遮擋。

此版本也包含了兩項框架清理:

  • 先前已棄用的 ignoringSemantics 屬性已從 IgnorePointer、AbsorbPointer、SliverIgnorePointer 及其渲染物件中移除。
  • 當子項沒有基線時,RenderProxyBox 和其他使用 RenderProxyBoxMixin 的渲染物件上的 computeDryBaseline 現在會返回 null,而不是斷言失敗。

了解更多:Flutter 重大變更

新鮮出爐,準備升級

感謝社群中的每一位貢獻者,他們提交了議題、審查了設計並提交了 Pull Request,幫助塑造 Flutter 的下一個十年。Flutter 3.50 與 Dart 3.14 一起新鮮出爐。立即升級您的 SDK,以試用此版本中的所有功能:

1
flutter upgrade

若要探索此版本中包含的所有 Pull Request,請查看 Flutter 3.50 發行說明 和 重大變更。

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/ai-makes-it-easy-to-migrate-flutter-makes-it-easy-to-scale

AI 讓遷移變得容易。Flutter 讓擴展變得容易。

為什麼驗證速度,而不是程式碼生成,是代理開發的真正瓶頸,以及單一事實來源如何為多平台擴展提供動力。

過去幾個月,行動工程討論一直圍繞著遷移故事。現在,AI 程式碼工具可以在一個下午內搭建立新原型,工程主管提出了一個公平的問題:

「如果現代 AI 模型可以幾乎即時地生成 Swift 和 Kotlin,為什麼不直接用原生程式碼建立兩次呢?」

在 Google,我們過去一年一直在研究這種動態,跨越我們的內部團隊、企業合作夥伴和更廣泛的開源社群。正如我們今年早些時候在 Dart 和 Flutter 如何思考 2026 年的 AI 中分享的:AI 讓遷移到新框架變得容易。Flutter 讓擴展變得容易。 將第一天的遷移速度與第 2000 天的生產維護混淆,可能會讓工程組織付出多年的速度代價。

以下是統一程式碼庫在 AI 時代比以往任何時候都更重要的第一原理、代理工具和企業數據。

為什麼「寫兩遍」會在大規模上崩潰

要點 1:程式碼庫是累積的記憶(「爭論一次,建立一次」)

當工程團隊在第一天使用 AI 代理重寫應用程式時,模型擅長於初始的順利路徑:語法轉換、REST 端點、UI 佈局和樣板數據模型。從頭開始開發感覺毫不費力,因為它沒有任何歷史。

然而,到了第 2000 天,生產程式碼庫不再僅僅是 UI 語法,而是組織累積的記憶。它包含多年來已解決的產品爭論、生產事後分析、合規規則和邊緣案例修復。正如 Shorebird 的創始人兼 Flutter 共同創作者 Eric Seidel 最近在 AI 時代的可攜式軟體 中所寫:

「應用程式不僅僅是 UI 螢幕的捆綁包。應用程式是意圖交付和意圖捕獲的產物。」

在統一的多平台程式碼庫中,團隊爭論一次並建立一次。當組織要求 AI 用原生 Swift 和 Kotlin「寫兩遍」時,它並沒有消除複雜性;它將其分岔了。 每個新要求和邊緣案例都必須在多個堆疊中獨立浮現、解決和維護。

要點 2:平行代理引入非確定性漂移

在系統工程中,N 個離散介面之間的協調開銷呈組合式增長:

$$ \frac{N(N-1)}{2} $$

一個常見的假設是 AI 代理可以消除這種協調稅:「我們將讓代理 A 撰寫 Swift,代理 B 撰寫 Kotlin。」實際上,每個 LLM 生成都是機率性的。即使使用相同的提示和規範,兩個在不同語言範式中工作的代理也會做出不同的假設:

  • 在 iOS 上: 代理 A 使用非同步流實現結帳狀態機,該流會丟棄過時的事件。
  • 在 Android 上: 代理 B 使用反應式流實現相同的狀態機,該流會將事件排入佇列。

The Parallel Agent Challenge. Directing two autonomous agents on two separate native codebases doesn't eliminate coordination — it introduces non-deterministic drift, shown as two agent trajectories diverging over time.

這兩種實作在第 1 天都通過了本地單元測試。幾週後在生產中,Android 結帳轉換率下降了 3%,例如,因為在間歇性網路中斷期間,重試時機的行為不同。資深工程師必須花費數天(或許多權杖)深入研究兩個不同的技術堆疊才能診斷根本原因。Flutter 從源頭消除了漂移。只有一個狀態機、一個數據序列化層和一個規範的業務邏輯來源。

要點 3:驗證而非生成才是真正的瓶頸

程式碼生成不再是軟體開發的瓶頸,驗證才是。雖然 73% 的開發人員表示使用 AI 開始任務時感覺更有效率,但 46% 的開發人員不信任 AI 在關鍵除錯方面的準確性,除非經過手動檢查。

代理的輸出品質受其回饋迴圈的速度和精確度所限制:

驗證步驟 傳統原生工作流程(Swift / Kotlin) Flutter 封閉迴圈工作流程
程式碼到執行延遲 每次建置、連結、歸檔和模擬器部署需要 2 到 5 分鐘。四次除錯運行需要等待編譯器 20 多分鐘。 透過有狀態熱重新載入在 200 毫秒內 將程式碼更改注入正在運行的 Dart VM,同時保留狀態。
自動化測試 需要繁重的 OS 模擬器生命週期和背景模擬器。 透過在獨立環境中執行無頭 Widget 測試,**<500 毫秒**。
UI 和狀態檢查 機率性且緩慢: 捕捉模擬器螢幕截圖並將像素傳遞給視覺模型以猜測佈局準確性。 確定性且結構化: 透過 MCP 查詢 Dart VM 服務和 DevTools,以檢查確切的 Widget 和 RenderObject 樹。

Flutter 旨在為人類開發人員提供即時回饋。正是這種架構使其在高速代理驗證方面獨樹一幟。

建構線束和運行時畫布

Flutter 的宣告式 UI、反應式 Widget 樹、受管理的運行時和有狀態熱重新載入,現在支援兩種不同的 AI 領域:建構時的 AI 輔助開發 和運行時的 生成式 UI。

要點 4:更好的線束勝過更好的提示

在沒有引導的情況下,代理對 Flutter 的了解僅限於其訓練截止日期:通常會使用已棄用的 API,誤讀第三方套件,或在驗證更改之前停止。提示工程無法可靠地解決這個問題;線束工程 可以。

我們在內部透過 Project One Shot 測試了這一點,Flutter 工程師 Reid Baker 和 Camille Simon 指派一名代理程式自主解決 camera_android_camerax 中的生產錯誤,這是我們最複雜的 Plugin 之一,涉及原生 Android CameraX 生命週期和表面紋理綁定。諸如「避免記憶體洩漏」之類的通用指令失敗了。代理程式僅在與確定性線束配對時才成功:透過 MCP 進行 AST 分析、特定套件的領域技能以及強制性的測試驅動防護(透過失敗的測試重現 → 撰寫最小修復 → 通過套件和洩漏檢查器)。

為了讓每個 Flutter 團隊都能擁有相同的線束,我們推出了三項核心功能:

  • 官方 Dart 和 Flutter 代理 Plugin: 捆綁我們專用的模型上下文協定 (MCP) 伺服器以及 10 個 Flutter 和 14 個 Dart 技能。它們讓代理程式直接存取 Dart 分析器和 Widget Inspector,以強制執行框架最佳實踐。
  • 專業的反重力代理程式: 嵌入式代理程式,可自動審核正在運行的 Flutter 應用程式是否存在輔助功能問題並提交慣用修復。
  • 代理技能 CLI: 與我們在 Serverpod 的朋友一起開發,Skills CLI 使套件作者能夠在其套件中提供領域特定的代理技能。運行一個指令即可為代理程式配備庫特定的規則:
1
2
# Scans immediate dependencies and discovers available skills
dart run skills@ get

我們針對實際的評估套件(涵蓋佈局溢出解決、響應式設計、REST API 連線、主題設定和許多其他日常開發人員任務)對線束中的每個工具和技能進行基準測試:

評估前沿模型 基準(無 Flutter 工具) 增強(有 Flutter 線束和 MCP)
Gemini 3.5 Flash 84.1% 85.5% (+1.4%)
Gemini 3.1 Pro 89.6% 92.7% (+3.1%)

要點 5:生成式 UI 是下一代通用畫布

在執行時,應用程式正從靜態的、預先渲染的畫面演變為即時適應使用者意圖的動態介面。Google 和業界合作夥伴正在 a2ui.org 開發 A2UI(代理到使用者介面),這是一種用於生成式介面的開放標準,為 Flutter 的 GenUI SDK 提供動力。

今年早些時候,我們將此投入生產,推出了 GenLatte,這是一個基於 Flutter、Firebase Functions 和 Genkit 的 AI 原生點餐系統,已實際供應超過 3,000 杯咖啡。其運作方式如下:客戶描述他們喜歡的地方,Gemini 設計自訂的拿鐵藝術,Flutter 的 GenUI 引擎動態水合互動式 UI 控制項,以便使用者可以完善結果。

如果 AI 代理必須在五種不同的作業系統上協商五種不同的原生佈局引擎,它就無法輕鬆地在執行時合成動態介面。然而,Flutter 是 GenUI 的理想基礎,因為它提供了將 UI 串流和渲染到任何地方所需的可移植、宣告式畫布,並且具有完全的一致性。

大規模證明:多表面覆蓋範圍和企業實況

數位產品不再僅限於手機。在 iOS、Android、WebAssembly 網頁、桌面(Windows、macOS、Linux)、車載顯示器和嵌入式資訊亭上維護獨立的原生程式碼庫,會使您的測試矩陣和代幣成本增加六倍。透過 Impeller 提供動力—它預先編譯 Metal、Vulkan 和 OpenGL 的著色器以提供無卡頓的 120fps 渲染—單一 Flutter 程式碼庫可部署到六個或更多目標。

這種架構優勢是全球工程團隊將任務關鍵型生產系統標準化為 Flutter 的原因:

  • 小米 (SU7 電動車 & HyperOS 4): 使用 Flutter 建立了 SU7 電動車配套應用程式,並使用 Flutter 重新編寫了核心 HyperOS 4 系統應用程式(圖庫、啟動器、天氣)的 UI,以及使用 Rust 處理數億部手機的後端邏輯。
  • Universal Destinations & Experiences: 統一行動點餐、互動式公園地圖、數位錢包和 Epic Universe 公園資訊亭,將程式碼庫大小減少 45%,且幾乎沒有當機。
  • 汽車: 豐田和 Lexus 在 RAV4 等車型中為車載資訊娛樂系統提供動力,而 BMW 則將全球 My BMW 應用程式標準化為 Flutter。
  • 金融科技: Nubank 為超過 1 億客戶提供服務,每週部署數百次。

使用者會注意到框架嗎?

在編寫獨立原生應用程式的論點之下,有一個不言而喻的前提:跨平台 UI 會給終端使用者在應用程式商店中注意到的體驗帶來負擔。

為了驗證這項主張,我們審查了從 Apple App Store 和 Google Play Store 中 201,000 個熱門行動應用程式獨立收集的 Apptopia 數據:

App Store 平台 Flutter 應用程式平均評分 非 Flutter 應用程式平均評分
Google Play 商店 4.1 ★(9,000 個應用程式樣本) 4.1 ★(66,000 個應用程式樣本)
Apple App Store 4.0 ★(13,000 個應用程式樣本) 4.0 ★(113,000 個應用程式樣本)

使用者不會評估底層框架;他們評估回應能力、視覺精美度和可靠性。

給團隊的 8 個討論點

下次您的組織討論是否要用 AI 維護多個原生程式碼庫時,請將這八個討論點帶入對話中:

Eight talking points for pairing Flutter and AI: 1. Single Codebase Memory, 2. No Non-Deterministic Drift, 3. Fast Verification Feedback Loop, 4. Premier Canvas for GenUI, 5. Multi-Surface Reach (1 Codebase = 6+ Deployments), 6. Multi-Platform GPU-Accelerated Rendering via Impeller, 7. Proven Enterprise Grade Quality and Stability, and 8. Developer Experience AI Readiness.

接下來會是什麼?

加速在第一天很容易推銷。架構規模是讓您的軟體在第 2000 天保持可靠的關鍵。若要在您的工作流程中開始搭配 Flutter 和 AI:

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://dart.dev/blog/announcing-genkit-dart-1-0

宣布 Genkit Dart 1.0:使用 Dart 和 Flutter 建構生產就緒的智能應用程式

發布 Genkit Dart 的穩定 1.0 版本,這是一個用於使用 Dart 建構生產就緒的智能應用程式和 AI 功能的開源框架。

Dart 和 Flutter 讓您可以從單一程式碼庫為行動、網頁和桌面建構高品質的應用程式。藉助 Genkit Dart,您可以將同樣的生產力帶到全端、智能應用程式中。

今天,我們宣布 Genkit Dart 1.0,這是 Google 開源框架的第一個穩定、生產就緒版本,用於使用 Dart 建構 AI 驅動的功能和代理程式。自從我們今年稍早的 預覽發布 以來,來自 Dart 和 Flutter 社群的回饋幫助我們改進了核心 API 並擴展了用於生產工作負載的工具包。

我們已在 Flutter 部落格上發布了 完整的教學和程式碼深入探討。

若要立即開始,請將 genkit 加入您的 Dart 或 Flutter 專案:

1
dart pub add genkit

您還可以安裝代理程式技能,讓 Antigravity、Claude Code 和 Codex 等 AI 程式碼助理具備最新的 Genkit Dart API 知識:

1
npx skills add genkit-ai/skills

Genkit Dart 1.0 的亮點

Genkit Dart 1.0 將統一的模型介面、端到端類型安全和代理程式基本功能帶到任何 Dart 環境中,無論您是建構後端服務、CLI 工具還是全端 Flutter 應用程式:

  • 跨模型提供者的一個 API: 在 Google Gemini、Anthropic Claude、OpenAI 和與 OpenAI 相容的模型之間切換,而無需重寫應用程式邏輯。
  • 使用 schemantic 實現端到端類型安全: 使用 schemantic 在 Dart 中定義資料模式一次,以生成結構化模型輸出,將 AI 邏輯包裝在可觀察的 流程 中,並在 Dart 伺服器和客戶端之間共享類型。
  • 跨客戶端和伺服器的靈活部署: 使用 GenkitRouter 在 Dart 伺服器上運行流程,使用 defineRemoteAction 從客戶端調用它們,或者在將模型請求透過安全後端路由的同時將 AI 邏輯保留在客戶端上,使用 defineRemoteModel。
  • 人機互動工具中斷: 當某個動作需要使用者確認時,透過返回 .interrupt(...) 暫停 defineTool 內的工具執行,然後從中斷處恢復生成。
  • 可組合的生成中介軟體: 附加來自 genkit_middleware 的預打包中介軟體(包括自動重試、動態 SKILL.md 加載和工具批准規則),或使用 defineGenerateMiddleware 編寫自定義中介軟體。
  • 使用 Dotprompt 進行提示管理: 使用 Dotprompt 將提示模板、模型設定和模式儲存在 .prompt 檔案中。
  • 本機開發者 UI 和 OpenTelemetry: 使用 genkit start 在本機測試流程並檢查執行追蹤,並使用 genkit_otel 匯出生產追蹤和指標。
  • 實驗性有狀態代理程式和 A2UI: 試用多回合持久性代理程式(package:genkit/experimental.dart 中的 defineAgent 和 remoteAgent)並使用 genkit_a2ui 串流互動式原生 UI 介面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Schema()
abstract class $TripRequest {
String get destination;
int get days;
}
// ...plus an Itinerary schema for the result.
final ai = Genkit(plugins: [googleAI()]);

final planTrip = ai.defineFlow(
name: 'planTrip',
inputSchema: TripRequest.$schema,
outputSchema: Itinerary.$schema,
fn: (request, _) async {
final response = await ai.generate(
model: googleAI.gemini('gemini-flash-latest'),
prompt: 'Plan a ${request.days}-day trip to ${request.destination}.',
outputSchema: Itinerary.$schema,
);
return response.output!;
},
);

await (GenkitRouter()..addAction(planTrip)).serve(port: 8080); // POST /planTrip

閱讀完整公告

若要獲取涵蓋多模型生成、遠端動作和模型、工具中斷、中介軟體、Dotprompt、OpenTelemetry、有狀態代理程式以及使用 A2UI 進行生成式 UI 的完整程式碼範例,請閱讀 Flutter 部落格上的 Genkit Dart 1.0 完整公告。

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/announcing-genkit-dart-1-0

宣布 Genkit Dart 1.0:使用 Dart 和 Flutter 建構正式發行的代理應用程式

宣布 Genkit Dart 的穩定 1.0 版本發佈,這是一個用於使用 Dart 和 Flutter 建構由 AI 驅動的功能和代理工作流程的開源框架。

Announcing Genkit Dart 1.0

Dart 和 Flutter 讓您可以從單一程式碼庫為行動裝置、網頁和桌面建構高品質的應用程式。藉助 Genkit Dart,您可以將同樣的生產力帶到全端代理應用程式中。

今天,我們宣布 Genkit Dart 1.0,這是 Google 的開源框架的第一個穩定、可正式發行的版本,用於使用 Dart 建構由 AI 驅動的功能和代理。自今年稍早發佈 預覽版 以來,Dart 和 Flutter 社群的回饋幫助我們改進了 API 並擴展了用於正式發行工作負載的工具套件。

若要開始使用,請將 genkit 加入您的專案:

1
dart pub add genkit

您也可以安裝代理技能,為 Antigravity、Claude Code 和 Codex 等 AI 程式碼助理提供最新的 Genkit Dart API 和最佳實踐知識:

1
npx skills add genkit-ai/skills

為什麼選擇 Genkit Dart

Genkit 提供跨模型供應商的統一 API、伺服器和用戶端之間的端對端型別安全,以及用於測試和偵錯 AI 工作流程的本機工具。

使用一個 API 使用任何模型

Genkit 透過單一介面支援 Google Gemini、Anthropic Claude、OpenAI 和與 OpenAI 相容的模型。您可以將模型供應商註冊為 Plugin,並且可以在不重寫應用程式邏輯的情況下切換模型:

1
2
3
4
5
6
7
8
9
10
11
12
final ai = Genkit(plugins: [googleAI(), anthropic()]);
final prompt = 'Suggest a weekend getaway from San Francisco.';

final fromGemini = await ai.generate(
model: googleAI.gemini('gemini-flash-latest'),
prompt: prompt,
);

final fromClaude = await ai.generate(
model: anthropic.model('claude-sonnet-5-5'),
prompt: prompt,
);

使用流程實現端對端型別安全

Genkit 讓您可以將 AI 邏輯包裝到流程中:強型別、可觀察的函數,易於測試並部署為 HTTP 端點。使用 schemantic 套件,您可以一次在 Dart 中定義資料模式,從模型生成結構化輸出,並在後端和 Flutter 應用程式之間共用這些精確的型別:

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
26
27
28
29
30
31
32
33
// shared/lib/models.dart (used by both server and app)
@Schema()
abstract class $TripRequest {
String get destination;
int get days;
}
// ...plus an Itinerary schema for the result.

// server/bin/server.dart
final planTrip = ai.defineFlow(
name: 'planTrip',
inputSchema: TripRequest.$schema,
outputSchema: Itinerary.$schema,
fn: (request, _) async {
final response = await ai.generate(
model: googleAI.gemini('gemini-flash-latest'),
prompt: 'Plan a ${request.days}-day trip to ${request.destination}.',
outputSchema: Itinerary.$schema,
);
return response.output!;
},
);
await (GenkitRouter()..addAction(planTrip)).serve(port: 8080); // POST /planTrip

// app/lib/main.dart
final planTrip = defineRemoteAction(
url: 'https://api.example.com/planTrip', // Your Genkit endpoint
inputSchema: TripRequest.$schema,
outputSchema: Itinerary.$schema,
);
final itinerary = await planTrip(
input: TripRequest(destination: 'Kyoto', days: 5),
);

Dart 執行到哪裡,程式碼就執行到哪裡

因為您的 AI 邏輯是用標準 Dart 撰寫的,所以您可以透過熱重載快速疊代,並靈活地在適合您架構的任何地方執行程式碼:

  • 直接在 Flutter 中:直接從您的應用程式呼叫模型,進行快速原型開發或自帶金鑰體驗(切勿在已發佈的用戶端應用程式中嵌入私有 API 金鑰)。
  • 在 Dart 伺服器上:執行複雜的流程並將敏感的提示保留在後端,然後使用 defineRemoteAction 從 Flutter 呼叫它們,如上所示。
  • 在 Flutter 中使用遠端模型:將您的 AI 邏輯保留在 Flutter 應用程式中,同時透過輕量級 Genkit 後端路由模型請求,該後端保護您的 API 金鑰並強制執行授權:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// server/bin/server.dart
final genkit = GenkitRouter()
..addAction(
googleAI().model('gemini-flash-latest'),
path: '/gemini',
// Runs before the model; throw a GenkitException to reject the request.
contextProvider: (request) async =>
{'user': await verifyUser(request.headers['authorization'])},
);
await genkit.serve(port: 8080);

// app/lib/main.dart
final ai = Genkit();
final gemini = ai.defineRemoteModel(
name: 'gemini',
url: 'https://api.example.com/gemini',
headers: (context) async =>
{'Authorization': 'Bearer ${await getIdToken()}'},
);
final response = await ai.generate(
model: gemini,
prompt: 'Suggest a packing list for Kyoto in April.',
);

使用開發人員 UI 進行測試和偵錯

Genkit 包含一個本機開發人員 UI,用於測試流程、嘗試提示以及逐步檢查執行追蹤。使用 Genkit CLI 與您的 Dart 程序一起啟動它:

1
genkit start -- dart run bin/server.dart
Inspecting an agent's model and tool calls in the Genkit Developer UI
在 Genkit 開發人員 UI 中檢查代理的模型和工具呼叫

專為代理工作流程而建構

自預覽版發佈以來,我們已擴展 Genkit Dart,使其具備為多步驟代理工作流程設計的功能,包括人工介入中斷、生成中介軟體、提示管理和正式發行遙測。

為模型提供人工介入工具

工具讓模型可以呼叫您的 Dart 函數來獲取資料或觸發動作,例如搜尋航班或預訂飯店。當動作需要使用者確認時,工具可以透過回傳 .interrupt(...) 而不是 .response(...) 來暫停生成循環:

1
2
3
4
5
6
7
8
9
10
11
12
13
final bookHotel = ai.defineTool(
name: 'bookHotel',
description: 'Books a hotel room for the user.',
inputSchema: HotelBooking.$schema,
fn: (input, ctx) async {
// Ask the user to confirm before charging their card.
if (ctx.resumed == null) {
return .interrupt({'hotel': input.hotelName, 'total': input.totalPrice});
}
final confirmation = await hotels.book(input);
return .response(confirmation.id);
},
);

將核准檢查放在工具內部可確保模型無法繞過它。當 generate 回傳 FinishReason.interrupted 時,您的 Flutter 應用程式可以提示使用者確認並從暫停的位置恢復執行。

使用中介軟體擴展生成

中介軟體直接掛鉤到 generate 循環中,以攔截模型呼叫、注入工具或修改請求和回應。使用 genkit 和 genkit_middleware,您可以將預先打包的功能(如自動重試、動態 SKILL.md 載入和工具核准規則)附加到任何 generate 呼叫:

1
2
3
4
5
6
7
8
9
10
11
12
final ai = Genkit(plugins: [googleAI(), SkillsPlugin(), ToolApprovalPlugin()]);

final response = await ai.generate(
model: googleAI.gemini('gemini-flash-latest'),
prompt: 'Move my Kyoto hotel check-in to Friday.',
tools: [findBookings, updateBooking],
use: [
retry(maxRetries: 3),
skills(skillPaths: ['./skills']),
toolApproval(approved: ['findBookings', 'use_skill']),
],
);

您也可以使用 defineGenerateMiddleware 編寫自訂中介軟體,用於記錄、快取或模型備援等跨領域邏輯。

使用 Dotprompt 管理提示

Dotprompt 讓您可以將提示範本、模型設定和輸入/輸出模式一起管理在 .prompt 檔案中。Genkit 會自動從您的 prompts/ 目錄載入提示,因此您可以在 Dart 中將它們作為可呼叫函數來調用:

1
2
3
4
5
6
7
---
model: googleai/gemini-flash-latest
input:
schema:
destination: string
---
Write a friendly, two-sentence introduction to {{destination}} for a first-time visitor.
1
2
final introPrompt = await ai.prompt('destinationIntro');
final response = await introPrompt({'destination': 'Kyoto'});

監控您正式發行的應用程式

當您準備部署時,genkit_otel 套件會使用 OpenTelemetry GenAI 語義約定匯出追蹤、令牌使用量和延遲指標,並直接與您現有的可觀察性後端整合:

1
2
3
4
5
6
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
import 'package:genkit/telemetry.dart';
import 'package:genkit_otel/genkit_otel.dart';

await OTel.initialize();
configureInstrumentation(GenAiInstrumentation());

接下來:有狀態代理和生成式 UI

除了穩定版 1.0 核心之外,我們正在 package:genkit/experimental.dart 導入下開發更進階的代理 API,以便您可以儘早試用並幫助設計它們。

有狀態代理將模型、工具、系統指令和狀態組合到一個 defineAgent 呼叫中。對話會使用會話儲存區在回合和應用程式重啟之間保持不變,您可以使用 remoteAgent 將任務委派給子代理,或透過 HTTP 公開代理以連接您的 Flutter 應用程式:

1
2
3
4
5
6
7
8
9
10
11
12
import 'package:genkit/experimental.dart';

final travelAgent = ai.defineAgent(
name: 'travelAgent',
model: googleAI.gemini('gemini-flash-latest'),
system: 'You help users plan and book trips.',
tools: [searchFlights, bookHotel],
store: FirestoreSessionStore(collection: 'sessions'),
);

final chat = travelAgent.chat(sessionId: 'user-123');
final response = await chat.send(text: 'Find me a weekend in Lisbon.');

使用 A2UI 的生成式 UI 讓代理程式可以串流互動式 UI 介面,而不是純文字。藉助 genkit_a2ui,代理程式可以發出日期選擇器、表單和確認卡等組件,您的 Flutter 應用程式會將其逐步渲染為原生 Widget。請查看 A2UI 指南 以了解更多資訊。

開始使用

Genkit Dart 1.0 現已在 pub.dev 上提供。感謝 Dart 和 Flutter 社群中的每一個人,他們透過預覽版進行建構、報告問題並貢獻拉取請求,幫助 Genkit Dart 達到 1.0 版本。

我們迫不及待想看到您使用 Genkit Dart 1.0 建立的成果!

更多來自 Flutter 的內容

材質和 Cupertino 解耦已實現

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/converted-genlatte-fullstack-dart

我如何將 GenLatte 轉換為全端 Dart

並減少應用程式的伺服器費用

How I converted an app to fullstack Dart
我如何將應用程式轉換為全端 Dart

如果您曾追隨 Flutter 團隊進軍經營快閃咖啡攤的曲折故事,您會知道我們結合了 Flutter、Firebase 和 Gemini 來提供奇異的咖啡。您也知道,由於不收取任何費用,我們未能獲利。

此外,如果您查看了程式碼,您可能還注意到我們的 Flutter 前端是由用 Node.js 撰寫的 Firebase 函式所補充的。這是一個有點令人驚訝的歷史遺物,因為 Firebase 對 Dart 函式的支援幾乎與 GenLatte 首次在 Google Cloud Next 出現的同時進入公開預覽。

在 YouTube 新分頁中觀看:「Flutter 在 Cloud Next 打造了一家咖啡店」

但這正是問題所在——由於 Firebase 對 Dart 的支援在最後一刻熱烈推出,我們根本無法承諾在生產應用程式中使用它,因為時間壓力很大。如果 Firebase 對 Dart 的支援因任何原因而延遲,我們可能就無法及時在 Cloud Next 部署。因此,當 GenLatte 在 4 月、5 月或 6 月出現時,它是以 Node.js 後端運行的。

我個人對此很不滿。

完全擁抱全端 Dart

Dart 和 JavaScript 是具有不同優勢的不同語言。從某種意義上說,這有點像一個不言而喻的說法,但它對伺服器端遷移也有更深層次的影響,因此一對一重寫可能不值得。畢竟,伺服器上的 Dart 可以享受與客戶端上的 Dart 的端到端類型安全,如果仍然堅持使用無類型 Map 那將會是一個真正的遺憾。

考慮到這一點,儘管我的目標可能比我的能力大,但我開始大幅重寫 GenLatte。我的許多目標包括:

  • 在整個堆疊中共享模型(這是一個小小的提升,因為我最初決定將所有資料類別放在單獨的 genlatte_data 套件中)
  • 將我們的 Firebase 函式足跡減少到單一可部署函式
  • 移除所有客戶端寫入;改為專注於呼叫伺服器端函式
  • 移除所有伺服器端觸發器,並明確地在從客戶端呼叫的伺服器端函式中執行任何資料變更
  • 保留所有基於角色的 ACL 檢查
  • 最終進行端到端測試!

這些目標很宏偉,而且不在我 2026 年的路線圖上,所以,我自然而然地保密了我的計畫並開始打字!

執行遷移

與我對原始 GenLatte 的工作不同,該專案主要是在沒有使用 AI 的情況下開發的,我知道我緊迫的時間表需要引入一個 Zoom zoom 打字助手。我選擇的模型是 Gemini 3.6 Flash,它結合了低延遲和通用的知識,這對我來說非常有用。

從高層次來看,我仍然閱讀 Gemini 撰寫的每一行程式碼,以保持對專案的認知所有權。從深入了解的角度開始,這讓事情變得更容易,如果沒有這個承諾,我不認為重寫會在我可用的幾天內成功。程式碼助手,儘管它們很棒,但仍然非常受益於人類的指導。

共享模型

全端類型安全意味著客戶端和伺服器共享程式碼。如前所述,這意味著將任何必須在伺服器上執行的程式碼拆分成自己的套件,並且,至關重要的是,不能依賴 Flutter SDK。這在很大程度上是簡單的,但確實需要對我自己的資料管理套件 pkg:data_layer 進行補充。它的其中一個補充套件 pkg:data_layer_firestore 依賴於客戶端 Firebase SDK(它依賴於 Flutter),因此我被迫添加了 pkg:data_layer_firestore_admin,它是純 Dart 並且因此對伺服器友好。

使用單一 Firebase 函式

預設情況下,您部署的每個 Firebase 函式都會變成自己的 Cloud Run 服務。(如果您是 Firebase Functions 使用者,而這對您來說是新聞,請導航到 Google Cloud 控制台中的 Cloud Run,看看 Firebase 香腸是如何製作的!)

然而,在 GenLatte 的 Node.js 時代,我依賴了超過 15 個獨立的 Cloud Run 服務,我知道我想要一個更精簡的設定,原因有很多。首先,部署會顯著加快,但更重要的是,它會大幅降低 GenLatte 的伺服器費用。

「但是 Craig!」您說,「Cloud Run 在不使用時會縮小到零,所以這實際上很重要嗎?」

好問題。我很高興您有注意。

是的,這非常重要!當 GenLatte 正在使用時,各種資料寫入和非同步任務會啟動所有 15 個服務,雖然每個服務在閒置時都會關閉,但這仍然對我們的伺服器費用產生可預測的影響。但是,更糟糕的是,我們將每個服務的最小節點計數設定為 1,以避免冷啟動,這當然會取消這個縮小到零的功能。最終結果是 GenLatte 的啟動成本出奇地高。

如何將所有內容塞入一個 Firebase 服務

為了省錢,我決定借鑑 Serverpod 的靈感,引入一個 BackendMessage DTO 來告訴我的單一 Firebase 函式我想要它實際呼叫的內部函式。在 Gemini 幫助我編寫的一些巧妙的 pkg:freezed 技巧下,我甚至能夠獲得類型化的回應。

最終的類別設定有點複雜,但如果您喜歡類型安全和省錢,那麼值得理解。

BackendMessage

這是我在實際函式簽章中使用的父 DTO 類別。

1
2
3
4
abstract interface class BackendMessage<R extends MessageResponse> {
/// Json serializer.
Json toJson();
}

MessageParameters

這實作了 BackendMessage 並使用 pkg:freezed 將個別訊息類型與預期的回應類別綁定。

@Implements.fromString 技巧產生滿足父類別要求以綁定 MessageResponse 類型的類別。雖然源類別使用原始字串感覺類型不安全,但任何拼寫錯誤都會導致產生類別中的錯誤,這意味著它在功能上是類型安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@freezed
sealed class MessageParameters with _$MessageParameters {
/// Creates a new latte order.
@Implements.fromString('BackendMessage<SaveOrderResponse>')
const factory MessageParameters.saveOrder({
@LatteOrderConverter() required LatteOrder order,
}) = SaveOrderParameters;

/// Marks a latte order as completed.
@Implements.fromString('BackendMessage<EmptyResponse>')
const factory MessageParameters.completeOrder({
required String orderId,
required String baristaId,
}) = CompleteOrderParameters;

// Many more message types...
}

MessageResponse

這關閉了迴圈,宣告了每個方法呼叫預期的回應類別。它為需要即時回饋的方法提供了單獨的回應類型,並提供了空回應以表示 202 Accepted 或 204 No Content HTTP 回應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@freezed
sealed class MessageResponse with _$MessageResponse {
/// Return data for [SaveOrderParameters].
const factory MessageResponse.saveOrder({required LatteOrder? latteOrder}) =
SaveOrderResponse;

// Many more response types...

/// Placeholder for method calls which require no return value.
///
/// This is typically because the client will pick up any state changes via
/// watched Firestore collections.
const factory MessageResponse.empty() = EmptyResponse;
}

我的 DTO 都準備好後,我需要一個單一的伺服器端函式來接收並相應地路由每個傳入的 BackendMessage。

1
2
3
4
5
6
7
8
9
10
11
firebase.https.onCallWithData<MessageParameters, Object>(
(request) {
final MessageResponse response = switch (request.data) {
SaveOrderParameters msg => saveOrder(msg),
CompleteOrderParameters msg => completeOrder(msg),
...
};
return response.toJson();
},
...
);

最後,我需要滿足其契約的 saveOrder 和 completeOrder 方法。

1
2
3
4
5
// Other Dart files

Future<SaveOrderResponse> saveOrder(SaveOrderParameters message) {}
Future<EmptyResponse> completeOrder(CompleteOrderParameters message) {}
// Many more functions...

有了這些系統,我就可以將任意多的伺服器端操作塞進一個 Dart 函式中。太省錢了!

重構所有寫入

上述 BackendMessage 系統為立即實現我的另外三個子目標奠定了基礎:

  1. 我透過在 firestore.rules 檔案中阻擋客戶端寫入來移除所有客戶端寫入。我也重構了我的資料管理層,以呼叫單一後端函式,而不是直接呼叫 docRef.set() 等 Firestore 函式。
  2. 我同樣移除了所有 Firestore 觸發器,但將遺失的功能重新實例化為我可以從客戶端明確呼叫的函式。
  3. 我透過在 Dart 程式碼中引入 ACL 檢查來維持 GenLatte 嚴格的權限模型,作為一個可測試的系統,這讓我晚上睡得安穩。

加入端對端測試

在我的最後一招中,我能夠命令 Gemini 撰寫伺服器端測試,並且非常令人興奮地,在客戶端測試中模擬後端行為,這些測試突然變成了端對端測試。鑒於我的類型安全回傳值,這帶來了我滿意的穩定性和應用程式可靠性!

全端語言的樂趣

無論您喜歡關聯式資料,因此使用 Serverpod 之類的工具,還是像 Cloud Firestore 這樣的非關聯式解決方案,在整個堆疊中以相同的語言撰寫程式碼都是一種夢想。如果該語言是 Dart,並且您發現自己沉浸在完全健全的 null-safe 類型安全的榮光中,那麼勝利將不斷降臨。它確實創造了電腦實際上是您朋友的錯覺!

我很享受將 GenLatte 轉換為處處使用 Dart 的過程,透過這樣做,我將其伺服器費用削減到 Node.js 時代的一小部分,並提高了可靠性和效能。我用於執行變更的 Gemini 代幣費用在短短幾分鐘的操作後就輕鬆收回了。現在,GenLatte 可以長期為活動參與者提供個人化咖啡,或者直到我們厭倦經營它為止。

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://dart.dev/blog/google-summer-of-code-2026-results

Google Summer of Code 2026 成果

今天,我們將重點介紹由 Dart 和 Flutter 在 Google Summer of Code 2026 中指導的貢獻者所完成的專案和成果。

Dash wearing a sun hat lounges on a pool float with a laptop, under the Google Summer of Code sun logo.

Google Summer of Code (GSoC) 計畫旨在吸引全球新開發者參與開源軟體開發。Google 贊助貢獻者在夏季與一個指導開源組織合作進行為期 12 週的程式設計專案。在過去的 20 年中,超過 20,000 名貢獻者參與了 Google Summer of Code。

今年,Dart 和 Flutter 連續第七年作為指導組織參與 Google Summer of Code。我們收到了來自世界各地近 100 份潛在參與者的專案提案。今天,我們將重點介紹今年夏天我們指導的一些專案。貢獻者在這篇文章中描述了他們的專案。

將 WebSocket 和 gRPC 支援加入 Flutter DevTools 網路面板

作者:Yash Hosalli

在開發即時系統時,我注意到開發者體驗中存在一個空白。Flutter DevTools 對 HTTP 請求提供了很好的可見性,但是一旦 WebSocket 連接建立起來,就很難看到實際發生了什麼。框架流、連接生命週期和長時間的活動可能會很快變成一個黑盒子,使即時調試變得令人沮喪。

許多現代應用程式都依賴 WebSocket,但幾乎沒有調試可見性。這個專案將這種缺失的可見性帶入現有的 Dart 和 Flutter 工具棧。我擴展了 dart:io、dart:developer 和 VM Service,以捕獲輕量級連接級別和框架級別的 WebSocket 資料,遵循為 HTTP 分析已建立的架構。目標是讓 WebSocket 調試感覺熟悉,而不是引入全新的分析工作流程。

收集到的資料隨後直接顯示在 DevTools 網路面板中,開發者可以在其中檢查連接、大小、時間戳和生命週期事件。這將 WebSocket 通信轉變為開發者可以實際觀察和理解的內容。

若要了解更多資訊,請查看我的 GSoC 進度追蹤頁面。

Flutter DevTools Network panel showing WebSocket connection overview details such as status, bytes, and frames sent and received.
Flutter DevTools Network panel showing a table of WebSocket frames with timestamps, directions, events, and sizes.
Flutter DevTools Network panel listing HTTP and WebSocket network requests.

FFIgen 的 C++/C 相容性工具

作者:Hassnaa Mohamed Ahmed

本專案的主要目標是擴展 package:ffigen 以自動生成 C++ API 的 Dart FFI 綁定。Dart 可以透過 FFI 套件與 C 和 C 相容的 API 通信,而 FFIgen 可以自動生成這些 API 的 Dart 綁定。然而,C++ 引入了 Dart FFI 無法直接表示的概念,例如類別、建構子和解構子、繼承和原生物件所有權。因此,從 Dart 使用 C++ 函式庫需要額外的膠水程式碼並仔細處理 Dart 物件及其底層 C++ 物件之間的關係。

FFIgen 中的實驗性 C++ 支援透過為 C++ 類別生成 Dart 綁定以及所需的 C++ 膠水程式碼來解決這些挑戰。生成的綁定支援 C++ 類別指標和原生物件生命週期管理,允許 Dart 物件與其底層 C++ 物件互動,同時保留原生所有權語義。這使得從 Dart 使用 C++ 類別成為可能,而無需使用者手動編寫綁定和記憶體管理層。

範例 C++ 標頭:

1
2
3
4
5
6
7
8
9
class Animal {
public:
int age;
Animal(int age);
~Animal();
void speak();
int getAge() const;
static int getCount();
};

生成的 C++ 膠水程式碼:

1
2
3
4
5
6
7
8
9
10
11
FFIGEN_EXPORT Animal* Animal_new(int age) {
return new Animal(age);
}
​
FFIGEN_EXPORT int Animal_getAge(const Animal* self) {
return self->getAge();
}
​
FFIGEN_EXPORT void Animal_delete(Animal* self) {
delete self;
}

生成的 Dart 綁定用法:

1
2
3
4
5
6
7
8
9
10
11
// Instantiate the C++ class through the generated constructor wrapper.
final animal = Animal(10);
expect(animal.getAge(), 10);
​
animal.speak();
​
// Invoke a static C++ method.
expect(Animal.getCount(), 42);
​
// Deterministically release the native object.
animal.dispose();

這種方法在 C++ 物件導向 API 和 Dart 的 FFI 模型之間提供了橋樑,同時使生成的綁定盡可能接近原始 C++ API 的語義。

有關更多詳細資訊,請查看 GitHub 上的 GSoC 2026 專案報告。

將 IntelliJ Plugin 從 Weberknecht WebSocket 函式庫遷移出來

作者:Javad Asadi

IntelliJ 的 Dart 和 Flutter Plugin 使用 Weberknecht 函式庫與 Dart Tooling Daemon 和 VM 服務進行通信。Weberknecht 是一個老舊、未維護的函式庫,繼續依賴它不再是一個可行的選擇。

在 GSoC 2026 中,我們決定用 JDK 內建的 WebSocket 客戶端替換這個函式庫。它從 Java 11 開始提供,使我們能夠避免另一個外部依賴。

遷移一次一個服務地進行,首先在 Dart Plugin 中,然後在 Flutter Plugin 中。在每個階段,我嘗試編寫針對 WebSocket 的測試,包括正常路徑和邊緣案例。目標是讓測試在遷移之前和之後都通過。

遷移成功完成。Dart 和 Flutter Plugin 現在都使用 JDK 內建的 WebSocket 客戶端,同時保留了所有工作流程中的現有行為。在最終遷移階段之後,所有對 Weberknecht 的引用——包括其依賴項和捆綁的 JAR 檔案——都從兩個儲存庫中刪除。因此,這些 Plugin 不再依賴於過時的外部 WebSocket 函式庫。

Architecture diagram showing the IntelliJ Dart and Flutter plugins connecting to the Dart SDK VM Service and Dart Tooling Daemon via WebSockets.

有關此專案的更多資訊可以在我的 專案摘要 gist 中找到。

package:webcrypto 的 Android JCA 後端

作者:M. Fazri Nizar

package:webcrypto 目前隨 Android 應用程式一起提供額外的 BoringSSL 原生函式庫。在 GSoC 2026 期間,我探索了透過 Java Cryptography Architecture (JCA) 和 package:jni 使用 Android 自己的密碼學 API。由於 Android 已經提供了內建的密碼學,利用它可以最終減少應用程式隨附的額外原生程式碼。第一個里程碑是讓 SHA-256 原語正常工作。

在第一個原始功能合併後,我加入了 HMAC 和 AES,接著是 RSA、橢圓曲線操作、HKDF 和安全亂數生成。在整個開發過程中運行套件現有的測試,使我能夠將 JCA 實作與 BoringSSL 和瀏覽器後端進行比較。這些測試還發現了不同提供者之間的差異,特別是在較舊的 Android 版本上關於 AES-GCM 參數和 RSA 私鑰的差異。PBKDF2 仍未解決,因為 Web Crypto 接受原始密碼位元組,而 JCA 的標準 API 接受字元。

JCA 後端實作了現有的介面,這意味著公共 API 保持完全不變。因此,開發人員將能夠受益於減少的應用程式大小而無需更改任何程式碼。

若要了解有關實作和持續進行的工作的更多資訊,請查看我在 GitHub 上的 GSoC 2026 專案報告。

在 Dart DevTools 中檢查原生記憶體

作者:Nourhan H.

在調試涉及 Pointer<X> 的 Dart 程式碼時,調試器通常會顯示無用且模糊的資訊。這迫使開發人員依賴印出語句而不是直接檢查變數。

例如,給定以下程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'dart:ffi';
import 'package:ffi/ffi.dart';
​
final class MyStruct extends Struct {
@Int32()
external int a;
​
@Double()
external double b;
}
​
void main() {
final pInt16 = malloc<Int16>()..value = -500;
final ptr = malloc<MyStruct>();
​
ptr.ref.a = 42;
ptr.ref.b = 3.14;
​
malloc.free(ptr);
malloc.free(pInt16);
}

調試器以前顯示:

Dart DevTools debugger variables pane before the fix, displaying unhelpful Pointer<Never> type information for pointer variables.

此外,嘗試檢查指向無效記憶體的指標以前會導致 segmentation fault,使得記憶體檢查不安全。這個問題影響了各種 Pointer<X> 類型,包括基本類型、結構體、聯合體等。

我們解決了這個問題,使記憶體檢查既安全又方便開發人員。

例如,考慮檢查一個包含多個指標類型的容器:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import 'dart:developer';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
​
class MultiPointerContainer {
final Pointer pInt16;
final Pointer pInt32;
final Pointer pDouble;
final Pointer pNull;
final Pointer pGarbage;
final Pointer pBool;
​
MultiPointerContainer({
required this.pInt16,
required this.pInt32,
required this.pDouble,
required this.pNull,
required this.pGarbage,
required this.pBool,
});
}
​
void main() {
final pInt16 = calloc()..value = -500;
final pInt32 = calloc()..value = -100;
final pDouble = calloc()..value = 1.5;
final pGarbage = Pointer.fromAddress(0xDEADBEEF);
final pBool = calloc()..value = true;
​
final container = MultiPointerContainer(
pInt16: pInt16,
pInt32: pInt32,
pDouble: pDouble,
pNull: nullptr,
pGarbage: pGarbage,
pBool: pBool,
);
​
debugger();
​
calloc.free(pInt16);
calloc.free(pInt32);
calloc.free(pDouble);
calloc.free(pGarbage);
calloc.free(pBool);
}

調試器現在顯示:

Dart DevTools debugger variables pane after the fix, displaying structured pointer information including raw bytes, addresses, and error handling for invalid memory.

無效記憶體讀取和 null 指標現在已完全安全地處理。我們整合了低階核心 API,以防止在五個主要作業系統中進行任意記憶體讀取時發生分割錯誤:

  • Linux 和 Android: pread64(/proc/self/mem)
  • Windows: ReadProcessMemory
  • macOS 和 iOS: mach_vm_read_overwrite

這個修正顯著增強了任何編寫 Dart 程式碼並需要清晰、可靠地了解指標內部發生情況的開發人員的體驗。

有關此專案的更多資訊,請查看 GitHub 上的 GSoC 2026 專案儲存庫。

更多來自 Dart

Dart in Google Summer of Code 2026

Dart in Google Summer of Code 2026

我們很高興地宣布,Dart 專案將在 Google Summer of Code 2026 中第七次指導貢獻者!

Jonas Finnemann Jensen Jonas Finnemann Jensen
2026 年 3 月 5 日 · 2 分鐘閱讀
Google Summer of Code 2025 Results

Google Summer of Code 2025 成果

Google Summer of Code (GSoC) 計畫旨在吸引全球新開發者參與開源軟體開發。Google...

Daco Harkes Daco Harkes
2025 年 10 月 20 日 · 9 分鐘閱讀

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/decoupling-material-cupertino

Material 和 Cupertino 的解耦終於實現了

長久以來,Flutter 都是一個「內建電池」的框架,擁有出色的工具、設計系統、動畫等。但電池並非永恆,因此在需要時能夠更換它們非常重要。回溯到 2017 年,當 Flutter 接近可行性時,提升 Material 和 Cupertino 的地位被普遍認為是正確的決定。快轉到 2020 年代,這種觀點發生了轉變,Material 和 Cupertino 這兩塊電池開始感覺更像是焊死的,而不是簡單地內建的。多年來,改進這個系統一直是 Flutter 開發人員最主要的需求。現在,到了 2026 年,一個漫長的過程終於讓這個開發人員的願望成真。

我們正在做什麼

從幾個月前開始,並於 2026 年 8 月完成,這項工作凍結了對 Flutter 框架內 Material 和 Cupertino 函式庫的貢獻,並在 pub.dev 上以 1.0.0 版本發布了完全相同的 pkg:material_ui 和 pkg:cupertino_ui 複本。 (這些外部函式庫並未凍結,並已準備好接受貢獻!) 然後,在未來某個日期,這項工作將完成,Material 和 Cupertino 函式庫將從 Flutter 框架本身中棄用並最終移除。

暫停其他工作以完成此舉的原因有很多,其中一些如下所述。

減少更新 Flutter 時的摩擦

多年來,升級 Flutter 以獲取安全補丁和其他修復,必然會包含任何 Material 或 Cupertino UI 更新。毋庸置疑,這是開發人員摩擦的主要來源,導致許多團隊停留在較舊的 Flutter 版本。這也意味著當團隊最終啟動升級時,這項任務可能會耗費開發人員數週的時間,並在團隊解決 UI 變更時瓶頸整個儲存庫。

現在,Material 和 Cupertino 函式庫已從核心框架中遷移出來,我們預計更新 Flutter 將恢復到它本來應該是一個簡單的任務——僅當底層平台變更需要 Flutter 開發人員注意時才會變得複雜。

加速 Material 和 Cupertino 的進程

Flutter 每 3 個月發布一個新的穩定版本,對於其範圍的專案來說,這個速度相當快。然而,儘管速度很快,它仍然意味著最壞的情況(在穩定版本截止後一分鐘提交程式碼)可能會導致 16 週的延遲,直到該變更在穩定版本中發布。這…不太好。

現在,pkg:material_ui 和 pkg:cupertino_ui 分別獨立成套件,可以按照自己的每週排程發布。這簡化了 CI 工作流程,並且更容易貢獻,特別是對於那些不熟悉 Flutter 內部結構的人來說。

基於這些原因,我們預計 Material 和 Cupertino 將加速開發進程,並更好地與 Google 和 Apple 的設計語言更新保持同步。例如,Liquid Glass 和 Material 3 Expressive 的官方實作已經在進行中。我們正在與我們的套件作者社群合作,以確保 Flutter 對這些函式庫的實作具有最佳的品質和相容性。

強化基礎 Widget

在 Flutter 框架中提升 Material 和 Cupertino 函式庫的地位所造成的意想不到的後果之一是,在這些設計函式庫中構建基礎功能太具誘惑力了。兩者在技術上都是在普通 widgets 函式庫之上構建的,但 widgets 函式庫本身充滿了基本 UI 概念中的漏洞和空白。

為了解決這個問題,解耦工作的一部分將填補 widgets 函式庫中的這些空白,以提供一個更堅實的基礎,開發人員可以在其之上構建其他設計語言。Flutter 生態系統已經支援許多設計語言,包括 macos_ui、fluent_ui、yaru、arna、chicago、VSP、shadcn 等。隨著時間的推移,我們希望透過用更多有用的構建塊來擴充 Flutter 的核心 Widget,從而簡化這項任務。

我們沒有做什麼

pub.dev 上的 material_ui 和 cupertino_ui 套件將繼續受到 Flutter 團隊本身的相同關注,並且自 2026 年 8 月 13 日起,開放給所有 Flutter 開發人員貢獻。雖然 Flutter 團隊的資源有限,我們偶爾必須做出艱難的選擇並降低某些計畫的優先順序;但這不是那回事。Flutter 團隊仍然致力於為 Material 和 Cupertino 提供出色的產品,讓您的 Flutter 應用程式在您所針對的所有平台上看起來現代且符合慣例。

展望未來

所有能夠存活到第十二年的專案都會有技術債。目標不是完全避免這些債務,而是識別它們並撥出時間和精力來償還它們。對於 Flutter 團隊來說,解決上述問題一直是我們願望清單上的項目,我們很高興最終能為所有開發人員提供更流暢的升級、更快的創新和更強大的核心 Widget 函式庫。

若要開始使用,如果您的應用程式使用 flutter/material.dart 或 flutter/cupertino.dart,請使用 dart fix 將新的獨立函式庫以 1.0.0 版本新增到您的 pubspec.yaml 檔案中,並替換您的導入以使用新的套件。我們的預期是這將是一個直接的變更,但如果您遇到任何問題,請 提交議題。

如果您是具有 Material 或 Cupertino 專業知識的 UI 開發人員,請考慮為獨立函式庫做出貢獻!一如既往,我們建議您要嘛尋找一個您感興趣的議題,要嘛為您想完成的工作提交一個功能請求。

下次再見,祝您 Fluttering 愉快!

  1. 為了簡化您的遷移,我們特意選擇了版本 1.0.0 來標記獨立版本,該版本完美匹配每個函式庫在框架中的狀態。未來,這些函式庫將使用語義版本控制進行演進。 ↩

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://dart.dev/blog/skills-cli-1-0-bundle-and-distribute-ai-agent-skills-for-your-packages

Skills CLI 1.0:為您的套件捆綁和分發 AI 代理技能

直接與您的套件一起發布官方 AI 代理技能,以提高可發現性。

作者:Jake Macdonald
2026 年 9 月 8 日 · 閱讀約 4 分鐘

Illustration representing package skills in Dart.

自 2025 年末以來,Agent Skills 一直在改變使用者為其 AI 代理提供更好背景資訊的方式。它們易於建立,並透過漸進式披露有效利用 token。這些優點共同彌補了知識截止期的差距,使代理能夠更有效地處理不熟悉或更新的程式碼庫、依賴項和工具。

今天,技能通常透過 Git 儲存庫共享,要求開發人員手動複製和拷貝檔案,或依賴 npx skills 等工具。雖然 npx skills 運作良好,但對於像我這樣熱衷於 Dart 的人來說,僅僅為了運行一個命令列工具就需要 Node.js 感覺格格不入。Dart 始終採用「開箱即用」的方法,您所需的一切都內建於 SDK 中。管理 AI 技能也應該感覺同樣原生。

即使對於已經安裝 Node.js 和 npx 的開發人員來說,仍然缺乏可發現性和套件版本支援。您如何確保您使用的技能與專案中套件的確切版本匹配?

導入 skills CLI

為了解決這兩個問題,Serverpod 團隊建立了 skills CLI。我們很高興地宣布 1.0 版本,現在由 Dart 團隊直接維護和發布。

這個工具不僅僅是 npx skills 的替代品。真正的魔力在於它使套件作者能夠輕鬆地直接與其套件一起發布技能。他們所需要做的就是將它們放在頂層的 skills/ 目錄下:

1
2
3
4
5
my-project/
├── src/
├── skills/ <--- skills
├── pubspec.yaml
└── README.md

我們建議使用新的 dart run skills@ 語法運行此工具,它始終將 CLI 的最新版本作為全域工具運行。或者,您可以使用 dart install 安裝該工具(只需記住定期更新它!)。

對於套件消費者:安裝技能

若要直接從 pubspec.yaml 中的依賴項獲取技能,請運行 get 命令:

1
2
# Scans immediate dependencies and discovers available skills.
dart run skills@ get

skills CLI 掃描您的專案依賴項中的捆綁 skills/ 目錄。然後它會列出可用的技能,讓您選擇要安裝的技能。

隨後的運行是增量的——只顯示新的、更新的、已移除的或先前跳過的技能。您也可以傳遞 --all 標誌以一次安裝所有技能依賴項。

對於套件作者:發布技能

套件作者今天就可以開始與其程式碼一起發布技能。若要建立技能,請在 skills/ 中加入一個目錄,並以您的套件名稱作為前綴(以防止衝突),並包含一個 SKILL.md 指令檔。這遵循標準的 Agent Skills 佈局。

例如,一個名為 networking 的套件可以使用以下結構強制執行正確的 API 使用:

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
---
name: networking-error-handling
description: >-
Use when the user is making network requests using the networking APIs
to ensure safe patterns.
---

# Networking Error Handling

## Guidelines

- Operations must always be wrapped in a try/catch block.
- You must catch `NetworkException` specifically
to handle retries and log diagnostics.
- Provide fallback UI state in the event of a timeout.

## Examples

```dart
try {
final response = await NetworkingClient.fetchData();
} on NetworkException catch (e) {
Logger.log(e.context);
return FallbackData();
}
1
2
3
4
5
6
7
8

## 使用 `add` 命令從 Git 安裝

雖然直接從依賴項中提取技能很方便,但整個生態系統已經圍繞透過 GitHub 儲存庫共享技能而存在。這些甚至可能不直接與 Dart 或 Flutter 套件相關聯。`skills` CLI 還支援使用 `add` 命令從任何 Git 儲存庫明確安裝技能:

```bash
# Install universally applicable skills from a Git repository
dart run skills@ add https://github.com/my-org/custom-ai-skills.git

您可以安裝在 skills.sh 上找到的任何技能,這是一個流行的技能聚合網站,只需將 npx skills 命令替換為 dart run skills@。

這使團隊能夠從多個來源(包括 Dart 依賴項和外部儲存庫)組合他們的指令,所有這些都由單一工具管理。

今天就試試 skills

我們很高興看到 skills CLI 如何簡化 AI 驅動的 Dart 和 Flutter 開發!

以下是一個快速回顧:

  • 套件作者: 在您的套件儲存庫中加入 skills/<package_name>-<skill-name>/SKILL.md 檔案。
  • 套件消費者: 在您的專案根目錄中運行 dart run skills@ get 以發現並安裝來自您的依賴項的可用技能。

非常感謝 Serverpod 的初步實作,以及所有與我們一起塑造這些代理工作流程的早期採用者!如果您有任何回饋或遇到任何問題,請在 dart-lang/ai 儲存庫中提交問題告訴我們。

若要了解更多並探索實際操作中的技能:

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/a2ui-client-side-functions

使用 A2UI 的客戶端函數實現快速、可靠的計算

了解客戶端函數如何讓代理直接將本地操作委託給在使用者設備上執行的 Dart 程式碼。

學習如何使用客戶端函數
學習如何使用客戶端函數

生成式使用者介面 (GenUI) 正在改變我處理 Flutter UI 開發的方式。GenUI 不再為每個場景硬編碼固定畫面,而是讓 AI 代理在執行時動態生成和調整使用者介面元件。使用 Agent-to-User Interface (A2UI) JSON 訊息和 genui 套件,Flutter 應用程式可以即時渲染動態的 AI 驅動卡片和介面。

然而,當我開始建構真實世界的代理應用程式時,我發現自己正在尋找減少延遲並減少模型出錯機會的方法。當使用大型語言模型 (LLM) 進行建構時,很容易依賴模型來處理所有事情。但要求 LLM 承擔它不一定設計用於執行的任務(例如算術)會引入延遲並增加出錯的機會。

這就是 A2UI 的 **客戶端** 函數的用武之地。

在這篇文章中,我將介紹客戶端函數如何讓代理直接將本地操作委託給在使用者設備上執行的 Dart 程式碼,從而減少往返次數並在您的 Flutter GenUI 應用程式中提供更可預測的結果。


客戶端函數解決了什麼問題

為了純粹的數學運算,來回向 LLM 發送原始提示效率低下,增加了往返延遲並消耗了額外的令牌。相反,客戶端函數讓代理在設備上本地計算諸如成分成本、稅費總計或單位轉換之類的值。

客戶端函數透過創建清晰的分工來解決這個問題:

  1. **Flutter 客戶端** 在 GenUI 目錄中宣告可用的客戶端函數,告知代理可以呼叫哪些本地操作、它們期望哪些參數以及它們返回什麼格式。
  2. **LLM 代理** 決定何時以及何處顯示組件,並發出一個 A2UI 表達式,呼叫客戶端函數並提供必要的參數(例如項目 ID 和數量)。
  3. **Flutter 客戶端** 在本地評估表達式並在 Dart 程式碼中同步執行數學運算,立即在螢幕上渲染清晰、格式化的結果。

透過將計算卸載到客戶端設備,您的應用程式避免了不必要的網路開銷,實現了一致的格式化,並降低了總體令牌使用量。

dart
sequenceDiagram 
 participant LLM as Gemini Agent 
 participant Client as Flutter App (genui) 
 participant Function as CalculateCost (Dart) 
 LLM->>Client: A2UI Payload with calculateCost(black_beans, 3) 
 Client->>Function: executeSync(args) 
 Function->>Client: "$2.97" 
 Client->>Client: Render Text widget ($2.97) 

讓我們看看我如何在名為 **Commis** 的範例應用程式中實作這種模式,Commis 是一個為商業廚房和餐飲團隊建置的智慧助理。


CalculateCostFunction 類別的解剖

在準備即將到來的餐飲活動時,廚師可能會詢問菜單中使用的食材。為了可靠地計算和顯示食材成本,而無需等待伺服器往返,我創建了一個名為 CalculateCostFunction 的客戶端函數。

在 `genui` 套件中,同步客戶端函數擴展了 `SynchronousClientFunction`。這是 `client_functions.dart` 的完整實作:

dart
/// 一個客戶端函數,使用本地 Dart 邏輯直接在設備上計算食材成本。 
class CalculateCostFunction extends SynchronousClientFunction { 
 const CalculateCostFunction(); 
​ 
 // 1. LLM 在 A2UI 負載中引用的識別碼。 
 @override 
 String get name => 'calculateCost'; 
​ 
 // 2. 提供給 LLM 的清晰描述,以便它知道何時以及為何使用該函數。 
 @override 
 String get description => 
 'Calculates the cost for a certain quantity of an ingredient. ' 
 'Returns a formatted dollar string (for example, \$4.50).'; 
​ 
 // 3. 綁定的預期回傳類型。 
 @override 
 ClientFunctionReturnType get returnType => ClientFunctionReturnType.string; 
​ 
 // 4. 定義所需輸入參數的 JSON Schema。 
 @override 
 Schema get argumentSchema => S.object( 
 properties: { 
 'ingredient_id': S.string(description: 'The ID of the ingredient.'), 
 'quantity': S.number(description: 'The quantity of the ingredient.'), 
 }, 
 required: ['ingredient_id', 'quantity'], 
 ); 
​ 
 // 5. 客戶端上的同步 Dart 執行邏輯 
 @override 
 Object? executeSync(JsonMap args, ExecutionContext context) { 
 final ingredientId = args['ingredient_id'].toString(); 
 final quantity = num.tryParse(args['quantity'].toString())?.toDouble(); 
​ 
 if (quantity == null || quantity < 1) { 
 return '\$0.00'; 
 } 
​ 
 // 呼叫本地成本服務來獲取價格並格式化為貨幣 
 final cost = CostService().fetchPrice(ingredientId, quantity); 
 return '\$${cost.toStringAsFixed(2)}'; 
 } 
} 

讓我們分解這個類別的關鍵部分:

  1. `name`:AI 代理在 A2UI 負載中生成函數呼叫時使用的唯一識別碼(`calculateCost`)。
  2. `description`:發送給 LLM 的簡潔說明,讓它知道何時以及為何呼叫此函數,以及預期什麼輸出格式。
  3. `returnType`:指定函數返回的資料類型(在此範例中為字串)。
  4. `argumentSchema`:使用 `json_schema_builder` 建立,此 Schema 會明確告知 LLM 需要哪些參數(`ingredient_id` 和 `quantity`)。
  5. `executeSync`:當 UI 渲染時,在使用者設備上執行的核心 Dart 方法。它會解析傳入的參數,呼叫我的本地 `CostService`,並返回格式化的美元字串。

如果您剛剛注意到這種模式與用於 UI 元件的目錄條目的模式非常相似,那麼您是正確的!兩者都為代理提供了推理時使用的元數據,並搭配了執行有用操作的 Dart 邏輯:要嘛建立 Widget,要嘛在此範例中計算一個值。


目錄註冊和系統提示整合

為了讓代理了解 `calculateCost`,我在應用程式的 GenUI `Catalog` 中註冊了它。

在實例化 `Catalog` 時,我將 `CalculateCostFunction()` 傳遞到 `functions` 列表以及我的 UI 元件中:

dart
// lib/ui/catalog/catalog.dart 
​ 
final commisCatalog = Catalog( 
 [ 
 cateringJobItem, 
 recipeLineCatalogItem, 
 ingredientLineCatalogItem, 
 navigationCardCatalogItem, 
 simpleCardCatalogItem, 
 ], 
 functions: [ 
 CalculateCostFunction(), // 在這裡! 
 ], 
 catalogId: 'commis_catalog', 
); 

當初始化對話會話時,`genui` 的 `PromptBuilder` 會檢查目錄並自動提取所有客戶端函數宣告,將它們的名稱、描述和 Schema 整合到提供給 Gemini 的系統提示中。

有了這些,當廚師詢問食譜價格時,Gemini 不會嘗試猜測或計算美元總額。相反,它會發出一個 A2UI 訊息,其中包含對 `calculateCost` 的呼叫(在此範例中,用於三罐豆子的價格):

json
{ 
 "id": "cost_val", 
 "component": "Text", 
 "text": { 
 "call": "calculateCost", 
 "args": { 
 "ingredient_id": "black_beans", 
 "quantity": 3 
 }, 
 "returnType": "string" 
 }, 
 "variant": "h2" 
} 

請注意,A2UI 訊息中 `call` 屬性的值與目錄中註冊的函數名稱 (`calculateCost`) 相符。當 `SurfaceController` 收到此訊息時,它會在設備上使用 `executeSync` 本地評估 `calculateCost`,並立即在螢幕上渲染準確的美元字串(例如 `$2.97`)。


使用 Flutter 熱重載實現快速開發人員迭代

由於客戶端函數不會被後端微服務或雲端函數部署鎖定,因此使用它們仍然感覺像普通的 Dart。

如果我想更新貨幣格式(例如,添加批量折扣邏輯或從 `$4.50` 切換到 USD `4.50`),我只需在 Dart 中編輯 `executeSync`,保存檔案,然後觀察熱重載用新結果更新我的應用程式。


摘要與後續步驟

準備好在您的應用程式中嘗試 GenUI 和客戶端函數了嗎?

祝您建構愉快!

更多 Flutter 內容

0%