Migrating to Flutter's GenUI v0.9: A Step-by-Step Guide
Introduction
Generative UI (GenUI) is a design pattern where an AI agent not only generates content but also decides how to present and make it interactive. For Flutter developers, this is made possible through A2UI, an open protocol for agents and clients to collaborate on UI composition. The Flutter team's genui package builds on A2UI, connecting agents with a widget catalog. Recently, both the genui package and A2UI protocol received updates, shifting from a "Structured Output First" approach (where A2UI messages streamed via structured output APIs) to a "Prompt First" approach (agents embed JSON in text responses). This guide walks you through migrating from genui v0.7.0 to v0.9.0, focusing on architectural decoupling, dependency updates, and wiring up new chat loops.
What You Need
- An existing Flutter project using genui v0.7.0 or earlier
- Flutter SDK (latest stable version recommended)
- Basic familiarity with Flutter and state management
- Access to an LLM provider (e.g., Google AI, Firebase AI, OpenAI)
- Your agent's API key or configuration
- Code editor or IDE (VS Code, Android Studio, etc.)
Step-by-Step Migration Guide
Step 1: Update Package Dependencies
Begin by updating the genui package to the latest version. Open your pubspec.yaml file, locate the dependencies section, and change the version constraint for genui to ^0.9.0 (or higher). Then run flutter pub get to fetch the new version. If you have any provider-specific packages like genui_firebase_ai, genui_google_generative_ai, or genui_dartantic, remove them from pubspec.yaml — these are no longer supported in v0.9.0.
Step 2: Clean Up Deprecated Imports
With the old provider packages gone, you'll need to remove any imports referencing them. Search your codebase for lines like:
import 'package:genui_firebase_ai/genui_firebase_ai.dart';
Delete these imports. Also, remove any code that uses ContentGenerator classes — they no longer exist. The framework now uses a decoupled architecture: Engine (SurfaceController), Transport (A2uiTransportAdapter), and Facade (Conversation).
Step 3: Set Up the New Architecture
Replace your old SurfaceController instantiation. Previously, you might have done:
final generator = FirebaseAiContentGenerator();
final controller = SurfaceController(generator: generator);
Now, you'll create a SurfaceController without a generator. Instead, you'll manage the connection to the LLM yourself. Create a transport adapter that streams messages between the agent and your UI. For example:
import 'package:genui/genui.dart';
import 'your_llm_connection.dart';
final transport = A2uiTransportAdapter(
sendMessage: (message) async {
// Your code to send message to LLM and get response
return await yourLlm.send(message);
},
);
final controller = SurfaceController(transport: transport);
You are now responsible for the conversation history, retry logic, and error handling. This gives you full control over the LLM call.
Step 4: Implement the Chat Loop with Conversation Facade
Use the Conversation class to manage chat states. Create a Conversation object that wraps your SurfaceController:
final conversation = Conversation(controller: controller);
// Use the conversation to send user input
await conversation.sendUserMessage('Hello');
// Then listen for responses
conversation.stream.listen((event) {
// Handle UI updates
});
You can customize how the conversation processes each message, adding your own prompt logic, system instructions, or function calling before sending to the LLM.
Step 5: Wire Up Your LLM Connection
Since the framework no longer wraps the agent, you'll need to integrate your preferred LLM provider directly. For example, using the google_generative_ai package:
import 'package:google_generative_ai/google_generative_ai.dart';
final model = GenerativeModel(model: 'gemini-pro', apiKey: 'YOUR_KEY');
final chat = model.startChat();
// Inside your transport sendMessage:
sendMessage: (message) async {
final response = await chat.sendMessage(Content.text(message));
return response.text ?? '';
},
You can add generation config, safety settings, or any custom functions here. The key point: you're no longer constrained by the framework's API.
Step 6: Test and Debug
Run your Flutter app. You should see the UI rendering generated widgets from the agent. Test typical flows: sending messages, receiving responses that include widget definitions, and interacting with those widgets. Common issues include:
- Missing or incorrect JSON in agent responses — ensure your prompt instructs the agent to output valid A2UI JSON.
- Transport adapter not receiving responses — check your LLM API call and error handling.
- Widgets not appearing — verify your widget catalog is registered with the SurfaceController.
Add logging in the transport adapter to see messages flowing. Use the Conversation facade's events to monitor state changes.
Tips
- Start small: Migrate one screen at a time to isolate issues.
- Leverage the Conversation facade: It simplifies chat state management and provides a clean API.
- Handle errors gracefully: Since you now control LLM calls, implement retry logic and user-friendly error messages.
- Optimize prompts: The "Prompt First" approach means your agent's instructions directly impact JSON output. Iterate on system prompts to get reliable widget definitions.
- Keep the old code as reference: While the architecture changed, the core concepts of A2UI remain. Use your old app's behavior to validate the new implementation.
- Check the official documentation for genui and A2UI protocol for advanced usage and examples.
Related Articles
- 7 Essential Insights About Go 1.25's Flight Recorder
- Exploring Python 3.15 Alpha 6: What's New and What's Next?
- Breaking the Clock: How JavaScript's Date Handling Fails and Temporal Comes to the Rescue
- 5 Essential Facts About Python's LEGB Rule and Scope Resolution
- 8 Hal yang Wajib Developer Indonesia Tahu Tentang TestSprite untuk AI Testing Lokal
- Supply Chain Attacks Now Target Developer Secrets: Three Major Campaigns in 48 Hours
- Multi-Agent AI Coordination: Intuit Engineers Claim It's the Toughest Scaling Problem in Tech
- PowerToys Grab and Move: The Window Management Feature I Never Knew I Needed