Skip to main content

genui-workshop-step-4

Execute Step 4 of the GenUI Workshop, setting up the GenUI scaffolding and initial chat interface.

Source facts

Repository
flutter/demos
Last source activity
July 6, 2026 at 02:18
Detected SKILL.md language
English
Stars
152
Forks
102

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
genui_workshop_step_4
description
Execute Step 4 of the GenUI Workshop, setting up the GenUI scaffolding and initial chat interface.
# GenUI Workshop - Step 4 **Goal**: Execute Step 4 of the GenUI workshop. **Instructions**: Use your code editing tools to create or overwrite the following files in the project. 1. 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}); } 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. '''; ``` 2. 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), ], ), ); } } ``` 3. Overwrite `lib/main.dart` with: ```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<void> 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<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final List<ConversationItem> _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<void> _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'), ), ], ), ), ), ], ), ); } } ```
View on GitHub