From bce8d1881c1ca181af9bbf5d692350d5b2217600 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 02:56:58 +0200 Subject: [PATCH 1/7] delete .firebaserc --- genui_workshop/.firebaserc | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 genui_workshop/.firebaserc diff --git a/genui_workshop/.firebaserc b/genui_workshop/.firebaserc deleted file mode 100644 index e282fbb..0000000 --- a/genui_workshop/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "callstest-f6c61" - } -} From 37c525d6c998523d8604ec15cec24d63535f3fb5 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:02:12 +0200 Subject: [PATCH 2/7] update README.md --- genui_workshop/README.md | 902 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 901 insertions(+), 1 deletion(-) diff --git a/genui_workshop/README.md b/genui_workshop/README.md index e11b9f7..0297642 100644 --- a/genui_workshop/README.md +++ b/genui_workshop/README.md @@ -1,3 +1,903 @@ # genui_workshop -A new Flutter project. +## Step 1: Set up Firebase + +### Create a Firebase project +1. Sign into the [Firebase console](https://console.firebase.google.com/) + using your Google Account. + +1. Click the button to create a new project, and then enter a project name + (for example, `GenUI Workshop`).
+ +1. Click **Continue**. + +1. If prompted, review and accept the [Firebase terms](/terms), and then click + **Continue**. + +1. Click **Create project**, wait for your project to provision, and then click + **Continue**. + +### Enable Firebase AI Logic + +To enable Firebase AI Logic in your Firebase project: + +1. In the Firebase console, go to **AI Services** > + [**AI Logic**](https://console.firebase.google.com/project/_/ailogic/). + +1. Click **Get started**. + +1. Choose to use the _Gemini Developer API_ by clicking + **Get started with this API**. + +1. Click **Enable API** and confirm. + +## Step 2: Activate Cloud Shell + +Cloud Shell space is persistent between projects so make sure you are creating things in proper project directories. + +## Step 3: Creating the project + +Cloud shell needs to link to the stored version of Flutter so we need the following command before running `flutter`. + +```shell +git config --global --add safe.directory /google/flutter +``` + +We'll be starting with an empty project and adding code as we go. Warning that this might take a little while on Cloud Shell. + +```shell +flutter create --empty genui_workshop +``` + +Next we can attach our local code to the previously created project with `flutterfire`. We only need the web target. + +```shell +dart pub global activate flutterfire_cli +export PATH="$PATH":"$HOME/.pub-cache/bin" + +cd +flutterfire configure +;; Select the project you just created +;; deselect all but web +``` + +```shell +flutter pub add genui firebase_core firebase_ai json_schema_builder +``` + +Let's run the app. + +Cloud shell doesn't allow `flutter run -d chrome` by default so we will test it by building the web target and starting a server. +```shell +flutter build web +python -m http.server 8080 --directory build/web +``` + +## Step 4: Setup GenUI scaffolding. + +Create `lib/genui_utils.dart` +```dart +sealed class ConversationItem {} + +class TextItem extends ConversationItem { + final String text; + final bool isUser; + TextItem({required this.text, this.isUser = false}); +} +``` + +Create `lib/message_bubble.dart`. +```dart +import 'package:flutter/material.dart'; + +class MessageBubble extends StatelessWidget { + final String text; + final bool isUser; + + const MessageBubble({super.key, required this.text, required this.isUser}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + + final bubbleColor = isUser + ? colorScheme.primary + : colorScheme.surfaceContainerHighest; + + final textColor = isUser + ? colorScheme.onPrimary + : colorScheme.onSurfaceVariant; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0), + child: Column( + crossAxisAlignment: isUser + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: isUser + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + Flexible( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + decoration: BoxDecoration( + color: bubbleColor, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(20), + topRight: const Radius.circular(20), + bottomLeft: Radius.circular(isUser ? 20 : 0), + bottomRight: Radius.circular(isUser ? 0 : 20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(20), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + gradient: isUser + ? LinearGradient( + colors: [ + colorScheme.primary, + colorScheme.primary.withAlpha(200), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + ), + child: Text( + text, + style: theme.textTheme.bodyLarge?.copyWith( + color: textColor, + height: 1.3, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 2), + ], + ), + ); + } +} + +``` + +Paste the following to `main.dart` +```dart +import 'package:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_ai/firebase_ai.dart'; +import 'package:genui_workshop/message_bubble.dart'; +import 'package:genui_workshop/genui_utils.dart'; +import 'firebase_options.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Weather Today', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + ), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final List _items = []; + final _textController = TextEditingController(); + final _scrollController = ScrollController(); + late final ChatSession _chatSession; + + @override + void initState() { + super.initState(); + final model = FirebaseAI.googleAI().generativeModel( + model: 'gemini-3.1-flash-lite', + ); + _chatSession = model.startChat(); + _chatSession.sendMessage(Content.text(systemInstruction)); + } + + void _scrollToBottom() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + + @override + void dispose() { + _textController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + Future _addMessage() async { + final text = _textController.text; + + if (text.trim().isEmpty) { + return; + } + _textController.clear(); + + setState(() { + _items.add(TextItem(text: text, isUser: true)); + }); + + _scrollToBottom(); + + final response = await _chatSession.sendMessage(Content.text(text)); + + if (response.text?.isNotEmpty ?? false) { + setState(() { + _items.add(TextItem(text: response.text!, isUser: false)); + }); + _scrollToBottom(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Weather'), + ), + body: Column( + children: [ + Expanded( + child: ListView( + controller: _scrollController, + padding: const EdgeInsets.all(16), + children: [ + for (final item in _items) + switch (item) { + TextItem() => MessageBubble( + text: item.text, + isUser: item.isUser, + ), + }, + ], + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _textController, + onSubmitted: (_) => _addMessage(), + decoration: const InputDecoration( + hintText: 'Enter a message', + ), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _addMessage, + child: const Text('Send'), + ), + ], + ), + ), + ), + ], + ), + ); + } +} +``` + + +Add the system instruction to `genui_utils.dart`. +```dart + const systemInstruction = ''' + ## PERSONA + You are a meteorologist. + + ## GOAL + Work with me to produce of weather forecasts. + + ## RULES + + Do not offer opinions unless I ask for them. + + ## PROCESS + ### Planning + * Ask me for a location to check the weather. + * Follow up and ask for a date if not provided. + * Synthesize a list of weather forecasts from the provided information. + * Where available, you will use tool calls to retreive the info (not implemented yet) + * Advise if you are pulling the data from a real source or making it up. + * Ask clarifying questions if you need to. + * Respond to my suggestions for changes to date or location, if I have any. +'''; +``` + +Test app. It runs but doesn't really repsond. + +## Integrate GenUI package +Add the following to `genui_utils.dart` +```dart +class SurfaceItem extends ConversationItem { + final String surfaceId; + SurfaceItem({required this.surfaceId}); +} + +``` + +In `main.dart`, add a case for the `SurfaceItem` type to the ListView `build` method: +```dart +Expanded( + child: ListView( + controller: _scrollController, + padding: const EdgeInsets.all(16), + children: [ + for (final item in _items) + switch (item) { + TextItem() => MessageBubble( + text: item.text, + isUser: item.isUser, + ), + // New! + SurfaceItem() => Surface( + surfaceContext: _controller.contextFor( + item.surfaceId, + ), + ), + }, + ], + ), +), +``` + + +At the top of lib/main.dart, import the genui library: +```dart +import 'package:genui/genui.dart' hide TextPart; +import 'package:genui/genui.dart' as genui; +``` + + +Add the following lifecycle controllers and adapters to `main.dart` +```dart +class _MyHomePageState extends State { + // ... existing members + late final ChatSession _chatSession; + + // Add GenUI controllers + late final SurfaceController _controller; + late final A2uiTransportAdapter _transport; + late final Conversation _conversation; + late final Catalog catalog; +``` + +RUN. + +Update `_addMessage` and `sendAndReceive` like in code lab. + +```dart + Future _addMessage() async { + final text = _textController.text; + + if (text.trim().isEmpty) { + return; + } + + _textController.clear(); + + setState(() { + _items.add(TextItem(text: text, isUser: true)); + }); + + _scrollToBottom(); + + // Send the user's input through GenUI instead of directly to Firebase. + await _conversation.sendRequest(ChatMessage.user(text)); + } +``` + +```dart + Future _sendAndReceive(ChatMessage msg) async { + final buffer = StringBuffer(); + + // Reconstruct the message part fragments + for (final part in msg.parts) { + if (part.isUiInteractionPart) { + buffer.write(part.asUiInteractionPart!.interaction); + print(part.asUiInteractionPart!.interaction); + } else if (part is genui.TextPart) { + buffer.write(part.text); + } + } + + if (buffer.isEmpty) { + return; + } + + final text = buffer.toString(); + + // Send the string to Firebase AI Logic. + final response = await _chatSession.sendMessage(Content.text(text)); + + if (response.text?.isNotEmpty ?? false) { + // Feed the response back into GenUI's transportation layer + _transport.addChunk(response.text!); + } + } +``` + +TEST. + +### Actually wire up A2UI + +Add to the end of initState. +```dart + // remove this line + // _chatSession.sendMessage(Content.text(systemInstruction)); + + // Initialize the GenUI Catalog. + // The genui package provides a default set of primitive widgets (like text + // and basic buttons) out of the box using this class. + catalog = BasicCatalogItems.asCatalog().copyWith( +// newItems: [weatherInput, weatherCard], + ); + + // Create a SurfaceController to manage the state of generated surfaces. + _controller = SurfaceController(catalogs: [catalog]); + + // Create a transport adapter that will process messages to and from the + // agent, looking for A2UI messages. + _transport = A2uiTransportAdapter(onSend: _sendAndReceive); + + // Link the transport and SurfaceController together in a Conversation, + // which provides your app a unified API for interacting with the agent. + _conversation = Conversation( + controller: _controller, + transport: _transport, + ); + + // Listen to GenUI stream events to update the UI + _conversation.events.listen((event) { + setState(() { + switch (event) { + case ConversationSurfaceAdded added: + _items.add(SurfaceItem(surfaceId: added.surfaceId)); + _scrollToBottom(); + case ConversationSurfaceRemoved removed: + _items.removeWhere( + (item) => + item is SurfaceItem && item.surfaceId == removed.surfaceId, + ); + case ConversationContentReceived content: + _items.add(TextItem(text: content.text, isUser: false)); + _scrollToBottom(); + case ConversationError error: + debugPrint('GenUI Error: ${error.error}'); + default: + } + }); + }); + + // Create the system prompt for the agent, which will include this app's + // system instruction as well as the schema for the catalog. + final promptBuilder = PromptBuilder.chat( + catalog: catalog, + systemPromptFragments: [systemInstruction], + ); + + // Send the prompt into the Conversation, which will subsequently route it + // to Firebase using the transport mechanism. + _conversation.sendRequest( + ChatMessage.system(promptBuilder.systemPromptJoined()), + ); +``` + +We could explore doing some progress indicator UI here or not. + +## Make weather input widget +The components it picks is random and can't be submitted sometimes + +Create a `lib/catalog` directory. One file per item with the schema included. + +Add the following to `lib/catalog/weather_input.dart` +```dart +import 'package:flutter/material.dart'; +import 'package:genui/genui.dart'; +import 'package:json_schema_builder/json_schema_builder.dart'; + +final simpleWeatherSchema = S.object( + properties: { + 'location': S.string(description: 'The location to check the weather.'), + 'date': S.string(description: 'The date to check the weather.'), + }, +); + +// Example raw action +//{version: v0.9, action: {name: submit_weather_request, sourceComponentId: submitButton, +//timestamp: 2026-05-26T22:10:19.087, context: {}, surfaceId: weather_input_surface}} +final weatherInput = CatalogItem( + name: 'WeatherInput', + dataSchema: simpleWeatherSchema, + widgetBuilder: (itemContext) { + final json = itemContext.data as Map; + final data = SimpleWeatherData.fromJson(json); + return WeatherInput( + data: data, + onFetchRequest: (loc, date) async { + final JsonMap resolvedContext = await resolveContext( + itemContext.dataContext, + {'location': loc, 'date': date.toString()}, + ); + itemContext.dispatchEvent( + UserActionEvent( + name: 'submit_weather_request', + sourceComponentId: 'submitButton', + timestamp: DateTime.now(), + context: resolvedContext + ), + ); + }, + ); + }, +); + +class SimpleWeatherData { + String location; + DateTime date; + + SimpleWeatherData({required this.location, required this.date}); + + factory SimpleWeatherData.defaultValues() { + return SimpleWeatherData(location: 'Baltimore', date: DateTime.now()); + } + + factory SimpleWeatherData.fromJson(Map json) { + if (json.isNotEmpty) { + return SimpleWeatherData( + location: json['location'] as String, + date: DateTime.parse(json['date'] as String), + ); + } else { + + return SimpleWeatherData(location: 'Baltimore', date: DateTime.now()); + } + } +} + +class WeatherInput extends StatefulWidget { + final SimpleWeatherData data; + final void Function(String, DateTime) onFetchRequest; + + const WeatherInput({ + super.key, + required this.data, + required this.onFetchRequest, + }); + + @override + State createState() => _WeatherInputState(); +} + +class _WeatherInputState extends State { + late TextEditingController _controller; + DateTime? selectedDate = DateTime.now(); + + Future _selectDate() async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now().copyWith(month: 1, day: 1), + lastDate: DateTime.now().copyWith(month: 12, day: 31), + ); + + setState(() { + selectedDate = pickedDate; + }); + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + _controller = TextEditingController(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + + @override + Widget build(BuildContext context) { + return Container( + width: 320, + padding: const EdgeInsets.all(16), + child: Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Location", style: TextStyle(fontWeight: FontWeight.bold) ), + const SizedBox(height: 8), + TextField(decoration: InputDecoration(border: OutlineInputBorder()), controller: _controller,), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 50, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), + ), + onPressed: () { + + widget.onFetchRequest(_controller.text, selectedDate!); + }, + child: const Text("Get Forecast", style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold)), + ), + ), + ], + ), + ), + ), + ); + } +} +``` +- Add `weatherInput` as a new item in the basic catalog by importing it and adding it to `newItems` in `lib/main.dart`: + +```dart +import 'package:genui_workshop/catalog/weather_input.dart'; +// ... + catalog = BasicCatalogItems.asCatalog().copyWith( + newItems: [weatherInput], + ); +``` + +- Change the system prompt to point it to the catalog item. + +Add this to end of the system instruction prompt. +```dart + + ## USER INTERFACE + * To request the location to retreive weather, create an instance of the WeatherInput + catalog item. +``` + +Create a file called `lib/catalog/weather_card.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:genui_workshop/genui_utils.dart'; +import 'package:genui/genui.dart'; +import 'package:json_schema_builder/json_schema_builder.dart'; +import 'dart:io'; + +import 'dart:convert'; +import 'fake_forecast.dart'; + +final forecastSchema = S.object( + properties: { + 'area_name': S.string(), + //"flags": S.object(), + 'current_condition': S.object( + properties: { + "temp_C": S.number(), + "temp_F": S.number(), + "humidity": S.number(), + "observation_time": S.string(), + }, + ), + 'weatherDesc': S.string(), + 'weatherIconUrl': S.string(), + }, +); + +final weatherCard = CatalogItem( + name: 'WeatherCard', + dataSchema: forecastSchema, + widgetBuilder: (itemContext) { + return WeatherCard(data: itemContext.data as Map); + }, +); + +class WeatherCard extends StatelessWidget { + final Map data; + const WeatherCard({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + final currentCondition = data["current_condition"] as Map; + return Container( + width: 500, + padding: const EdgeInsets.all(20), + child: Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: .end, + children: [ + Text( + "Observed at ${currentCondition["observation_time"]}", + style: TextStyle(fontSize: 12), + ), + ], + ), + const SizedBox(height: 8), + // Header: Icon and City Name + Row( + children: [ + Image.network( + data["weatherIconUrl"].toString(), + errorBuilder: (context, err, trace) { + return Icon(Icons.wb_sunny, size: 32,); + }, + ), + const SizedBox(width: 16), + Text( + data["area_name"].toString(), + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 24), + + // Temperature Display + Row( + children: [ + Text( + currentCondition["temp_C"]!.toString(), + style: TextStyle(fontSize: 56, fontWeight: FontWeight.bold), + ), + SizedBox(width: 8), + Text( + "°C", + style: TextStyle(fontSize: 32, fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox(height: 16), + + // Low Temp row + Row( + children: [ + const SizedBox(width: 16), + Text( + currentCondition['temp_F'].toString(), + style: TextStyle(fontSize: 28, fontWeight: FontWeight.w500), + ), + SizedBox(width: 8), + Text( + "°F", + style: TextStyle(fontSize: 20, color: Colors.grey), + ), + ], + ), + const SizedBox(height: 16), + + // Weather Condition + Text("${data['weatherDesc']}", style: TextStyle(fontSize: 22)), + ], + ), + ), + ), + ); + } +} +``` + +Add the `weatherCard` item to your catalog inside `lib/main.dart`: + +```dart +import 'package:genui_workshop/catalog/weather_card.dart'; +// ... + catalog = BasicCatalogItems.asCatalog().copyWith( + newItems: [weatherInput, weatherCard], + ); +``` + +Create a file called `lib/catalog/fake_forecast.dart` +```dart +var fakeForecast = { + 'area_name': 'Baltimore (Mount Clare)', + 'current_condition': { + 'temp_C': 22, + 'temp_F': 72, + 'humidity': 61, + 'observation_time': '02:25 PM', + }, + 'weatherDesc': ' Sunny', + 'weatherIconUrl': + 'https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png', +}; +``` + +## Functions + +Good area to split to another tutorial. + +Create a file named `lib/tool_calls.dart`. + +```dart +import 'package:firebase_ai/firebase_ai.dart'; + +final fetchWeatherGeocodeTool = FunctionDeclaration( + 'fetchWeather', + 'Retrieves the weather for the current date and returns geocoded location data', + parameters: { + 'location': Schema.string( + description: 'The location for which to retrieve the weather', + ), + 'date': Schema.string( + description: + 'The date for which to retrieve the weather', + ), + }, +); +``` + +Add the import to the top of `main.dart`. +```dart +import 'package:genui_workshop/tool_calls.dart'; +``` + +Update the model in `initState` from main.dart to the following: +```dart +final model = FirebaseAI.googleAI().generativeModel( + model: 'gemini-3.1-flash-lite', + // tools: [ + // Tool.functionDeclarations([fetchWeatherGeocodeTool]), + // ], + ); +``` + +.... +TBD + From de403f86178361e43d574be00e1baf2c055884b9 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:21:02 +0200 Subject: [PATCH 3/7] add flutter_run_cloudshell.sh --- genui_workshop/flutter_run_cloudshell.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 genui_workshop/flutter_run_cloudshell.sh diff --git a/genui_workshop/flutter_run_cloudshell.sh b/genui_workshop/flutter_run_cloudshell.sh new file mode 100755 index 0000000..255435f --- /dev/null +++ b/genui_workshop/flutter_run_cloudshell.sh @@ -0,0 +1,3 @@ +#!/bin/sh +flutter build web +python -m http.server 8080 --directory build/web From f31390dc8233056363546d405b951d936f3bc7c6 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:25:45 +0200 Subject: [PATCH 4/7] remove TBD and add links --- genui_workshop/README.md | 45 +++++----------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/genui_workshop/README.md b/genui_workshop/README.md index 0297642..e0e8d72 100644 --- a/genui_workshop/README.md +++ b/genui_workshop/README.md @@ -859,45 +859,10 @@ var fakeForecast = { }; ``` -## Functions +## Learn more -Good area to split to another tutorial. - -Create a file named `lib/tool_calls.dart`. - -```dart -import 'package:firebase_ai/firebase_ai.dart'; - -final fetchWeatherGeocodeTool = FunctionDeclaration( - 'fetchWeather', - 'Retrieves the weather for the current date and returns geocoded location data', - parameters: { - 'location': Schema.string( - description: 'The location for which to retrieve the weather', - ), - 'date': Schema.string( - description: - 'The date for which to retrieve the weather', - ), - }, -); -``` - -Add the import to the top of `main.dart`. -```dart -import 'package:genui_workshop/tool_calls.dart'; -``` - -Update the model in `initState` from main.dart to the following: -```dart -final model = FirebaseAI.googleAI().generativeModel( - model: 'gemini-3.1-flash-lite', - // tools: [ - // Tool.functionDeclarations([fetchWeatherGeocodeTool]), - // ], - ); -``` - -.... -TBD +For more information and detailed references on the technologies used in this workshop, check out the following resources: +- **Flutter**: Discover guides, tutorials, and widget references at the [Official Flutter Documentation](https://docs.flutter.dev). +- **Firebase AI Logic**: Learn how to securely integrate generative AI models directly into your web and mobile apps on the [Firebase AI Logic Documentation](https://firebase.google.com/docs/ai-logic). +- **A2UI Protocol**: Understand the Agent-to-User Interface protocol specification and ecosystem at the [A2UI Official Site](https://a2ui.org). From 8dd6ea41d2ebd5fae7b39087770231cfe3082dd9 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:32:18 +0200 Subject: [PATCH 5/7] tiny nits --- genui_workshop/README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/genui_workshop/README.md b/genui_workshop/README.md index e0e8d72..9550838 100644 --- a/genui_workshop/README.md +++ b/genui_workshop/README.md @@ -55,11 +55,11 @@ Next we can attach our local code to the previously created project with `flutte dart pub global activate flutterfire_cli export PATH="$PATH":"$HOME/.pub-cache/bin" -cd +cd genui_workshop flutterfire configure -;; Select the project you just created -;; deselect all but web ``` +Select the project you just created; +deselect all but web ```shell flutter pub add genui firebase_core firebase_ai json_schema_builder @@ -349,7 +349,8 @@ Add the system instruction to `genui_utils.dart`. '''; ``` -Test app. It runs but doesn't really repsond. +Run the app. +It runs and responds with weather information, but no generative UI yet. ## Integrate GenUI package Add the following to `genui_utils.dart` @@ -407,7 +408,7 @@ class _MyHomePageState extends State { late final Catalog catalog; ``` -RUN. +Run the app. Update `_addMessage` and `sendAndReceive` like in code lab. @@ -462,9 +463,7 @@ Update `_addMessage` and `sendAndReceive` like in code lab. } ``` -TEST. - -### Actually wire up A2UI +### Wire up A2UI Add to the end of initState. ```dart @@ -685,7 +684,8 @@ class _WeatherInputState extends State { } } ``` -- Add `weatherInput` as a new item in the basic catalog by importing it and adding it to `newItems` in `lib/main.dart`: + +Add `weatherInput` as a new item in the basic catalog by importing it and adding it to `newItems` in `lib/main.dart`: ```dart import 'package:genui_workshop/catalog/weather_input.dart'; @@ -695,9 +695,9 @@ import 'package:genui_workshop/catalog/weather_input.dart'; ); ``` -- Change the system prompt to point it to the catalog item. +Change the system prompt to point it to the catalog item. +Add this to end of the system instruction prompt: -Add this to end of the system instruction prompt. ```dart ## USER INTERFACE @@ -859,6 +859,8 @@ var fakeForecast = { }; ``` +Run the app. + ## Learn more For more information and detailed references on the technologies used in this workshop, check out the following resources: From 1bfddc03bcc22af3ae449b2eac6fa97d0139a23b Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:34:23 +0200 Subject: [PATCH 6/7] more tiny nits --- genui_workshop/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/genui_workshop/README.md b/genui_workshop/README.md index 9550838..1268d65 100644 --- a/genui_workshop/README.md +++ b/genui_workshop/README.md @@ -11,7 +11,7 @@ 1. Click **Continue**. -1. If prompted, review and accept the [Firebase terms](/terms), and then click +1. If prompted, review and accept the Firebase terms, and then click **Continue**. 1. Click **Create project**, wait for your project to provision, and then click @@ -35,7 +35,7 @@ To enable Firebase AI Logic in your Firebase project: Cloud Shell space is persistent between projects so make sure you are creating things in proper project directories. -## Step 3: Creating the project +## Step 3: Creating the Flutter project Cloud shell needs to link to the stored version of Flutter so we need the following command before running `flutter`. From b72eb8b4d6634866fd2963aba3da48a99820e059 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 25 Jun 2026 03:55:41 +0200 Subject: [PATCH 7/7] add link to GenUI SDK for Flutter --- genui_workshop/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/genui_workshop/README.md b/genui_workshop/README.md index 1268d65..666824b 100644 --- a/genui_workshop/README.md +++ b/genui_workshop/README.md @@ -868,3 +868,4 @@ For more information and detailed references on the technologies used in this wo - **Flutter**: Discover guides, tutorials, and widget references at the [Official Flutter Documentation](https://docs.flutter.dev). - **Firebase AI Logic**: Learn how to securely integrate generative AI models directly into your web and mobile apps on the [Firebase AI Logic Documentation](https://firebase.google.com/docs/ai-logic). - **A2UI Protocol**: Understand the Agent-to-User Interface protocol specification and ecosystem at the [A2UI Official Site](https://a2ui.org). +- [**GenUI SDK for Flutter**](https://docs.flutter.dev/ai/genui)