Flutter GenUI: A Complete Tutorial for Building AI-Powered Interfaces
Move beyond chatbot text. Learn how to build Flutter experiences where AI can compose interactive interfaces from an approved catalog of widgets.
Article type: Technical tutorial | Last reviewed: October 2026
Artificial intelligence is changing how developers design mobile applications. Traditional applications rely on predefined screens and navigation flows. AI-powered applications can go further by adapting the interface to a user's request.
Imagine asking a travel assistant to plan a weekend trip. Instead of receiving a long paragraph, the user sees destination cards, date selectors, budget controls, and action buttons. The interface becomes part of the conversation.
This approach is known as Generative UI (GenUI). In this tutorial, we will explore the Flutter GenUI SDK, understand the A2UI protocol, examine the application architecture, and learn how to connect an AI model to Flutter's widget-rendering system.
What you will learn
- What Generative UI means and when to use it.
- How the Flutter GenUI SDK and A2UI work together.
- How to configure a Flutter project with Firebase AI Logic.
- How catalogs, surfaces, conversations, and data models fit together.
- How to connect an AI response to the GenUI transport layer.
- How to manage generated surfaces and user interactions.
- Security, performance, testing, and production considerations.
Table of Contents
- What is Flutter GenUI?
- Traditional UI vs Generative UI
- Understanding the architecture
- Core GenUI concepts
- Set up the Flutter project
- Configure Firebase AI Logic
- Implement the GenUI integration
- Render generated UI surfaces
- Create a custom widget catalog
- Handle user interactions
- Production architecture and security
- Performance and testing
- Troubleshooting
- Frequently asked questions
1. What Is Flutter GenUI?
Flutter GenUI is an SDK for building interactive, AI-driven interfaces in Flutter applications. Instead of displaying only a text response from a language model, an application can render structured UI content using a catalog of widgets that the developer has made available.
The AI does not directly write and execute arbitrary Flutter code on the device. Instead, it produces structured descriptions of UI components. The GenUI renderer interprets those descriptions and maps them to the application's supported widgets.
Example: An AI travel assistant
USER REQUEST
Plan a three-day trip with a budget of $800.
AI-GENERATED INTERFACE
Your Weekend Plan
Illustrative interface only; this is not a live GenUI widget.
This pattern is useful for AI assistants, shopping recommendations, task planners, onboarding flows, travel planners, and other experiences where interactive controls are more useful than plain text.
2. Traditional Flutter UI vs Generative UI
| Feature | Traditional UI | Generative UI |
|---|---|---|
| Interface structure | Primarily predefined by developers | Composed dynamically from approved components |
| Personalization | Usually through predefined branches | Can adapt component choices to user intent |
| Interaction | Buttons, forms, and flows designed in advance | AI-selected controls can participate in a conversation |
| Predictability | Generally high | Requires schema validation and guardrails |
| Best fit | Stable workflows and core navigation | Adaptive, conversational, AI-assisted experiences |
Generative UI does not replace every conventional Flutter screen. A strong application often combines stable navigation and critical workflows with AI-generated surfaces where flexibility provides real value.
3. Understanding the Flutter GenUI Architecture
Before writing code, it is important to understand the data flow between the user, the AI model, and the Flutter widget tree.
Natural-language request or UI action
Coordinates the conversation lifecycle
Chooses suitable components and produces structured output
Parses the AI response into UI messages
Manages surfaces, component state, and updates
Renders the approved interactive widgets
Conceptual architecture. Exact integration depends on the selected agent provider.
When the user interacts with a generated control, GenUI can update the relevant data model and pass interaction information back into the conversation. The agent can then respond with another UI update.
This creates a loop: user input → AI response → structured UI → user interaction → updated context.
4. Core Concepts You Need to Know
Catalog
Defines which components the AI is allowed to use, their data schemas, and the Flutter builders that render them.
Conversation
Coordinates messages and the interaction between the model, transport, and generated UI.
SurfaceController
Processes UI messages and manages the state and lifecycle of generated surfaces.
A2UI
An open protocol for representing and exchanging structured UI descriptions between agents and UI renderers.
DataModel
Stores dynamic UI data and supports bindings that update widgets when their associated values change.
Surface
A Flutter widget used to display the UI associated with a surface context.
5. Set Up Your Flutter Project
This example uses Firebase AI Logic as the model provider. It is one supported integration option; a server-side A2UI agent is another option for applications with a backend architecture.
Step 1: Create a Flutter project
flutter create genui_travel_demo
cd genui_travel_demo
Step 2: Add the dependencies
flutter pub add firebase_core firebase_ai genui json_schema_builder
The example uses json_schema_builder for the later custom-catalog section. If you only need the basic catalog, you may not need that package immediately.
Step 3: Configure Firebase
- Create a project in the Firebase Console.
- Install and configure the FlutterFire CLI using the official Firebase instructions.
- Run
flutterfire configurefrom your project directory. - Register the platforms you intend to support and verify the generated
firebase_options.dartfile. - Enable Firebase AI Logic and configure the chosen Gemini API provider in the Firebase Console.
Follow the current Firebase setup guide for your selected platform and AI provider. The setup requirements can change over time.
6. Initialize Firebase AI Logic
Initialize Firebase before creating your application. The following example shows the essential startup structure after running FlutterFire configuration.
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.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: 'Flutter GenUI Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
),
useMaterial3: true,
),
home: const GenUIHomePage(),
);
}
}
The GenUIHomePage screen is implemented in the next section. Keep initialization separate from the screen's UI logic so the project remains easy to maintain.
7. Implement the GenUI Integration
At the heart of the integration are three objects: a Catalog, a SurfaceController, and an A2uiTransportAdapter. The Conversation connects the controller and transport.
Step 1: Import the packages
import 'package:firebase_ai/firebase_ai.dart';
import 'package:genui/genui.dart' hide TextPart;
import 'package:genui/genui.dart' as genui;
The separate genui import prefix avoids a naming collision between the TextPart types exported by the AI SDK and GenUI.
Step 2: Create the catalog and controllers
late final SurfaceController controller;
late final A2uiTransportAdapter transport;
late final Conversation conversation;
late final Catalog catalog;
void initializeGenUI() {
catalog = BasicCatalogItems.asCatalog();
controller = SurfaceController(
catalogs: [catalog],
);
transport = A2uiTransportAdapter(
onSend: sendAndReceive,
);
conversation = Conversation(
controller: controller,
transport: transport,
);
}
This is the core wiring pattern. The sendAndReceive function is the bridge to your model provider, which we implement next. Place these fields and methods inside a stateful screen or a dedicated service that manages its lifecycle.
Step 3: Create a system prompt
The model needs clear instructions about the task and the catalog of widgets it can use. PromptBuilder can construct a prompt containing the catalog schema and your application-specific instructions.
const systemInstruction = '''
You are a travel planning assistant.
Help users create a practical travel itinerary.
Use the available UI components when a structured
form, selection, or interactive result is useful.
Keep labels concise and provide helpful explanations.
Never invent confirmed bookings or prices.
Ask for missing details when necessary.
''';
String buildSystemPrompt(Catalog catalog) {
final promptBuilder = PromptBuilder.chat(
catalog: catalog,
systemPromptFragments: [systemInstruction],
);
return promptBuilder.systemPromptJoined();
}
In your screen initialization, create the prompt and send it through the conversation:
conversation.sendRequest(
ChatMessage.system(
buildSystemPrompt(catalog),
),
);
Use a prompt tailored to your application's actual purpose. Prompt instructions guide the model, but they do not replace schema validation, authorization, or application-side safety checks.
Step 4: Connect the model to the transport adapter
The following illustrates the integration pattern with Firebase AI Logic. The response text is passed to GenUI's parser, which processes structured A2UI messages. For production use, adapt this to the current SDK version and handle streaming, cancellation, exceptions, and conversation history as needed.
late final GenerativeModel model;
late final ChatSession chatSession;
void initializeModel(String systemPrompt) {
model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.5-flash',
systemInstruction: Content.system(systemPrompt),
);
chatSession = model.startChat();
}
Future<void> sendAndReceive(ChatMessage message) async {
final buffer = StringBuffer();
for (final part in message.parts) {
if (part.isUiInteractionPart) {
buffer.write(
part.asUiInteractionPart!.interaction,
);
} else if (part is genui.TextPart) {
buffer.write(part.text);
}
}
if (buffer.isEmpty) return;
final response = await chatSession.sendMessage(
Content.text(buffer.toString()),
);
final text = response.text;
if (text != null && text.isNotEmpty) {
transport.addChunk(text);
}
}
Important: This is an integration example, not a complete standalone application. It assumes the fields and initialization shown in the preceding sections have been wired into the same lifecycle. The model name, Firebase APIs, transport behavior, and streaming support must be checked against the current versions installed in your project. For a streaming response, process response chunks and pass them to transport.addChunk() as they arrive.
8. Render AI-Generated UI Surfaces
GenUI represents generated interfaces as surfaces. Your application listens for surface lifecycle events and creates a Flutter Surface widget for each active surface.
Track surface events
final surfaceIds = <String>[];
void listenToConversation() {
conversation.events.listen((event) {
if (!mounted) return;
setState(() {
switch (event) {
case ConversationSurfaceAdded added:
surfaceIds.add(added.surfaceId);
case ConversationSurfaceRemoved removed:
surfaceIds.remove(removed.surfaceId);
case ConversationContentReceived content:
debugPrint('AI response: ${content.text}');
case ConversationError error:
debugPrint('GenUI error: ${error.error}');
default:
break;
}
});
});
}
In a real implementation, keep a reference to the stream subscription and cancel it in dispose(). If you display text responses in a chat history, store them in a message model rather than only printing them.
Display the generated surfaces
ListView.builder(
itemCount: surfaceIds.length,
itemBuilder: (context, index) {
final surfaceId = surfaceIds[index];
return Surface(
surfaceContext: controller.contextFor(surfaceId),
);
},
)
When the controller processes valid UI messages, the associated surface can update. The rendered components are selected from the catalog available to the application.
Send a user message
Future<void> sendUserMessage(String text) async {
if (text.trim().isEmpty) return;
await conversation.sendRequest(
ChatMessage.user(text.trim()),
);
}
This sends the user's request into the GenUI conversation. A complete screen should also manage the input controller, loading state, errors, and disposal of all controllers.
9. Create a Custom Widget Catalog
Built-in components are useful for experimentation. For a branded application, custom catalog items let you define exactly how a product card, task list, travel summary, or other domain-specific component should look and behave.
A custom catalog item generally includes three parts:
- Schema: Describes the allowed component properties and their types.
- Parser: Validates and converts incoming structured data into typed Dart values.
- Builder: Renders the typed data as a Flutter widget.
Example catalog design
Imagine a custom DestinationCard with these permitted fields:
title— destination name.description— a short summary.priceLabel— display text for an estimated price.action— a declared UI action.
Your schema restricts the shape of the data, your parser validates it, and your builder controls the final visual design.
To define custom schemas, add json_schema_builder and use the current CatalogItem APIs described in the official GenUI examples. The exact constructor signatures can change while the SDK is in alpha.
Professional design tip: Build custom components with consistent typography, spacing, colors, accessibility labels, and responsive behavior. This ensures that AI-generated layouts still feel like part of your application.
10. Handle User Interactions
Generating a button is only one part of the experience. The application must also define what happens when the user taps it.
For example, a user may select a destination or mark a task as complete. GenUI's event and data-binding mechanisms can communicate the interaction to the agent, which may then update the generated surface.
For sensitive operations such as making a payment, booking travel, deleting data, or changing account settings, the backend must independently validate the request and permissions. Never treat an AI-generated UI event as proof that an action is authorized.
11. Production Architecture and Security
A working demonstration and a production-ready AI application have different requirements. Before releasing a GenUI feature, plan for the following concerns.
Use a controlled widget catalog
Expose only components that make sense for your product. Validate properties, restrict component types, and ensure unknown or malformed data cannot cause unexpected behavior.
Keep business rules outside generated UI
Prices, eligibility, account balances, permissions, and transaction outcomes should come from trusted application services or backend systems. The AI may present the information, but it should not be the authority for business decisions.
Protect credentials and user data
- Use a supported managed AI integration or a secure backend.
- Apply authentication and authorization to backend requests.
- Do not log access tokens, passwords, or sensitive user content.
- Apply data minimization and retention policies to conversation history.
- Configure usage limits, quotas, and abuse protection.
Make AI behavior predictable
Use explicit system instructions, schema validation, sensible fallbacks, and application-side checks. AI output is probabilistic, so your UI must handle missing fields, invalid component data, and unexpected responses gracefully.
12. Performance and Testing
Reduce perceived latency
AI model calls can be slower than normal local widget construction. Avoid waiting for an AI response before displaying the entire application shell. Show a useful loading state, keep stable navigation available, and consider caching suitable non-sensitive generated content.
For advanced use cases, investigate the official Flutter team's guidance on asynchronous A2UI generation and caching. Cached results must be invalidated when the underlying data or user permissions change.
Test the experience at multiple levels
- Unit tests: Validate parsers, schemas, and business logic.
- Widget tests: Verify catalog components, empty states, and accessibility.
- Integration tests: Exercise the conversation and surface lifecycle.
- Failure tests: Simulate timeouts, malformed messages, and provider errors.
- Security tests: Confirm unauthorized actions are rejected by the backend.
13. Common Problems and Troubleshooting
| Problem | What to check |
|---|---|
| Generated UI does not appear | Verify the catalog, prompt instructions, transport wiring, incoming messages, and active surface IDs. |
| The AI returns only plain text | Confirm the model prompt includes the catalog instructions and the selected model/provider supports the required interaction. |
| Custom component fails to render | Check component registration, schema properties, parsing logic, and current package API signatures. |
| AI interaction does not update the UI | Inspect data bindings, event definitions, controller updates, and the agent's response handling. |
| Firebase request fails | Check Firebase initialization, provider setup, permissions, App Check configuration, quotas, and network access. |
| Compilation errors after upgrading | Review the GenUI migration notes, resolve compatible dependencies, and compare code with the current official examples. |
14. Frequently Asked Questions
What is Flutter GenUI?
Flutter GenUI is an SDK for creating dynamic, AI-driven interfaces in Flutter. It renders structured UI descriptions through an approved widget catalog rather than executing arbitrary AI-generated Dart code.
What is the relationship between GenUI and A2UI?
A2UI is the protocol used to describe and exchange UI structures and updates between an agent and a renderer. The Flutter GenUI SDK implements the client-side components needed to process these messages and render supported widgets.
Does GenUI work with Gemini?
Yes. The official getting-started documentation describes using Firebase AI Logic with Gemini. GenUI also supports alternative agent integration patterns, including a server-side A2UI approach.
Can I use my own Flutter widgets?
Yes. You can define a custom catalog with schemas and builders for your application's own components. This allows you to maintain design consistency and control which UI elements the model can request.
Is Flutter GenUI production-ready?
The official package is in alpha and may change. Evaluate its stability, compatibility, security, testing coverage, and migration requirements before using it for critical production workflows.
Does GenUI replace normal Flutter widgets?
No. Conventional widgets remain appropriate for navigation, stable layouts, forms, and core business flows. GenUI is an additional approach for experiences that benefit from AI-driven composition.
Conclusion
Generative UI introduces a different way to design conversational applications. Instead of limiting AI responses to text, developers can let an agent compose structured interfaces from a controlled catalog of Flutter widgets.
The essential building blocks are the catalog, conversation, transport adapter, surface controller, data model, and surface renderer. Together, these components support an interaction loop in which the AI can respond to user intent with more than words.
For a professional implementation, start with a small use case, use approved widgets, validate generated data, protect backend operations, and measure performance before expanding the experience.
Ready to explore Flutter GenUI?
Read the official documentation and build your first AI-powered interface.
Open Official GenUI GuideOfficial Resources
- Flutter GenUI SDK Documentation
- Get Started with GenUI
- GenUI Components and Concepts
- Flutter Blog: Updates to A2UI and GenUI
- Google Codelab: Build a Generative UI App
- GenUI Package on pub.dev
- Firebase AI Logic Documentation
Technical note: This article is an educational overview with integration examples. Package APIs and model availability can change. Verify all code against the current stable Flutter SDK, installed package versions, and official setup guides before deployment.
Comments
Post a Comment