Skip to content

Commit 020c97f

Browse files
authored
Retire UiChangeEvent and UiEventManager (#254)
1 parent 526d82a commit 020c97f

25 files changed

+136
-402
lines changed

examples/simple_chat/lib/main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ class _ChatScreenState extends State<ChatScreen> {
5050
of the user message.
5151
''',
5252
catalog: CoreCatalogItems.asCatalog(),
53-
onSurfaceAdded: _onSurfaceAdded,
53+
onSurfaceAdded: _handleSurfaceAdded,
5454
onTextResponse: _onTextResponse,
5555
// ignore: avoid_print
5656
onWarning: (value) => print('Warning from UiAgent: $value'),
5757
);
5858
final ScrollController _scrollController = ScrollController();
5959

60-
void _onSurfaceAdded(SurfaceAdded surface) {
60+
void _handleSurfaceAdded(SurfaceAdded surface) {
6161
if (!mounted) return;
6262
setState(() {
6363
_messages.add(MessageController(surfaceId: surface.surfaceId));

examples/simple_chat/lib/message.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ class MessageView extends StatelessWidget {
2525

2626
if (surfaceId == null) return Text(controller.text ?? '');
2727

28-
return GenUiSurface(host: host, surfaceId: surfaceId, onEvent: (event) {});
28+
return GenUiSurface(host: host, surfaceId: surfaceId);
2929
}
3030
}

examples/travel_app/lib/main.dart

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
6+
57
import 'package:dart_schema_builder/dart_schema_builder.dart';
68
import 'package:firebase_app_check/firebase_app_check.dart';
79
import 'package:firebase_core/firebase_core.dart';
@@ -89,7 +91,7 @@ class TravelPlannerPage extends StatefulWidget {
8991
class _TravelPlannerPageState extends State<TravelPlannerPage> {
9092
late final GenUiManager _genUiManager;
9193
late final AiClient _aiClient;
92-
late final UiEventManager _eventManager;
94+
late final StreamSubscription<UserMessage> _userMessageSubscription;
9395
final List<ChatMessage> _conversation = [];
9496
final _textController = TextEditingController();
9597
final _scrollController = ScrollController();
@@ -108,7 +110,9 @@ class _TravelPlannerPageState extends State<TravelPlannerPage> {
108110
),
109111
),
110112
);
111-
_eventManager = UiEventManager(callback: _onUiEvents);
113+
_userMessageSubscription = _genUiManager.onSubmit.listen(
114+
_handleUserMessageFromUi,
115+
);
112116
_aiClient =
113117
widget.aiClient ??
114118
FirebaseAiClient(
@@ -146,7 +150,7 @@ class _TravelPlannerPageState extends State<TravelPlannerPage> {
146150
@override
147151
void dispose() {
148152
_genUiManager.dispose();
149-
_eventManager.dispose();
153+
_userMessageSubscription.cancel();
150154
_textController.dispose();
151155
_scrollController.dispose();
152156
super.dispose();
@@ -206,37 +210,14 @@ class _TravelPlannerPageState extends State<TravelPlannerPage> {
206210
return;
207211
}
208212

209-
void _onUiEvents(String surfaceId, List<UiEvent> events) {
210-
final actionEvent = events.firstWhere((e) => e.isAction);
211-
final message = StringBuffer(
212-
'The user triggered the "${actionEvent.eventType}" event on widget '
213-
'"${actionEvent.widgetId}"',
214-
);
215-
final value = actionEvent.value;
216-
if (value is String && value.isNotEmpty) {
217-
message.write(' with value "$value"');
218-
}
219-
message.write('.');
220-
221-
final changeEvents = events.where((e) => !e.isAction).toList();
222-
if (changeEvents.isNotEmpty) {
223-
message.writeln(' Current values of other widgets:');
224-
for (final event in changeEvents) {
225-
message.writeln('- Widget "${event.widgetId}": ${event.value}');
226-
}
227-
}
228-
213+
void _handleUserMessageFromUi(UserMessage message) {
229214
setState(() {
230215
_conversation.add(UserUiInteractionMessage.text(message.toString()));
231216
});
232217
_scrollToBottom();
233218
_triggerInference();
234219
}
235220

236-
void _handleUiEvent(UiEvent event) {
237-
_eventManager.add(event);
238-
}
239-
240221
void _sendPrompt(String text) {
241222
if (_isThinking || text.trim().isEmpty) return;
242223
setState(() {
@@ -273,7 +254,6 @@ class _TravelPlannerPageState extends State<TravelPlannerPage> {
273254
child: Conversation(
274255
messages: _conversation,
275256
manager: _genUiManager,
276-
onEvent: _handleUiEvent,
277257
scrollController: _scrollController,
278258
),
279259
),
@@ -398,7 +378,7 @@ to the user.
398378
a Column with the existing OptionsFilterChipInput, a
399379
ItineraryWithDetails containing the full itinerary, and a Trailhead
400380
containing some options of specific details to book e.g. "Book accommodation in Kyoto", "Train options from Tokyo to Osaka".
401-
381+
402382
Note that during this step, the user may change their search parameters and
403383
resubmit, in which case you should regenerate the itinerary to match their
404384
desires, updating the existing surface.
@@ -450,7 +430,7 @@ When processing a user message or event, you should add or update one surface
450430
and then call provideFinalOutput to return control to the user. Never continue
451431
to add or update surfaces until you receive another user event. If the last
452432
entry in the context is a functionResponse, just call provideFinalOutput
453-
immediately - don't try to update the UI.
433+
immediately - don't try to update the UI.
454434
455435
# UI style
456436

examples/travel_app/lib/src/catalog/options_filter_chip_input.dart

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,7 @@ extension type _OptionsFilterChipInputData.fromMap(Map<String, Object?> _json) {
106106
/// options.
107107
///
108108
/// It is typically used within a [inputGroup] to manage multiple facets of
109-
/// a user's query. When an option is selected, it dispatches a [UiChangeEvent],
110-
/// which informs the AI of the user's choice, allowing it to refine its
111-
/// subsequent responses.
109+
/// a user's query.
112110
final optionsFilterChipInput = CatalogItem(
113111
name: 'OptionsFilterChipInput',
114112
dataSchema: _schema,
@@ -142,6 +140,7 @@ final optionsFilterChipInput = CatalogItem(
142140
widgetId: id,
143141
dispatchEvent: dispatchEvent,
144142
icon: icon,
143+
values: values,
145144
);
146145
},
147146
);
@@ -152,6 +151,7 @@ class _OptionsFilterChip extends StatefulWidget {
152151
required this.options,
153152
required this.widgetId,
154153
required this.dispatchEvent,
154+
required this.values,
155155
this.icon,
156156
});
157157

@@ -160,6 +160,7 @@ class _OptionsFilterChip extends StatefulWidget {
160160
final String widgetId;
161161
final IconData? icon;
162162
final DispatchEventCallback dispatchEvent;
163+
final Map<String, Object?> values;
163164

164165
@override
165166
State<_OptionsFilterChip> createState() => _OptionsFilterChipState();
@@ -201,17 +202,11 @@ class _OptionsFilterChipState extends State<_OptionsFilterChip> {
201202
setModalState(() {
202203
tempSelectedOption = newValue;
203204
});
205+
widget.values[widget.widgetId] = newValue;
204206
if (newValue != null) {
205207
setState(() {
206208
_currentChipLabel = newValue;
207209
});
208-
widget.dispatchEvent(
209-
UiChangeEvent(
210-
widgetId: widget.widgetId,
211-
eventType: 'filterOptionSelected',
212-
value: newValue,
213-
),
214-
);
215210
Navigator.pop(context);
216211
}
217212
},

examples/travel_app/lib/src/catalog/text_input_chip.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ final textInputChip = CatalogItem(
5050
initialValue: textInputChipData.initialValue,
5151
widgetId: id,
5252
dispatchEvent: dispatchEvent,
53+
values: values,
5354
);
5455
},
5556
);
@@ -60,12 +61,14 @@ class _TextInputChip extends StatefulWidget {
6061
this.initialValue,
6162
required this.widgetId,
6263
required this.dispatchEvent,
64+
required this.values,
6365
});
6466

6567
final String label;
6668
final String? initialValue;
6769
final String widgetId;
6870
final DispatchEventCallback dispatchEvent;
71+
final Map<String, Object?> values;
6972

7073
@override
7174
State<_TextInputChip> createState() => _TextInputChipState();
@@ -106,16 +109,10 @@ class _TextInputChipState extends State<_TextInputChip> {
106109
onPressed: () {
107110
final newValue = _textController.text;
108111
if (newValue.isNotEmpty) {
112+
widget.values[widget.widgetId] = newValue;
109113
setState(() {
110114
_currentValue = newValue;
111115
});
112-
widget.dispatchEvent(
113-
UiChangeEvent(
114-
widgetId: widget.widgetId,
115-
eventType: 'textInputChanged',
116-
value: newValue,
117-
),
118-
);
119116
Navigator.pop(context);
120117
}
121118
},

examples/travel_app/lib/src/widgets/conversation.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ class Conversation extends StatelessWidget {
1414
super.key,
1515
required this.messages,
1616
required this.manager,
17-
required this.onEvent,
1817
this.userPromptBuilder,
1918
this.showInternalMessages = false,
2019
this.scrollController,
2120
});
2221

2322
final List<ChatMessage> messages;
24-
final UiEventCallback onEvent;
2523
final GenUiManager manager;
2624
final UserPromptBuilder? userPromptBuilder;
2725
final bool showInternalMessages;
@@ -74,7 +72,6 @@ class Conversation extends StatelessWidget {
7472
key: message.uiKey,
7573
host: manager,
7674
surfaceId: message.surfaceId,
77-
onEvent: onEvent,
7875
),
7976
);
8077
case InternalMessage():

examples/travel_app/test/options_filter_chip_input_test.dart

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ void main() {
2020

2121
UiEvent? dispatchedEvent;
2222

23+
final values = <String, Object?>{};
24+
2325
await tester.pumpWidget(
2426
MaterialApp(
2527
home: Scaffold(
@@ -33,7 +35,7 @@ void main() {
3335
dispatchedEvent = event;
3436
},
3537
context: context,
36-
values: {},
38+
values: values,
3739
);
3840
},
3941
),
@@ -66,12 +68,9 @@ void main() {
6668
// Check if the chip label is updated.
6769
expect(find.text('\$\$'), findsOneWidget);
6870

69-
// Check if the event was dispatched.
70-
expect(dispatchedEvent, isA<UiChangeEvent>());
71-
final changeEvent = dispatchedEvent as UiChangeEvent;
72-
expect(changeEvent.widgetId, 'testId');
73-
expect(changeEvent.eventType, 'filterOptionSelected');
74-
expect(changeEvent.value, '\$\$');
71+
expect(dispatchedEvent, null);
72+
final value = values['testId'];
73+
expect(value, '\$\$');
7574
});
7675

7776
testWidgets('renders correctly and handles selection without an icon', (
@@ -83,7 +82,7 @@ void main() {
8382
};
8483

8584
UiEvent? dispatchedEvent;
86-
85+
final values = <String, Object?>{};
8786
await tester.pumpWidget(
8887
MaterialApp(
8988
home: Scaffold(
@@ -97,7 +96,7 @@ void main() {
9796
dispatchedEvent = event;
9897
},
9998
context: context,
100-
values: {},
99+
values: values,
101100
);
102101
},
103102
),
@@ -121,12 +120,9 @@ void main() {
121120
// Check if the chip label is updated.
122121
expect(find.text('\$\$\$'), findsOneWidget);
123122

124-
// Check if the event was dispatched.
125-
expect(dispatchedEvent, isA<UiChangeEvent>());
126-
final changeEvent = dispatchedEvent as UiChangeEvent;
127-
expect(changeEvent.widgetId, 'testId');
128-
expect(changeEvent.eventType, 'filterOptionSelected');
129-
expect(changeEvent.value, '\$\$\$');
123+
expect(dispatchedEvent, null);
124+
final value = values['testId'];
125+
expect(value, '\$\$\$');
130126
});
131127
});
132128
}

examples/travel_app/test/widgets/conversation_test.dart

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ void main() {
4242
await tester.pumpWidget(
4343
MaterialApp(
4444
home: Scaffold(
45-
body: Conversation(
46-
messages: messages,
47-
manager: manager,
48-
onEvent: (_) {},
49-
),
45+
body: Conversation(messages: messages, manager: manager),
5046
),
5147
),
5248
);
@@ -56,16 +52,12 @@ void main() {
5652
});
5753
testWidgets('renders UserPrompt correctly', (WidgetTester tester) async {
5854
final messages = [
59-
const UserMessage([TextPart('Hello')]),
55+
UserMessage([const TextPart('Hello')]),
6056
];
6157
await tester.pumpWidget(
6258
MaterialApp(
6359
home: Scaffold(
64-
body: Conversation(
65-
messages: messages,
66-
manager: manager,
67-
onEvent: (_) {},
68-
),
60+
body: Conversation(messages: messages, manager: manager),
6961
),
7062
),
7163
);
@@ -95,11 +87,7 @@ void main() {
9587
await tester.pumpWidget(
9688
MaterialApp(
9789
home: Scaffold(
98-
body: Conversation(
99-
messages: messages,
100-
manager: manager,
101-
onEvent: (_) {},
102-
),
90+
body: Conversation(messages: messages, manager: manager),
10391
),
10492
),
10593
);
@@ -109,15 +97,14 @@ void main() {
10997

11098
testWidgets('uses custom userPromptBuilder', (WidgetTester tester) async {
11199
final messages = [
112-
const UserMessage([TextPart('Hello')]),
100+
UserMessage(const [TextPart('Hello')]),
113101
];
114102
await tester.pumpWidget(
115103
MaterialApp(
116104
home: Scaffold(
117105
body: Conversation(
118106
messages: messages,
119107
manager: manager,
120-
onEvent: (_) {},
121108
userPromptBuilder: (context, message) =>
122109
const Text('Custom User Prompt'),
123110
),

0 commit comments

Comments
 (0)