From 83e0f337347d9959d9271f1316d2fe082eceef24 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Wed, 30 Jul 2025 14:51:23 -0700 Subject: [PATCH 1/5] - --- doc/USAGE.md | 5 ++++- examples/travel_app/lib/main.dart | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/USAGE.md b/doc/USAGE.md index 99b09c9d7..b55d16237 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -2,4 +2,7 @@ ## Getting Started -TODO: add 3P steps +1. Follow [the steps](https://firebase.google.com/docs/flutter/setup) +to configure Firebase for your project. + +2. diff --git a/examples/travel_app/lib/main.dart b/examples/travel_app/lib/main.dart index fe98e3548..d70af839f 100644 --- a/examples/travel_app/lib/main.dart +++ b/examples/travel_app/lib/main.dart @@ -1,8 +1,11 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; -// import 'package:firebase_core/firebase_core.dart'; -// import 'firebase_options.dart'; + import 'app.dart'; +import 'firebase_options.dart'; -void main() { +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); runApp(const MyApp()); } From 853db7ab460a6a02557728134807840602729890 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Wed, 30 Jul 2025 15:00:38 -0700 Subject: [PATCH 2/5] - --- README.md | 1 + examples/travel_app/README.md | 21 ++------------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8a8b3afc2..1ecd52083 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This repository is a collection of packages and applications for the Flutter Gen ## TODO before publishing 1. Clean up Google internal information. +2. Make colors and sizes configurable in catalog. ## Packages diff --git a/examples/travel_app/README.md b/examples/travel_app/README.md index 7573da8e6..0fefc02bf 100644 --- a/examples/travel_app/README.md +++ b/examples/travel_app/README.md @@ -1,20 +1,3 @@ -# Example +# Travel App -This is prototype for agentic app. - -## Diagrams - -To regenerate diagrams: - -```shell -dart pub global activate layerlens -layerlens -``` - -## TODO - -TODO before productizing: - -1. Make colors and sizes configurable. - -## Next +TODO: describe From 0ff4c3ea09e83a601085565b628f1db039a338f7 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Wed, 30 Jul 2025 15:11:18 -0700 Subject: [PATCH 3/5] - --- doc/USAGE.md | 2 + .../src/catalog/core_widgets/radio_group.dart | 4 ++ .../lib/src/catalog/core_widgets/text.dart | 2 + .../src/catalog/core_widgets/text_field.dart | 2 + .../lib/src/core/conversation_manager.dart | 14 ++-- .../lib/src/core/conversation_widget.dart | 2 +- .../lib/src/core/core_catalog.dart | 2 +- pkgs/flutter_genui/lib/src/model/catalog.dart | 7 +- .../lib/src/model/catalog_item.dart | 20 +++--- .../lib/src/model/dynamic_ui.dart | 3 +- .../lib/src/to_merge/model/controller.dart | 2 +- pkgs/flutter_genui/pubspec.yaml | 2 +- pkgs/flutter_genui/test/dynamic_ui_test.dart | 2 +- .../test/core/widget_registry_test.dart | 64 ++++++++++--------- .../test/widgets/error_handling_test.dart | 58 +++++++++-------- 15 files changed, 107 insertions(+), 79 deletions(-) diff --git a/doc/USAGE.md b/doc/USAGE.md index b55d16237..f945ab0bf 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -5,4 +5,6 @@ 1. Follow [the steps](https://firebase.google.com/docs/flutter/setup) to configure Firebase for your project. + NOTE: see how secure it is to publish the app [here](https://firebase.google.com/docs/projects/learn-more#config-files-objects). + 2. diff --git a/pkgs/flutter_genui/lib/src/catalog/core_widgets/radio_group.dart b/pkgs/flutter_genui/lib/src/catalog/core_widgets/radio_group.dart index 48679f6ca..057619939 100644 --- a/pkgs/flutter_genui/lib/src/catalog/core_widgets/radio_group.dart +++ b/pkgs/flutter_genui/lib/src/catalog/core_widgets/radio_group.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_dynamic_calls + import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter/material.dart'; @@ -62,7 +64,9 @@ class _RadioGroupState extends State<_RadioGroup> { return RadioListTile( title: Text(label), value: label, + // ignore: deprecated_member_use groupValue: _groupValue, + // ignore: deprecated_member_use onChanged: changedCallback, ); }).toList(), diff --git a/pkgs/flutter_genui/lib/src/catalog/core_widgets/text.dart b/pkgs/flutter_genui/lib/src/catalog/core_widgets/text.dart index 122cb0af7..694bb49e6 100644 --- a/pkgs/flutter_genui/lib/src/catalog/core_widgets/text.dart +++ b/pkgs/flutter_genui/lib/src/catalog/core_widgets/text.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_dynamic_calls + import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter/material.dart'; import '../../model/catalog_item.dart'; diff --git a/pkgs/flutter_genui/lib/src/catalog/core_widgets/text_field.dart b/pkgs/flutter_genui/lib/src/catalog/core_widgets/text_field.dart index f22176a9f..2cccc25d4 100644 --- a/pkgs/flutter_genui/lib/src/catalog/core_widgets/text_field.dart +++ b/pkgs/flutter_genui/lib/src/catalog/core_widgets/text_field.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_dynamic_calls + import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter/material.dart'; diff --git a/pkgs/flutter_genui/lib/src/core/conversation_manager.dart b/pkgs/flutter_genui/lib/src/core/conversation_manager.dart index 2eb11a33c..548ff4da1 100644 --- a/pkgs/flutter_genui/lib/src/core/conversation_manager.dart +++ b/pkgs/flutter_genui/lib/src/core/conversation_manager.dart @@ -2,13 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:firebase_ai/firebase_ai.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_genui/flutter_genui.dart'; +import '../../flutter_genui.dart'; -import '../model/catalog.dart'; import '../model/chat_message.dart'; import 'conversation_widget.dart'; -import 'event_debouncer.dart'; -import '../model/ui_models.dart'; class ConversationManager { ConversationManager( @@ -33,7 +30,8 @@ class ConversationManager { int _outstandingRequests = 0; - // Stream of updates to the ui data which are used to build the Conversation Widget every time the conversation is updated. + // Stream of updates to the ui data which are used to build the + // Conversation Widget every time the conversation is updated. final StreamController> _uiDataStreamController = StreamController>.broadcast(); @@ -49,7 +47,8 @@ class ConversationManager { _eventDebouncer.dispose(); } - /// Sends a prompt on behalf of the end user. This should update the UI and also trigger an LLM inference via the llmConnection. + /// Sends a prompt on behalf of the end user. This should update the UI and + /// also trigger an LLM inference via the llmConnection. void sendUserPrompt(String prompt) { if (prompt.isEmpty) { return; @@ -205,7 +204,8 @@ class ConversationManager { 'surfaceId': Schema.string( description: 'The ID of the surface to perform the action on. For the ' - '`add` action, this will be a new surface ID. For `update` and ' + '`add` action, this will be a new surface ID. ' + 'For `update` and ' '`delete`, this will be an existing surface ID.', ), 'definition': Schema.object( diff --git a/pkgs/flutter_genui/lib/src/core/conversation_widget.dart b/pkgs/flutter_genui/lib/src/core/conversation_widget.dart index b2de00d60..52d6fc5e4 100644 --- a/pkgs/flutter_genui/lib/src/core/conversation_widget.dart +++ b/pkgs/flutter_genui/lib/src/core/conversation_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import '../model/catalog.dart'; +import '../model/chat_message.dart'; import '../model/dynamic_ui.dart'; import '../model/ui_models.dart'; -import '../model/chat_message.dart'; class ConversationWidget extends StatelessWidget { const ConversationWidget({ diff --git a/pkgs/flutter_genui/lib/src/core/core_catalog.dart b/pkgs/flutter_genui/lib/src/core/core_catalog.dart index 1c683836a..bc3602b38 100644 --- a/pkgs/flutter_genui/lib/src/core/core_catalog.dart +++ b/pkgs/flutter_genui/lib/src/core/core_catalog.dart @@ -1,10 +1,10 @@ -import '../model/catalog.dart'; import '../catalog/core_widgets/checkbox_group.dart'; import '../catalog/core_widgets/column.dart'; import '../catalog/core_widgets/elevated_button.dart'; import '../catalog/core_widgets/radio_group.dart'; import '../catalog/core_widgets/text.dart'; import '../catalog/core_widgets/text_field.dart'; +import '../model/catalog.dart'; final coreCatalog = Catalog([ elevatedButtonCatalogItem, diff --git a/pkgs/flutter_genui/lib/src/model/catalog.dart b/pkgs/flutter_genui/lib/src/model/catalog.dart index d25f9a3b3..f00992ac5 100644 --- a/pkgs/flutter_genui/lib/src/model/catalog.dart +++ b/pkgs/flutter_genui/lib/src/model/catalog.dart @@ -42,12 +42,15 @@ class Catalog { return Schema.object( description: - 'Represents a *single* widget in a UI widget tree. This widget could be one of many supported types.', + 'Represents a *single* widget in a UI widget tree. ' + 'This widget could be one of many supported types.', properties: { 'id': Schema.string(), 'widget': Schema.object( description: - 'The properties of the specific widget that this represents. This is a oneof - only *one* field should be set on this object!', + 'The properties of the specific widget ' + 'that this represents. This is a oneof - only *one* ' + 'field should be set on this object!', properties: schemaProperties, optionalProperties: optionalSchemaProperties, ), diff --git a/pkgs/flutter_genui/lib/src/model/catalog_item.dart b/pkgs/flutter_genui/lib/src/model/catalog_item.dart index fa0d7bf50..c5040b8af 100644 --- a/pkgs/flutter_genui/lib/src/model/catalog_item.dart +++ b/pkgs/flutter_genui/lib/src/model/catalog_item.dart @@ -1,16 +1,18 @@ -import 'package:flutter/material.dart'; import 'package:firebase_ai/firebase_ai.dart'; +import 'package:flutter/material.dart'; -typedef CatalogWidgetBuilder = Widget Function( - dynamic - data, // The actual deserialized JSON data for this widget. The format of this data will exactly match dataSchema below. - String id, // The ID of this widget. - Widget Function(String id) +typedef CatalogWidgetBuilder = + Widget Function( + // The actual deserialized JSON data for this widget. The format of this + // data will exactly match dataSchema below. + dynamic data, + String id, // The ID of this widget. + Widget Function(String id) buildChild, // A function used to build a child based on the given ID. - void Function(String widgetId, String eventType, Object? value) + void Function(String widgetId, String eventType, Object? value) dispatchEvent, // A function used to dispatch an event. - BuildContext context, // The build context. -); + BuildContext context, // The build context. + ); /// Defines a UI layout type, its schema, and how to build its widget. class CatalogItem { diff --git a/pkgs/flutter_genui/lib/src/model/dynamic_ui.dart b/pkgs/flutter_genui/lib/src/model/dynamic_ui.dart index 1da99d90b..b1f119835 100644 --- a/pkgs/flutter_genui/lib/src/model/dynamic_ui.dart +++ b/pkgs/flutter_genui/lib/src/model/dynamic_ui.dart @@ -54,7 +54,8 @@ class _DynamicUiState extends State { } /// The main recursive build function. - /// It reads a widget definition and its current state from [_widgetStates] + /// It reads a widget definition and its current state from + /// `widget.definition` /// and constructs the corresponding Flutter widget. Widget _buildWidget(String widgetId) { return widget.catalog.buildWidget( diff --git a/pkgs/flutter_genui/lib/src/to_merge/model/controller.dart b/pkgs/flutter_genui/lib/src/to_merge/model/controller.dart index b3ee0d2b1..1e5bd3656 100644 --- a/pkgs/flutter_genui/lib/src/to_merge/model/controller.dart +++ b/pkgs/flutter_genui/lib/src/to_merge/model/controller.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +import 'image_catalog.dart'; import 'input.dart'; import 'simple_items.dart'; -import 'image_catalog.dart'; typedef InputCallback = void Function(Input input); diff --git a/pkgs/flutter_genui/pubspec.yaml b/pkgs/flutter_genui/pubspec.yaml index 47f655589..57957c3d1 100644 --- a/pkgs/flutter_genui/pubspec.yaml +++ b/pkgs/flutter_genui/pubspec.yaml @@ -21,6 +21,6 @@ dependencies: stream_channel: ^2.1.4 dev_dependencies: + dart_flutter_team_lints: ^3.5.2 flutter_test: sdk: flutter - dart_flutter_team_lints: ^3.5.2 diff --git a/pkgs/flutter_genui/test/dynamic_ui_test.dart b/pkgs/flutter_genui/test/dynamic_ui_test.dart index 9cc3509b3..3c9c4063b 100644 --- a/pkgs/flutter_genui/test/dynamic_ui_test.dart +++ b/pkgs/flutter_genui/test/dynamic_ui_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_genui/flutter_genui.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { final testCatalog = Catalog([elevatedButtonCatalogItem, text]); diff --git a/pkgs/spikes/fcp_client/test/core/widget_registry_test.dart b/pkgs/spikes/fcp_client/test/core/widget_registry_test.dart index 76868cf4e..c4e7c6eba 100644 --- a/pkgs/spikes/fcp_client/test/core/widget_registry_test.dart +++ b/pkgs/spikes/fcp_client/test/core/widget_registry_test.dart @@ -5,7 +5,9 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('WidgetCatalogRegistry', () { - final testDefinition = WidgetDefinition({'properties': {}}); + final testDefinition = WidgetDefinition({ + 'properties': {}, + }); test('registering and retrieving a builder', () { final registry = WidgetCatalogRegistry(); @@ -14,14 +16,15 @@ void main() { LayoutNode node, Map properties, Map children, - ) => - const SizedBox(); + ) => const SizedBox(); - registry.register(CatalogItem( - name: 'TestWidget', - builder: testBuilder, - definition: testDefinition, - )); + registry.register( + CatalogItem( + name: 'TestWidget', + builder: testBuilder, + definition: testDefinition, + ), + ); final retrievedBuilder = registry.getBuilder('TestWidget'); expect(retrievedBuilder, isNotNull); @@ -41,29 +44,31 @@ void main() { LayoutNode node, Map properties, Map children, - ) => - const Text('1'); + ) => const Text('1'); Widget builder2( BuildContext context, LayoutNode node, Map properties, Map children, - ) => - const Text('2'); + ) => const Text('2'); - registry.register(CatalogItem( - name: 'TestWidget', - builder: builder1, - definition: testDefinition, - )); + registry.register( + CatalogItem( + name: 'TestWidget', + builder: builder1, + definition: testDefinition, + ), + ); final retrievedBuilder1 = registry.getBuilder('TestWidget'); expect(retrievedBuilder1, equals(builder1)); - registry.register(CatalogItem( - name: 'TestWidget', - builder: builder2, - definition: testDefinition, - )); + registry.register( + CatalogItem( + name: 'TestWidget', + builder: builder2, + definition: testDefinition, + ), + ); final retrievedBuilder2 = registry.getBuilder('TestWidget'); expect(retrievedBuilder2, equals(builder2)); }); @@ -75,15 +80,16 @@ void main() { LayoutNode node, Map properties, Map children, - ) => - const SizedBox(); + ) => const SizedBox(); expect(registry.hasBuilder('TestWidget'), isFalse); - registry.register(CatalogItem( - name: 'TestWidget', - builder: testBuilder, - definition: testDefinition, - )); + registry.register( + CatalogItem( + name: 'TestWidget', + builder: testBuilder, + definition: testDefinition, + ), + ); expect(registry.hasBuilder('TestWidget'), isTrue); }); }); diff --git a/pkgs/spikes/fcp_client/test/widgets/error_handling_test.dart b/pkgs/spikes/fcp_client/test/widgets/error_handling_test.dart index c9f0cfc09..3917ba370 100644 --- a/pkgs/spikes/fcp_client/test/widgets/error_handling_test.dart +++ b/pkgs/spikes/fcp_client/test/widgets/error_handling_test.dart @@ -11,8 +11,8 @@ void main() { Text(properties['data'] as String? ?? ''), definition: WidgetDefinition({ 'properties': { - 'data': {'type': 'String', 'isRequired': true} - } + 'data': {'type': 'String', 'isRequired': true}, + }, }), ), ); @@ -32,16 +32,16 @@ void main() { { 'id': 'node_a', 'type': 'Container', // This type is not in the test catalog - 'properties': {'child': 'node_b'} + 'properties': {'child': 'node_b'}, }, { 'id': 'node_b', 'type': 'Container', - 'properties': {'child': 'node_a'} - } - ] + 'properties': {'child': 'node_a'}, + }, + ], }, - 'state': {} + 'state': {}, }); // We need a registry that has Container to test the cycle @@ -53,20 +53,22 @@ void main() { Container(child: children['child'] as Widget?), definition: WidgetDefinition({ 'properties': { - 'child': {'type': 'WidgetId'} - } + 'child': {'type': 'WidgetId'}, + }, }), ), ); final cycleCatalog = cycleRegistry.buildCatalog(catalogVersion: '1.0.0'); - await tester.pumpWidget(MaterialApp( - home: FcpView( - packet: packet, - registry: cycleRegistry, - catalog: cycleCatalog, + await tester.pumpWidget( + MaterialApp( + home: FcpView( + packet: packet, + registry: cycleRegistry, + catalog: cycleCatalog, + ), ), - )); + ); expect(find.textContaining('Cyclical layout detected'), findsOneWidget); }); @@ -80,23 +82,27 @@ void main() { { 'id': 'text_node', 'type': 'Text', - 'properties': {} // Missing 'data' - } - ] + 'properties': {}, // Missing 'data' + }, + ], }, - 'state': {} + 'state': {}, }); - await tester.pumpWidget(MaterialApp( - home: FcpView( - packet: packet, - registry: testRegistry, - catalog: testCatalog, + await tester.pumpWidget( + MaterialApp( + home: FcpView( + packet: packet, + registry: testRegistry, + catalog: testCatalog, + ), ), - )); + ); expect( - find.textContaining('Missing required property "data"'), findsOneWidget); + find.textContaining('Missing required property "data"'), + findsOneWidget, + ); }); }); } From 32576ade105c019900a621edda557c0302e88864 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Wed, 30 Jul 2025 15:15:41 -0700 Subject: [PATCH 4/5] Update USAGE.md --- doc/USAGE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/USAGE.md b/doc/USAGE.md index f945ab0bf..69a6d8016 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -5,6 +5,7 @@ 1. Follow [the steps](https://firebase.google.com/docs/flutter/setup) to configure Firebase for your project. - NOTE: see how secure it is to publish the app [here](https://firebase.google.com/docs/projects/learn-more#config-files-objects). + NOTE: see how secure it is to publish the generated files + [here](https://firebase.google.com/docs/projects/learn-more#config-files-objects). 2. From bf93609044654e48f924c5f36cb4af5646f07194 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Wed, 30 Jul 2025 15:26:56 -0700 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ecd52083..0c12b78b8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository is a collection of packages and applications for the Flutter Gen ## TODO before publishing 1. Clean up Google internal information. -2. Make colors and sizes configurable in catalog. +2. Make colors and sizes consistent and organized in catalog. ## Packages