本文发布于公众号:stringwu的工程笔记 Flutter Generative UI 深度架构剖析与官方 SDK 实战
在生成式 AI(Generative AI)大模型能力爆发的当下,人机交互(HCI)正经历自 GUI 普及以来最深刻的一次范式转移。传统的"预设界面 + 固化交互"正在向 "意图驱动 + 即时生成"的 Generative UI(生成式 UI,简称 Gen UI) 演进。
你有没有想过,以后跟 App 说话,它回给你的不再是一长串文字,而是一个可以直接点的按钮、一张卡片、甚至一个完整的表单?
这就是 Generative UI(生成式 UI)想做的事。简单说,就是让 AI 根据你说的话,实时生成界面,而不是只给你一段文字回复。
2025 年底,Flutter 官方团队正式推出了官方标准套件 flutter_genui,彻底打通了从 LLM 意图推理到原生 UI 渲染的链路。本文将从概念演进、官方 SDK 架构设计、核心机制、代码落地及工业级痛点等维度,深度剖析 Flutter Generative UI 的前沿方案。
1 背景与定义:打破"墙状文本"的 UI 范式飞跃
1.1 从 Static UI、SDUI 到 Generative UI
UI 渲染架构的演进经历了三个关键阶段:
css
[Static UI] ───────────────> [Server-Driven UI (SDUI)] ───────────────> [Generative UI]
客户端写死布局 服务端下发预设 JSON 结构 Agent 依据意图即时调度 Widget
-
Static UI(传统静态 UI):App 长什么样,代码里一行行写清楚,改个按钮颜色都要发新版本。这叫 Static UI。
-
SDUI(服务端驱动 UI):服务器下发 JSON,客户端按模板渲染。比如电商大促时换个首页布局,不用发版,后台配一下就行。但问题是,这些模板也是人提前写好的,只是换了个地方配置而已。
-
Generative UI(生成式 UI) :用户通过自然语言或隐式行为表达意图,Agent 实时推理并决策"当前场景下最贴切的 UI 交互载体形态是什么",随之动态调度结构化数据充填到 UI 控件库(Catalog)中,在客户端实时生成自适应、个性化且即用即弃(Ephemeral)的 UI 界面。
1.1.1 三者核心能力对比
| 维度 | Static UI (静态 UI) | Server-Driven UI (SDUI) | Generative UI (生成式 UI) |
|---|---|---|---|
| 决定权归属 | 客户端开发者(编译期) | 后端工程师/运营平台(配置期) | AI Agent 结合上下文(运行时) |
| 交互载体 | 固定页面与路由 | 模板化卡片/组件树 | 以 Widget 语言替代文本(按钮、滑块、卡片组) |
| 应用架构 | 树状路由硬编码导航 | 固定的服务端配置中心 | 无感知路径(Pathless),UI 按需推送到屏幕 |
| 响应时延 | 毫秒级(本地) | 0.1s ~ 1s(依赖网络) | 1s ~ 3s(受 Token 首字与 Agent 推理制约) |
1.2 为什么 Flutter 是 Generative UI 的理想载体?
1.2.1 高度声明式与 UI 即代码(UI as Code)
Flutter 的 Widget 本质上是一个不可变(Immutable)的数据结构配置,与 LLM 擅长输出的 JSON / AST 结构存在天然的同构映射关系。
1.2.2 渲染管线一致性(Skia / Impeller)
传统原生跨端方案极度依赖 Native 原生 View 的映射,不同 OS 版本的系统组件表现差异大,容错边界复杂。而 Flutter 拥有掌控到像素级的自绘引擎,能保证 AI 生成的复杂 UI 在多端得到 100% 还原的渲染结果。
1.2.3 万物皆 Widget 的轻量级组合
Flutter 的组装粒度极细(从 Padding、SizedBox 到 Theme),这使得基于组件库构建 AI 能够理解的"UI 描述 Schema"变得非常直观。
2 官方套件 flutter_genui 架构解密
官方推出的 flutter_genui 官方套件的核心思路可以概括成一句话:AI 负责"决定用什么",Flutter 负责"把它画出来"。
2.1 核心架构与数据流
sql
+-------------------------------------------------------------------------------+
| 1. AI Agent Engine (e.g. Gemini via Firebase AI Logic) |
| - System Instruction: 定义 Agent 角色与 UI 生成指令 |
| - Catalog Tooling: Agent 识别组件 Schema 并填充数据 |
+-------------------------------------------------------------------------------+
│
▼ (Tool Call / Function Call)
+-------------------------------------------------------------------------------+
| 2. GenUI Conversation & Manager |
| - GenUIConversation: 维护整体对话流与 Agent 通信 |
| - GenUIManager: 状态同步、生命周期管理、触发 Surface 增删 |
+-------------------------------------------------------------------------------+
│
▼ (Surface ID & Data Stream)
+-------------------------------------------------------------------------------+
| 3. Flutter Client Presentation |
| - Widget Catalog: 注册原生 Widget 构建器 (Component Catalog) |
| - GenUISurface: 挂载点,将 Surface ID 渲染为高保真原生 Widget |
+-------------------------------------------------------------------------------+
-
AI Agent:你给 Agent 一个"菜单"(Catalog),告诉它:"我这儿有按钮、卡片、滑块这些组件,你根据用户需求挑合适的用。" Agent 不会直接写代码,而是像点菜一样,从菜单里选组件,并填好数据。 -
GenUIConversation:负责跟 AI 保持对话,GenUIManager管状态。当 AI 说"我要在界面上加一张卡片"时,管理器会通知 Flutter:"来活了,准备渲染。" -
Flutter Client:你提前注册好一套组件库(Widget Catalog)。当 AI 说要用WorkoutCard组件,并传入{title: "腿部训练", exercises: [...]}时,Flutter 就按这个 JSON 数据把原生 Widget 渲染出来。
整个流程像什么?像你去餐厅点菜。AI 是服务员,菜单是 Catalog,厨房是 Flutter Client。你说"我想吃点清淡的",服务员不会给你念菜谱,而是直接端上来一盘菜(界面)。
2.2 四大关键抽象概念
2.2.1 Widget Catalog(组件目录/契约库)
定义了 AI 允许使用的 Widget 列表。每个组件由名称、JSON Schema 约束(定义 AI 必须填入的数据格式)和 Widget Builder 函数组成
2.2.2 GenUIConversation(对话总控)
管理客户端与 LLM 之间的生命周期,负责把用户的 Prompt 转换为请求,并将 Agent 返回的 UI 指令分发给客户端。
2.2.3 GenUIManager(状态与生命周期管理器)
负责跨 Agent 与 UI 组件的状态同步,当 Agent 决定更新或废弃某个界面时,驱动客户端重新 rebuild。
2.2.4 GenUISurface(UI 挂载表面)
生成的动态 UI 块在 Flutter 中的视图宿主。每一个生成的卡片/控件在客户端对应一个唯一的 surfaceId。
3 实战演练:使用 flutter_genui 构建健身 Agent
下面基于 Flutter 官方推荐的标准范式,演示如何实现一个包含自定义 UI 控件的 Gen UI 应用。
3.1 依赖引入与环境配置
在 pubspec.yaml 中引入核心 SDK:
yaml
dependencies:
flutter:
sdk: flutter
flutter_genui: ^0.1.0 # 官方 Gen UI 核心套件
flutter_genui_firebase: ^0.1.0 # Gemini / Firebase 逻辑适配器,按需使用
json_schema_builder: ^0.1.0 # 强类型 JSON Schema 构建工具
3.2 定义强类型 Component Catalog Item
3.2.1 数据契约与原生 Widget 实现
dart
import 'package:flutter/material.dart';
import 'package:flutter_genui/flutter_genui.dart';
import 'package:json_schema_builder/json_schema_builder.dart';
/// 步骤1. 定义数据契约 Schema
Schema get workoutCardSchema {
return Schema.object(
properties: {
'title': Schema.string(description: '健身计划的标题,例如:Leg Day Workout'),
'exercises': Schema.array(
description: '包含的具体训练动作列表',
items: Schema.string(description: '单个动作名称'),
minLength: 3,
maxLength: 5,
),
},
required: ['title', 'exercises'],
);
}
/// 步骤2. 构建原生高保真 Widget
class WorkoutCardWidget extends StatelessWidget {
final String title;
final List<String> exercises;
const WorkoutCardWidget({
Key? key,
required this.title,
required this.exercises,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge),
const Divider(),
...exercises.map((e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
const Icon(Icons.check_circle_outline, color: Colors.green),
const SizedBox(width: 8),
Text(e, style: Theme.of(context).textTheme.bodyMedium),
],
),
)),
],
),
),
);
}
}
/// 步骤3. 打包为 CatalogItem 注册项
CatalogItem get workoutCardCatalogItem {
return CatalogItem(
name: 'WorkoutCard',
schema: workoutCardSchema,
widgetBuilder: (BuildContext context, Map<String, dynamic> data) {
// 解析 Agent 传入的结构化 JSON 数据
final title = data['title'] as String? ?? 'Custom Workout';
final rawExercises = data['exercises'] as List<dynamic>? ?? [];
final exercises = rawExercises.map((e) => e.toString()).toList();
return WorkoutCardWidget(title: title, exercises: exercises);
},
);
}
3.3 初始化对话与挂载渲染
3.3.1 初始化 GenUIConversation 并渲染 GenUISurface
在 Flutter State 中完成初始化,处理 onSurfaceAdded 与 onSurfaceDeleted 回调,并通过 ListView 挂载 GenUISurface:
dart
import 'package:flutter/material.dart';
import 'package:flutter_genui/flutter_genui.dart';
import 'package:flutter_genui_firebase/flutter_genui_firebase.dart';
class GenUIPage extends StatefulWidget {
const GenUIPage({Key? key}) : super(key: key);
@override
State<GenUIPage> createState() => _GenUIPageState();
}
class _GenUIPageState extends State<GenUIPage> {
late GenUIConversation _conversation;
final List<String> _surfaceIds = [];
final TextEditingController _inputController = TextEditingController();
@override
void initState() {
super.initState();
_initGenUI();
}
void _initGenUI() {
// 1. 获取默认 Catalog (包含基础 Text/Markdown 等) 并叠加自定义的 WorkoutCard
final catalog = Catalog.defaultCatalog().copyWith(
items: [workoutCardCatalogItem],
);
// 2. 创建 Generator 配置 Agent 系统提示词
final generator = FirebaseContentGenerator(
catalog: catalog,
systemInstruction: '''
你是一个专业的健身教练 Agent。
当用户表达锻炼需求时,不要只使用文本回复。
请优先调用 UI 工具生成 `WorkoutCard` Widget 呈现具体的训练计划。
''',
);
// 3. 初始化主控对话对象与生命周期回调
_conversation = GenUIConversation(
manager: GenUIManager(),
generator: generator,
onSurfaceAdded: (String surfaceId) {
setState(() {
_surfaceIds.add(surfaceId);
});
},
onSurfaceDeleted: (String surfaceId) {
setState(() {
_surfaceIds.remove(surfaceId);
});
},
);
}
void _sendMessage() {
final text = _inputController.text.trim();
if (text.isEmpty) return;
_inputController.clear();
// 向 Agent 发送请求,Agent 会自动决定是回传文本还是触发 CatalogItem 生成 Surface
_conversation.sendRequest(text);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Gen UI 官方方案实战')),
body: Column(
children: [
// 动态生成的 GenUI 渲染区域
Expanded(
child: ListView.builder(
itemCount: _surfaceIds.length,
itemBuilder: (context, index) {
final surfaceId = _surfaceIds[index];
// 使用 GenUISurface 进行界面挂载
return GenUISurface(
conversation: _conversation,
surfaceId: surfaceId,
);
},
),
),
// 底部输入栏
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _inputController,
decoration: const InputDecoration(
hintText: '例如:帮我安排一个腿部训练计划...',
),
),
),
IconButton(
icon: const Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
}
4 核心对比:官方 flutter_genui 方案 vs. 其它解法
在大模型生成 UI 的技术道路上,行业内有过多种尝试。通过下表可以清晰看出为什么官方选择 Tool Calling + Component Catalog 范式:
4.1 核心方案多维对比
| 维度 | 官方 flutter_genui 方案 |
动态代码解析器 (如 flutter_eval) |
自由 JSON AST 自定义解析 |
|---|---|---|---|
| 底层原理 | Tool Calling + Catalog Item 映射 | 实时编译 Dart 源码为字节码/AST | 递归解析 JSON 节点拼装原子控件 |
| 设计系统 (Design System) 兼容 | 100% 完美贴合(由 Flutter 开发者编写原生组件) | 极差(Agent 容易写出样式丑陋的代码) | 中等(需强约束属性映射) |
| 性能与热重载 | 完全原生 Widget 性能,原生支持 Flutter Hot Reload | 解释执行,开销大,易引发内存泄漏 | 递归构建开销,卡顿风险随深度增加 |
| 安全性 & 商店合规 | 绝对安全,不含任何动态代码注入,符合 Apple/Google 审核政策 | 高度危险(违反 App Store JIT/动态代码执行条款) | 安全(本质是反序列化数据) |
| 开发体验 | 支持 AI 辅助开发(如使用 Gemini CLI 实时重构组件) | 调试困难,运行时报错难以 StackTrace | 需手动维护极其繁重的 Parser 映射逻辑 |
5 总结
Generative UI 不是要替代传统的 UI 开发,而是给 AI Agent 赋予说"Widget 语言"的能力。
通过 flutter_genui 套件,Flutter 团队为全行业提供了一个标准范式:将客户端定义为严格的高保真 Widget Catalog 库,将大模型作为高度智化的 UI 调度引擎。这一架构既保留了 Flutter 原生 UI 的极致性能、完美视觉规范与安全性,又赋予了应用前所未有的动态个性化能力。
掌握这套结合 AI 工具流与 flutter_genui 的全新开发范式,将是 Flutter 开发者迈向下一代 AI-Native 应用架构的关键一步。
6 其他
官方的介绍视频
GenUI的组件的使用示例: