<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ genui - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ genui - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 27 Jun 2026 11:23:21 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/genui/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use GenUI in Flutter to Build Dynamic, AI-Driven Interfaces ]]>
                </title>
                <description>
                    <![CDATA[ In standard app development, the User Interface (UI) is static. You write code for a button, compile it, and it remains a button forever. GenUI flips this model on its head. With GenUI, Google’s Generative UI SDK, your application's interface becomes... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-genui-in-flutter-to-build-dynamic-ai-driven-interfaces/</link>
                <guid isPermaLink="false">694aca4b18de35b28c2daacb</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ genui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter-aware ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Tue, 23 Dec 2025 16:58:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766509116517/64c3ad0a-9328-4731-8292-90cc7fdbb60b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In standard app development, the User Interface (UI) is static. You write code for a button, compile it, and it remains a button forever. GenUI flips this model on its head.</p>
<p>With GenUI, Google’s Generative UI SDK, your application's interface becomes dynamic. You don’t hard-code widget trees. Instead, you provide an AI agent, such as Google’s Gemini, with a "kit" of UI components called a Catalog and a goal. The AI then generates the UI in real time, deciding whether to display a slider, a text field, or a complex card based on the user’s needs at that moment.</p>
<p>This guide takes you from zero to a fully functional AI-powered Christmas Card Generator that does more than generate text. It also generates the actual Flutter widgets to display them.</p>
<p>Your Christmas Holiday Card Maker will use Generative UI and AI to create personalized, high-quality Christmas cards instantly. Users provide simple inputs such as the recipient’s name, relationship, and preferred color theme, and the AI dynamically produces a festive, polished card UI complete with heartfelt copy, seasonal styling, and structured layout.</p>
<p>By combining Generative UI’s reactive data model with custom catalog widgets, this project will show you how you can guide AI to produce consistent, production-ready user interfaces rather than loosely assembled components.</p>
<p>It’s important to note that the GenUI package is currently in Alpha and is highly experimental. Because it’s in the early stages of development, here is what you should keep in mind:</p>
<ul>
<li><p><strong>API Stability:</strong> The classes, method signatures, and overall architecture described in this guide are likely to change as the Flutter team gathers feedback from the community.</p>
</li>
<li><p><strong>Safety and Guardrails:</strong> Since the UI is generated by an LLM, there is always a non-zero chance of "hallucinations" where the AI might attempt to use widgets or properties that don't exist in your catalog.</p>
</li>
<li><p><strong>Production Readiness:</strong> While GenUI is incredibly exciting for prototyping and internal tools, it requires robust error handling and fallback UIs to ensure a seamless user experience if the AI service is unavailable or returns an invalid structure.</p>
</li>
</ul>
<p>As you work through this guide, GenUI should be understood as a collaborative system rather than an autonomous one. You’re still responsible for defining the Catalog the AI can use, reviewing how those components are assembled, and testing the resulting interface in real scenarios.</p>
<p>This guide demonstrates GenUI in a guided setup, where Flutter provides structure and constraints, and the AI operates within them to dynamically assemble UI. The goal is not to remove developer judgment, but to shift it from hand-writing widget trees to designing, shaping, and validating the system that produces them.</p>
<h3 id="heading-table-of-contents"><strong>Table of Contents</strong></h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-mental-model-how-genui-thinks">The Mental Model: How GenUI Thinks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mapping-genui-components-to-the-christmas-card-app">Mapping GenUI Components to the Christmas Card App</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-genuiconversation-in-the-christmas-card-app">1. GenUiConversation in the Christmas Card App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-catalog-as-the-design-constraint">2. Catalog as the Design Constraint</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-datamodel-as-the-heart-of-personalization">3. DataModel as the Heart of Personalization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-contentgenerator-as-the-ai-gateway">4. ContentGenerator as the AI Gateway</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-a2uimessage-as-intent-not-ui">5. A2uiMessage as Intent, Not UI</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-architecture-works">Why This Architecture Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-overview-what-were-building">Project Overview: What We’re Building</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-structure">Project Structure</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-create-a-new-flutter-project">Step 1: Create a New Flutter Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-configure-your-agent-provider">Step 2: Configure Your Agent Provider</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-add-dependencies">Step 3: Add Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-get-a-google-gemini-api-key">Step 4: Get a Google Gemini API Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-app-entry-point-maindart">Step 5: App Entry Point (main.dart)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-root-app-widget">The Root App Widget</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-the-logic-controller-stateful-screen">Step 6: The Logic Controller (Stateful Screen)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-7-initializing-genui-and-firebase">Step 7: Initializing GenUI and Firebase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-8-sending-a-dynamic-prompt-to-the-ai">Step: 8 Sending a Dynamic Prompt to the AI</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-view">Building the View</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-folder-libscreendata">Folder: lib/screen/data/</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-folder-libextensions">Folder: lib/extensions/</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-folder-libscreencomponents">Folder: lib/screen/components/</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-your-own-widgets-to-the-genui-catalog">Adding Your Own Widgets to the GenUI Catalog</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-add-a-custom-widget">Why Add a Custom Widget?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-adding-jsonschemabuilder">Step 1: Adding json_schema_builder</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-defining-the-holiday-card-schema">Step 2: Defining the Holiday Card Schema</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-creating-the-catalogitem">Step 3: Creating the CatalogItem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-registering-the-widget-in-your-app">Step 4: Registering the Widget in Your App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-teaching-the-ai-to-use-the-widget">Step 5: Teaching the AI to Use the Widget</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-this-fits-into-your-existing-screen">How This Fits into Your Existing Screen</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-screenshots">Screenshots:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow this guide effectively, you need:</p>
<ol>
<li><p><strong>Flutter Development Environment:</strong> Flutter SDK installed (stable channel recommended) and an IDE like VS Code or Android Studio configured.</p>
</li>
<li><p><strong>Basic Flutter knowledge:</strong> You should understand how Widgets compose (Rows, Columns, Containers) and basic state management (<code>setState</code> or <code>FutureBuilder</code>).</p>
</li>
<li><p><strong>Google AI Studio API key:</strong> We will be using Google's Gemini model. You’ll need to get a free API key from <a target="_blank" href="https://aistudio.google.com/">Google AI Studio</a>.</p>
</li>
</ol>
<h2 id="heading-the-mental-model-how-genui-thinks">The Mental Model: How GenUI Thinks</h2>
<p>Before writing any code, it’s important to understand how GenUI <em>conceptually</em> sees your app. GenUI doesn’t think in terms of widget trees or screens. It thinks in terms of <strong>surfaces</strong>, <strong>state</strong>, and <strong>conversations</strong>.</p>
<p>A surface is simply a place where AI-generated UI can appear. A conversation controls how those surfaces evolve over time. The data model holds the truth, and messages move everything forward.</p>
<p>Here’s the full flow in one pass:</p>
<pre><code class="lang-dart">User Action
   |
   v
GenUiConversation
   |
   v
ContentGenerator (AI)
   |
   v
A2uiMessage stream
   |
   v
GenUiManager
   |
   v
DataModel + UI Surfaces
   |
   v
GenUiSurface (Flutter rebuild)
</code></pre>
<p>Nothing in this flow bypasses Flutter. GenUI does not render UI “outside” Flutter – it only decides <strong>what Flutter should render</strong>.</p>
<h2 id="heading-mapping-genui-components-to-the-christmas-card-app">Mapping GenUI Components to the Christmas Card App</h2>
<p>Now let’s ground this in the Christmas card generator we’ll be building. This is where GenUI really clicks.</p>
<h3 id="heading-1-genuiconversation-in-the-christmas-card-app">1. GenUiConversation in the Christmas Card App</h3>
<p>In the project we’ll be building, <code>GenUiConversation</code> represents the ongoing interaction between the user and the Christmas card generator.</p>
<p>When the user types a loved one’s name, selects a relationship, chooses a color, and taps <strong>Generate Card</strong>, your app sends that prompt through <code>GenUiConversation</code>.</p>
<p>At that moment, <code>GenUiConversation</code> already knows the conversation history. It knows whether this is the first card being generated or whether the user is regenerating a card with a different message. This context is what allows the AI to create <strong>unique cards for each person</strong> instead of repeating generic output.</p>
<p>Without <code>GenUiConversation</code>, every request would be stateless. With it, the app feels intentional and personal.</p>
<h3 id="heading-2-catalog-as-the-design-constraint">2. Catalog as the Design Constraint</h3>
<p>In the Christmas card app, the <code>Catalog</code> defines the visual language of your cards.</p>
<p>You might allow the AI to use text widgets for greetings, image widgets for festive backgrounds, container widgets for layout, and buttons for regeneration or sharing. What matters is that the AI cannot escape these constraints.</p>
<p>This is how you ensure that:</p>
<ul>
<li><p>Cards always look like cards</p>
</li>
<li><p>The AI does not invent unsupported UI</p>
</li>
<li><p>Your app remains visually consistent</p>
</li>
</ul>
<p>From the AI’s perspective, the catalog is the only toolbox it’s allowed to reach into. From your perspective, it’s the safety net that keeps the UI Flutter-native and predictable.</p>
<h3 id="heading-3-datamodel-as-the-heart-of-personalization">3. DataModel as the Heart of Personalization</h3>
<p>The <code>DataModel</code> is where personalization actually lives.</p>
<p>In the project we’ll be building, values like the recipient’s name, the greeting message, the card theme, or even animation flags live in the data model. When the user edits the name or regenerates the card, only the parts of the UI bound to those values change.</p>
<p>This is why GenUI feels dynamic without being inefficient. You aren’t rebuilding the entire card screen – You’re only updating what depends on the changed data.</p>
<p>This also means the AI doesn’t need to recreate the whole UI every time. It can simply update the data model and let Flutter do what it does best.</p>
<h3 id="heading-4-contentgenerator-as-the-ai-gateway">4. ContentGenerator as the AI Gateway</h3>
<p>The <code>ContentGenerator</code> is the only part of your app that knows how to talk to the AI.</p>
<p>In the Christmas card example, this component sends the user’s request to the model along with system instructions like “Generate a festive Christmas card UI using the available widgets.” It then listens as the AI responds.</p>
<p>Because the responses arrive as streams, the UI can begin rendering as soon as the first instructions arrive. This is especially useful if you later add animations or progressive reveals to your cards.</p>
<p>From a design standpoint, this separation is critical. Your Flutter app never depends directly on the AI SDK. It depends on GenUI, and GenUI depends on the ContentGenerator.</p>
<h3 id="heading-5-a2uimessage-as-intent-not-ui">5. A2uiMessage as Intent, Not UI</h3>
<p>This is one of the most important concepts to internalize: when the AI decides to generate a Christmas card, it doesn’t send Flutter widgets. Rather, it sends <code>A2uiMessage</code> instructions.</p>
<p>One message might say “start rendering a new surface.” Another might say “update the greeting text in the data model.” Another might say “replace the background image.”</p>
<p>These messages are processed by the <code>GenUiManager</code>, which translates intent into actual UI changes. This extra layer is what prevents GenUI from becoming fragile or unpredictable.</p>
<h2 id="heading-why-this-architecture-works">Why This Architecture Works</h2>
<p>What makes GenUI powerful is not that it uses AI. Plenty of tools do that. What makes it powerful is that <strong>AI never breaks Flutter’s rules</strong>, because the state is centralized, rendering is controlled, events are explicit, and updates are incremental.</p>
<p>In the Christmas card app, this means every card feels custom, every interaction feels responsive, and your app remains maintainable even as the AI logic grows more complex.</p>
<p>Once you understand this flow, you stop thinking of GenUI as “AI generating UI” and start thinking of it as <strong>AI participating in your app’s state machine</strong>.</p>
<h2 id="heading-project-overview-what-were-building">Project Overview: What We’re Building</h2>
<p>In this tutorial, we’ll build a Christmas Card Generator using Flutter and GenUI. The idea is simple but intuitive: a user types a name, selects a relationship and a card color description, and the AI dynamically generates a Flutter widget tree that represents a personalized Christmas card.</p>
<p>This project demonstrates three core GenUI ideas working together: the conversation loop, AI-driven UI rendering, and reactive state updates without manual widget wiring.</p>
<p>By the end, you’ll understand not just how to use GenUI, but how to structure a real Flutter app around it.</p>
<h2 id="heading-project-structure">Project Structure</h2>
<p>We’ll keep the structure intentionally simple so it’s easy to follow and extend later.</p>
<pre><code class="lang-dart">lib/
 ├── extensions/
 │    ├── loading.dart
 ├── screen/
 │    ├── components/
 │    │    ├── color_picker_list.dart       <span class="hljs-comment">// Widget for color selection</span>
 │    │    ├── custom_input_section.dart    <span class="hljs-comment">// Input form fields</span>
 │    │    ├── error_section.dart           <span class="hljs-comment">// Error message display</span>
 │    │   
 │    ├── data/
 │    │    └── static_list_data.dart        <span class="hljs-comment">// Hardcoded data or constants</span>
 │    ├── card_generator_screen.dart        <span class="hljs-comment">// Main UI logic for generating cards</span>
 │    └── christmas_card.dart               <span class="hljs-comment">// The specific card widget/view</span>
 ├── firebase_options.dart                  <span class="hljs-comment">// Firebase configuration file</span>
 └── main.dart                              <span class="hljs-comment">// App entry point</span>
</code></pre>
<h3 id="heading-step-1-create-a-new-flutter-project">Step 1: Create a New Flutter Project</h3>
<p>Start by creating a fresh Flutter app.</p>
<pre><code class="lang-bash">flutter create genui_christmas_card
<span class="hljs-built_in">cd</span> genui_christmas_card
</code></pre>
<p>This gives us a clean baseline with Material 3 support and proper platform setup.</p>
<h3 id="heading-step-2-configure-your-agent-provider">Step 2: Configure Your Agent Provider</h3>
<p><code>genui</code> can connect to a variety of agent providers. Choose the section below for your preferred provider.</p>
<h4 id="heading-configure-firebase-ai-logic">Configure Firebase AI Logic</h4>
<p>To use the built-in <code>FirebaseAiContentGenerator</code> to connect to Gemini via Firebase AI Logic, follow these instructions:</p>
<ol>
<li><p>Create a new <a target="_blank" href="https://support.google.com/appsheet/answer/10104995">Firebase project</a> using the Firebase Console.</p>
</li>
<li><p><a target="_blank" href="https://firebase.google.com/docs/gemini-in-firebase/set-up-gemini">Enable the Gemini API</a> for that pro<a target="_blank" href="https://pub.dev/packages/genui#2-configure-your-agent-provider">j</a>ect.</p>
</li>
<li><p>Follow the first three steps in <a target="_blank" href="https://firebase.google.com/docs/flutter/setup">Firebase's Flutter Setup</a> to add Firebase to your app.</p>
</li>
<li><p>Enable <strong>Gemini Developer API</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766152091749/500feb24-bdb5-4126-a05e-287a945c0ed9.png" alt="Firebase Dashboard" class="image--center mx-auto" width="3578" height="1726" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-step-3-add-dependencies">Step 3: Add Dependencies</h3>
<p>GenUI is modular. You always install the core framework, then add a content generator that knows how to talk to your AI provider.</p>
<p>Open <code>pubspec.yaml</code> and update your dependencies:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>

  <span class="hljs-attr">genui:</span> <span class="hljs-string">^0.6.0</span>
  <span class="hljs-attr">logging:</span> <span class="hljs-string">^1.2.0</span>
  <span class="hljs-attr">genui_firebase_ai:</span> <span class="hljs-string">^0.6.0</span>
  <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^4.3.0</span>
  <span class="hljs-attr">loader_overlay:</span> <span class="hljs-string">^5.0.0</span>
  <span class="hljs-attr">flutter_spinkit:</span> <span class="hljs-string">^5.2.2</span>
</code></pre>
<p>Then fetch the packages:</p>
<pre><code class="lang-bash">flutter pub get
</code></pre>
<p>At this point, your project has everything it needs to generate UI dynamically.</p>
<h3 id="heading-step-4-get-a-google-gemini-api-key">Step 4: Get a Google Gemini API Key</h3>
<p>GenUI itself does not provide AI models. You’ll need to connect one. To do this, go to Google AI Studio, create a new API key, and copy it.</p>
<p>Important note: For real production apps, never hard-code API keys. Use <code>--dart-define</code>, environment variables, or a backend proxy.</p>
<h3 id="heading-step-5-app-entry-point-maindart">Step 5: App Entry Point (<code>main.dart</code>)</h3>
<p>Now we’ll begin writing real code.</p>
<p>Replace the contents of <code>lib/main.dart</code> with the following:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:genui_flutter/screen/christmas_card.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:logging/logging.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;

<span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span>{
  <span class="hljs-comment">// Enable verbose logging so we can see exactly</span>
  <span class="hljs-comment">// what the AI sends back to GenUI.</span>
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    debugPrint(
      <span class="hljs-string">'<span class="hljs-subst">${record.level.name}</span>: <span class="hljs-subst">${record.time}</span>: <span class="hljs-subst">${record.message}</span>'</span>,
    );
  });

    WidgetsFlutterBinding.ensureInitialized();
    <span class="hljs-keyword">await</span> Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
    runApp(<span class="hljs-keyword">const</span> ChristmasCardApp());
}
</code></pre>
<p>This logging setup is optional, but highly recommended. When something goes wrong, logs are often the fastest way to understand why the AI didn’t generate what you expected.</p>
<h3 id="heading-the-root-app-widget">The Root App Widget</h3>
<p>Next, we define the root widget for our app.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:loader_overlay/loader_overlay.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'card_generator_screen.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_spinkit/flutter_spinkit.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChristmasCardApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> ChristmasCardApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Directionality(
      textDirection: TextDirection.ltr,
      child: LoaderOverlay(
        overlayWholeScreen: <span class="hljs-keyword">true</span>,
        overlayWidgetBuilder: (_) {
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Center(
            child: SpinKitWaveSpinner(color: Colors.red, size: <span class="hljs-number">50.0</span>),
          );
        },
        child: MaterialApp(
          title: <span class="hljs-string">'GenUI Christmas Card Generator'</span>,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(
              seedColor: Colors.red,
              primary: Colors.red,
            ),
            useMaterial3: <span class="hljs-keyword">true</span>,
          ),
          home: <span class="hljs-keyword">const</span> CardGeneratorScreen(),
        ),
      ),
    );
  }
}
</code></pre>
<p>This is standard Flutter – nothing GenUI-specific yet. The real work happens inside <code>CardGeneratorScreen</code>.</p>
<h3 id="heading-step-6-the-logic-controller-stateful-screen">Step 6: The Logic Controller (Stateful Screen)</h3>
<p>This screen is where we wire together Flutter, Firebase AI, and the GenUI logic. It handles the user inputs (Name, Relationship, Color) and orchestrates the AI generation.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CardGeneratorScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> CardGeneratorScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;CardGeneratorScreen&gt; createState() =&gt; _CardGeneratorScreenState();
}
</code></pre>
<p>Now the state class, which holds all GenUI logic and form state:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_CardGeneratorScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">CardGeneratorScreen</span>&gt; </span>{
  <span class="hljs-comment">// 1. Form State Management</span>
  <span class="hljs-keyword">final</span> TextEditingController nameController = TextEditingController();
  <span class="hljs-built_in">String</span> selectedRelationship = <span class="hljs-string">'Friend'</span>;
  <span class="hljs-built_in">String</span> selectedColorName = <span class="hljs-string">'Gold'</span>;
  Color selectedColorUi = Colors.amber;

  <span class="hljs-comment">// 2. GenUI Core Components</span>
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> A2uiMessageProcessor _a2uiMessageProcessor;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> FirebaseAiContentGenerator _contentGenerator;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> GenUiConversation _conversation;

  <span class="hljs-comment">// 3. UI State</span>
  <span class="hljs-built_in">String?</span> currentSurfaceId;
  <span class="hljs-built_in">String?</span> errorMessage;
</code></pre>
<p>The application manages user inputs through a form state that allows for dynamic prompt injection, while the <code>_a2uiMessageProcessor</code> acts as a decoder to convert raw AI data into specific Flutter widgets.</p>
<p>The backend connection is handled by the <code>FirebaseAiContentGenerator</code>, which manages system instructions and tool catalogs, while the <code>_conversation</code> object serves as a conductor to manage chat history and route data between the AI and the UI.</p>
<p>Finally, the <code>currentSurfaceId</code> tracks the specific widget tree being displayed, ensuring the <code>GenUiSurface</code> renders the correct AI-generated content.</p>
<h3 id="heading-step-7-initializing-genui-and-firebase">Step 7: Initializing GenUI and Firebase</h3>
<p>All setup happens in <code>initState</code>:</p>
<pre><code class="lang-dart">  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    <span class="hljs-comment">// 1. Setup the Processor with allowed widgets</span>
    _a2uiMessageProcessor = A2uiMessageProcessor(
      catalogs: [CoreCatalogItems.asCatalog()],
    );

    <span class="hljs-comment">// 2. Configure the AI personality and rules</span>
     _contentGenerator = FirebaseAiContentGenerator(
      catalog: CoreCatalogItems.asCatalog(),
      systemInstruction: <span class="hljs-string">'''
          You are an expert Festive UI Designer and Holiday Copywriter.

          YOUR GOAL: Generate a high-end, visually appealing Christmas card using the `surfaceUpdate` tool, suitable for printing or digital sharing. The card should feel personalized, warm, and festive.

          DESIGN GUIDELINES:
          - Layout: Use a vertical Column inside a Container with rounded corners, generous padding, and a border. Fill the Container with a color that **mixes Red with <span class="hljs-subst">$selectedColorName</span> ** to create a rich, holiday-themed background.
          - Typography: Use distinct font weights (Bold for headers, normal for body). Center all text.
          - Visuals: Include seasonal icons (🎄, ✨, ❄️) as decorative elements. Place a Christmas tree emoji strategically without overcrowding the layout.
          - Personalization: Display the recipient's name prominently in the middle of the card in a visually striking way.

          COPYWRITING GUIDELINES:
          - Create a deeply personal, heartfelt holiday message (3-4 sentences) that matches the relationship type (fun for friends, romantic for spouse, warm for family).
          - Include a proper closing/signature.
          - NEVER use placeholders. Always generate the **final text ready to display**.

          OUTPUT INSTRUCTIONS:
          - Use the `surfaceUpdate` tool to construct the UI.
          - Ensure all elements (Container, text, emojis) are visually aligned and harmonious.
          - The card must feel festive, elegant, and balanced.
          '''</span>,
    );

    <span class="hljs-comment">// 3. Start the conversation and listen for updates</span>
    _conversation = GenUiConversation(
      contentGenerator: _contentGenerator,
      a2uiMessageProcessor: _a2uiMessageProcessor,
      onSurfaceAdded: _onSurfaceAdded,
      onSurfaceDeleted: _onSurfaceDeleted,
    );
  }

  <span class="hljs-keyword">void</span> _onSurfaceAdded(SurfaceAdded update) {
    setState(() {
      currentSurfaceId = update.surfaceId;
    });
  }
</code></pre>
<p>In the <code>initState</code> method, we first configure the <code>A2uiMessageProcessor</code> with <code>CoreCatalogItems</code>, giving the AI access to standard widgets. Then, we initialize <code>FirebaseAiContentGenerator</code>.</p>
<p>Notice the <code>systemInstruction</code>: you are giving the AI two distinct roles here; "UI Designer" and "Copywriter." You explicitly tell it to write specific content based on relationships and design centered text.</p>
<p>Finally, we link them in <code>GenUiConversation</code> and attach a listener (<code>_onSurfaceAdded</code>). When the AI creates a new UI, we update <code>currentSurfaceId</code> inside <code>setState</code>, which tells Flutter to draw the new card.</p>
<h3 id="heading-step-8-sending-a-dynamic-prompt-to-the-ai">Step: 8 Sending a Dynamic Prompt to the AI</h3>
<p>This method kicks off the generation, using the user's form data to build a specific prompt.</p>
<pre><code class="lang-dart">  Future&lt;<span class="hljs-keyword">void</span>&gt; generateCard() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">if</span> (nameController.text.trim().isEmpty) {
      setState(() {
        errorMessage = <span class="hljs-string">"Please enter a name first!"</span>;
      });
      <span class="hljs-keyword">return</span>;
    }
    FocusScope.of(context).unfocus();
    setState(() {
      errorMessage = <span class="hljs-keyword">null</span>;
      currentSurfaceId = <span class="hljs-keyword">null</span>;
    });

    <span class="hljs-keyword">try</span> {
      context.showLoader();
       <span class="hljs-keyword">final</span> prompt = <span class="hljs-string">'''
        Create a personalized Christmas card for my <span class="hljs-subst">$selectedRelationship</span>, <span class="hljs-subst">${nameController.text}</span>.
        Theme: Blend Red and <span class="hljs-subst">$selectedColorName</span> for a festive background.
        Layout: Vertical Column in a rounded Container with padding and border; place the recipient's name prominently in the center.
        Visuals: Add Christmas trees (🎄), sparkles (✨), or snowflakes (❄️) where appropriate.
        Typography: Bold headers, normal body text, all centered.
        Message: Write a warm, personal 3-4 sentence holiday greeting that fits the relationship type, ending with a proper signature.
        Design: Make it look like an elegant, festive Christmas card ready to display or share.
        '''</span>;


      <span class="hljs-keyword">await</span> _conversation.sendRequest(UserMessage.text(prompt));
    } <span class="hljs-keyword">catch</span> (e) {
      debugPrint(<span class="hljs-string">'Error: <span class="hljs-subst">$e</span>'</span>);
      <span class="hljs-keyword">if</span> (mounted) {
        setState(() {
          errorMessage = <span class="hljs-string">"Oops! Failed to create card.\nError: <span class="hljs-subst">$e</span>"</span>;
        });
      }
    } <span class="hljs-keyword">finally</span> {
      <span class="hljs-keyword">if</span> (mounted) {
        context.hideLoader();
      }
    }
  }
</code></pre>
<p>The <code>generateCard</code> method is where prompt engineering meets code. First, it validates that a name exists. Then, it constructs a multi-line string using String Interpolation (<code>$selectedRelationship</code>, <code>$selectedColorName</code>). Instead of a generic request, you are sending a detailed brief: "Make a card for my Mom named Alice using Gold colors."</p>
<p>Finally, <code>_conversation.sendRequest</code> fires this prompt to Firebase. We wrap this in a try/catch block to handle network errors gracefully by showing the error message in the UI.</p>
<h2 id="heading-building-the-view">Building the View</h2>
<p>Now we’ll render the complex UI using the helper components we created in the <code>components/</code> folder. Here’s the code – but don’t worry, we’ll cover every custom component individually after this.</p>
<pre><code class="lang-dart">  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'🎄 Holiday Card Maker'</span>)...),
      body: Stack(
        children: [
          Column(
            children: [
              <span class="hljs-comment">// 1. The Input Form (Refactored into a component)</span>
              CustomInputSection(
                nameController: nameController,
                selectedRelationship: selectedRelationship,
                selectedColorName: selectedColorName,
                selectedColorUi: selectedColorUi,
                onColorSelected: onColorSelected,
                generateCard: generateCard,
                selectRelationship: selectRelationship,
              ),

              <span class="hljs-keyword">const</span> Divider(height: <span class="hljs-number">1</span>),

              <span class="hljs-comment">// 2. The GenUI Drawing Area</span>
              Expanded(
                child: Container(
                  color: Colors.grey[<span class="hljs-number">100</span>],
                  child: currentSurfaceId != <span class="hljs-keyword">null</span>
                      ? GenUiSurface(
                          host: _conversation.host,
                          surfaceId: currentSurfaceId!,
                        )
                      : <span class="hljs-keyword">const</span> Center(child: Text(<span class="hljs-string">'Fill in details...'</span>)),
                ),
              ),
            ],
          ),


          <span class="hljs-keyword">if</span> (errorMessage != <span class="hljs-keyword">null</span>)
            ErrorSection(errorMessage: errorMessage!, clearError: clearError),
        ],
      ),
    );
  }
}
</code></pre>
<p>In the build method, we use a Stack to allow us to float the <code>LoadingWidget</code> and <code>ErrorSection</code> on top of the main content.</p>
<p>Instead of writing all the input logic here, you used <code>CustomInputSection</code>. This keeps the main screen clean and focused on AI orchestration.</p>
<p>The bottom half of the screen contains the <code>GenUiSurface</code>. If <code>currentSurfaceId</code> exists, it renders the AI's widget tree using <code>_conversation.host</code>. If not, it shows a placeholder instruction.</p>
<p>At this point, you’ve seen the full <code>build()</code> method that renders the screen. Notice that the screen itself does very little visual work directly. Instead, it composes the UI from smaller, focused widgets and helper files. This is intentional.</p>
<p>Rather than cramming form fields, color selectors, error handling, and constants into a single screen file, the UI is split into clear, purpose-driven folders. Each folder represents a <strong>UI concern</strong>, not a state-management layer or architectural pattern.</p>
<p>In the next sections, we’ll walk through these folders one by one, showing how each piece contributes to the final screen you just built. You’ll see where reusable widgets live, where static UI data is defined, and how the main screen ties everything together without becoming cluttered.</p>
<h3 id="heading-folder-libscreendata">Folder: <code>lib/screen/data/</code></h3>
<p>This folder holds the static data used to populate dropdowns and color lists.</p>
<h4 id="heading-staticlistdata-libscreendatastaticlistdatadart">StaticListData: <code>lib/screen/data/static_list_data.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StaticListData</span> </span>{
  <span class="hljs-comment">// List of relationships for the dropdown menu</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; relationships = [
    <span class="hljs-string">'Husband'</span>,
    <span class="hljs-string">'Wife'</span>,
    <span class="hljs-string">'Son'</span>,
    <span class="hljs-string">'Daughter'</span>,
    <span class="hljs-string">'Grandma'</span>,
    <span class="hljs-string">'Grandpa'</span>,
    <span class="hljs-string">'Uncle'</span>,
    <span class="hljs-string">'Aunt'</span>,
    <span class="hljs-string">'Friend'</span>,
    <span class="hljs-string">'Relative'</span>,
    <span class="hljs-string">'Cousin'</span>,
    <span class="hljs-string">'Grandson'</span>,
    <span class="hljs-string">'Granddaughter'</span>,
    <span class="hljs-string">'Mom'</span>,
    <span class="hljs-string">'Dad'</span>,
  ];

  <span class="hljs-comment">// Map of color names to actual Flutter Color objects</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, Color&gt; colorOptions = {
    <span class="hljs-string">'Gold'</span>: Colors.amber,
    <span class="hljs-string">'Green'</span>: Colors.green,
    <span class="hljs-string">'Blue'</span>: Colors.blue,
    <span class="hljs-string">'Purple'</span>: Colors.deepPurple,
    <span class="hljs-string">'Silver'</span>: Colors.grey,
    <span class="hljs-string">'Yellow'</span>: Colors.yellow,
    <span class="hljs-string">'Pink'</span>: Colors.pink,
  };
}
</code></pre>
<p>This class serves as a central repository for constant data, housing the <code>relationships</code> list to allow for easy UI updates, such as adding "Colleague" or "Neighbor", without modifying core code, and the <code>colorOptions</code> map, which translates user-friendly names like "Gold" into functional <code>Color</code> objects like <code>Colors.amber</code> for styling.</p>
<h3 id="heading-folder-libextensions">Folder: <code>lib/extensions/</code></h3>
<p>This folder holds the static data used to populate dropdowns and color lists.</p>
<h4 id="heading-loaderoverlayextension-libextensionsloadingdart">LoaderOverlayExtension: <code>lib/extensions/loading.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:loader_overlay/loader_overlay.dart'</span>;

<span class="hljs-keyword">extension</span> LoaderOverlayExtension <span class="hljs-keyword">on</span> BuildContext {
  <span class="hljs-keyword">void</span> showLoader() {
    loaderOverlay.<span class="hljs-keyword">show</span>();
  }

  <span class="hljs-keyword">void</span> hideLoader() {
    loaderOverlay.<span class="hljs-keyword">hide</span>();
  }
}
</code></pre>
<p>The <code>LoaderOverlayExtension</code> adds two methods to any <code>BuildContext</code> object: <code>showLoader()</code>, which displays a <code>LoaderOverlay</code>, and <code>hideLoader()</code>, which hides it. This allows you to call <code>context.showLoader()</code> or <code>context.hideLoader()</code> anywhere in your widgets without directly referencing <code>loaderOverlay</code> every time, improving readability and reducing boilerplate whenever a loading state needs to be displayed.</p>
<h3 id="heading-folder-libscreencomponents">Folder: <code>lib/screen/components/</code></h3>
<p>This folder contains reusable UI components that are used specifically on screens in your app, particularly the <code>CardGeneratorScreen</code>. These are smaller, modular widgets that encapsulate a part of the UI, making the main screen code cleaner, easier to read, and maintainable.</p>
<h4 id="heading-errorsection-errorsectiondart">ErrorSection: <code>error_section.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ErrorSection</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> errorMessage;
  <span class="hljs-keyword">final</span> VoidCallback clearError;

  <span class="hljs-keyword">const</span> ErrorSection({
    <span class="hljs-keyword">super</span>.key,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.errorMessage,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.clearError,
  });

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Container(
      <span class="hljs-comment">// High opacity background to block out the UI behind it</span>
      color: Colors.white.withOpacity(<span class="hljs-number">0.95</span>),
      child: Center(
        child: Padding(
          padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">32.0</span>),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              <span class="hljs-keyword">const</span> Icon(Icons.error_outline, color: Colors.red, size: <span class="hljs-number">60</span>),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">16</span>),
              <span class="hljs-comment">// Displays the specific error message passed from the parent</span>
              Text(
                errorMessage,
                textAlign: TextAlign.center,
                style: <span class="hljs-keyword">const</span> TextStyle(fontSize: <span class="hljs-number">16</span>, color: Colors.red),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
              <span class="hljs-comment">// Button to dismiss the error</span>
              ElevatedButton(
                onPressed: () {
                  clearError();
                },
                child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Try Again"</span>),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
</code></pre>
<p>This robust error-handling view utilizes a large red icon and descriptive text to clearly signal an issue, while incorporating a <code>clearError</code> callback that triggers when the "Try Again" button is clicked to reset the parent state's <code>errorMessage</code> variable and dismiss the view.</p>
<h4 id="heading-colorpickerlist-colorpickerlistdart">ColorPickerList: <code>color_picker_list.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ColorPickerList</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> ColorPickerList({
    <span class="hljs-keyword">super</span>.key,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> selectedColorName,
    <span class="hljs-keyword">required</span> Color selectedColorUi,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, Color&gt; colorOptions,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.onColorSelected,
  })  : _selectedColorName = selectedColorName,
        _colorOptions = colorOptions;

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> _selectedColorName;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, Color&gt; _colorOptions;
  <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-built_in">Function</span>(<span class="hljs-built_in">String</span> colorName, Color colorUi) onColorSelected;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> SizedBox(
      height: <span class="hljs-number">85</span>,
      <span class="hljs-comment">// Horizontal scrolling list for colors</span>
      child: ListView(
        scrollDirection: Axis.horizontal,
        physics: <span class="hljs-keyword">const</span> BouncingScrollPhysics(),
        children: _colorOptions.entries.map((entry) {
          <span class="hljs-keyword">final</span> isSelected = _selectedColorName == entry.key;

          <span class="hljs-keyword">return</span> GestureDetector(
            onTap: () {
              <span class="hljs-comment">// Pass the selected color back to the parent</span>
              onColorSelected(entry.key, entry.value);
            },
            child: Container(
              margin: <span class="hljs-keyword">const</span> EdgeInsets.only(right: <span class="hljs-number">15</span>),
              width: <span class="hljs-number">50</span>,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  <span class="hljs-comment">// Outer ring animation</span>
                  AnimatedContainer(
                    duration: <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(milliseconds: <span class="hljs-number">250</span>),
                    padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">3</span>),
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      <span class="hljs-comment">// Show border only if selected</span>
                      border: Border.all(
                        color: isSelected ? entry.value : Colors.transparent,
                        width: <span class="hljs-number">2.5</span>,
                      ),
                    ),
                    <span class="hljs-comment">// Inner color circle</span>
                    child: Container(
                      width: <span class="hljs-number">35</span>,
                      height: <span class="hljs-number">35</span>,
                      decoration: BoxDecoration(
                        color: entry.value,
                        shape: BoxShape.circle,
                        boxShadow: [
                          <span class="hljs-keyword">if</span> (isSelected)
                            BoxShadow(
                              color: entry.value.withOpacity(<span class="hljs-number">0.3</span>),
                              blurRadius: <span class="hljs-number">6</span>,
                              offset: <span class="hljs-keyword">const</span> Offset(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>),
                            ),
                        ],
                        border: Border.all(color: Colors.white, width: <span class="hljs-number">2</span>),
                      ),
                    ),
                  ),
                  <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">6</span>),
                  <span class="hljs-comment">// Color name label</span>
                  Text(
                    entry.key,
                    textAlign: TextAlign.center,
                    maxLines: <span class="hljs-number">1</span>,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      fontSize: <span class="hljs-number">10</span>,
                      color: isSelected ? entry.value : Colors.grey[<span class="hljs-number">600</span>],
                      fontWeight:
                          isSelected ? FontWeight.bold : FontWeight.normal,
                    ),
                  ),
                ],
              ),
            ),
          );
        }).toList(),
      ),
    );
  }
}
</code></pre>
<p>This horizontal list of color circles uses a <code>ListView</code> with <code>scrollDirection: Axis.horizontal</code> to allow users to swipe through various options, while an <code>AnimatedContainer</code> provides polished visual feedback by animating the outer border into view over 250ms when a color is tapped.</p>
<p>The widget also incorporates selection logic that checks the <code>isSelected</code> state to determine whether to display bold text and a colored border, clearly indicating the user's current choice.</p>
<h4 id="heading-custominputsection-custominputsectiondart">CustomInputSection <code>custom_input_section.dart</code></h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../data/static_list_data.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'color_picker_list.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomInputSection</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> TextEditingController nameController;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> selectedRelationship;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> selectedColorName;
  <span class="hljs-keyword">final</span> Color selectedColorUi;
  <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-built_in">Function</span>(<span class="hljs-built_in">String</span> colorName, Color colorUi) onColorSelected;
  <span class="hljs-keyword">final</span> VoidCallback generateCard;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Function</span> selectRelationship;

  <span class="hljs-keyword">const</span> CustomInputSection({
    <span class="hljs-keyword">super</span>.key,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.nameController,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.selectedRelationship,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.selectedColorName,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.selectedColorUi,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.onColorSelected,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.generateCard,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.selectRelationship,
  });

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Container(
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(<span class="hljs-number">0.05</span>),
            blurRadius: <span class="hljs-number">10</span>,
            offset: <span class="hljs-keyword">const</span> Offset(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>),
          ),
        ],
      ),
      child: LayoutBuilder(
        builder: (context, constraints) {
          <span class="hljs-built_in">bool</span> isSmallScreen = constraints.maxWidth &lt; <span class="hljs-number">600</span>;

          <span class="hljs-keyword">return</span> Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: <span class="hljs-keyword">const</span> EdgeInsets.symmetric(horizontal: <span class="hljs-number">18.0</span>,vertical: <span class="hljs-number">20</span>),
                child: Flex(
                  direction: isSmallScreen ? Axis.vertical : Axis.horizontal,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Expanded(
                      flex: isSmallScreen ? <span class="hljs-number">0</span> : <span class="hljs-number">3</span>,
                      child: SizedBox(
                        width: isSmallScreen ? <span class="hljs-built_in">double</span>.infinity : <span class="hljs-keyword">null</span>,
                        child: TextField(
                          controller: nameController,
                          decoration: <span class="hljs-keyword">const</span> InputDecoration(
                            labelText: <span class="hljs-string">"Name (e.g., Alice)"</span>,
                            prefixIcon: Icon(Icons.person),
                            border: OutlineInputBorder(),
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: <span class="hljs-number">12</span>,
                              vertical: <span class="hljs-number">8</span>,
                            ),
                          ),
                        ),
                      ),
                    ),
                    <span class="hljs-comment">// Dynamic spacer</span>
                    isSmallScreen
                        ? <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">12</span>)
                        : <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">10</span>),
                    Expanded(
                      flex: isSmallScreen ? <span class="hljs-number">0</span> : <span class="hljs-number">2</span>,
                      child: SizedBox(
                        width: isSmallScreen ? <span class="hljs-built_in">double</span>.infinity : <span class="hljs-keyword">null</span>,
                        child: DropdownButtonFormField&lt;<span class="hljs-built_in">String</span>&gt;(
                          initialValue: selectedRelationship,
                          decoration: <span class="hljs-keyword">const</span> InputDecoration(
                            labelText: <span class="hljs-string">'Relationship'</span>,
                            border: OutlineInputBorder(),
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: <span class="hljs-number">12</span>,
                              vertical: <span class="hljs-number">8</span>,
                            ),
                          ),
                          items: StaticListData.relationships.map((<span class="hljs-built_in">String</span> rel) {
                            <span class="hljs-keyword">return</span> DropdownMenuItem(value: rel, child: Text(rel));
                          }).toList(),
                          onChanged: (val) =&gt; selectRelationship(val),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
              Padding(
                padding: <span class="hljs-keyword">const</span> EdgeInsets.only(left: <span class="hljs-number">18.0</span>),
                child: Text(
                  <span class="hljs-string">"Pick a theme color:"</span>,
                  style: TextStyle(
                    color: Colors.grey[<span class="hljs-number">700</span>],
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">8</span>),

              Padding(
                padding: <span class="hljs-keyword">const</span> EdgeInsets.only(left: <span class="hljs-number">16.0</span>),
                child: Flex(
                  direction: isSmallScreen ? Axis.vertical : Axis.horizontal,
                  crossAxisAlignment: isSmallScreen
                      ? CrossAxisAlignment.stretch
                      : CrossAxisAlignment.center,
                  children: [
                    isSmallScreen
                        ? ColorPickerList(
                            selectedColorName: selectedColorName,
                            selectedColorUi: selectedColorUi,
                            colorOptions: StaticListData.colorOptions,
                            onColorSelected: onColorSelected,
                          )
                        : Expanded(
                            child: ColorPickerList(
                              selectedColorName: selectedColorName,
                              selectedColorUi: selectedColorUi,
                              colorOptions: StaticListData.colorOptions,
                              onColorSelected: onColorSelected,
                            ),
                          ),

                    <span class="hljs-keyword">if</span> (isSmallScreen) <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">16</span>),

                    <span class="hljs-comment">// Generate Button</span>
                    Padding(
                      padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">18.0</span>),
                      child: SizedBox(
                        width: isSmallScreen ? <span class="hljs-built_in">double</span>.infinity : <span class="hljs-keyword">null</span>,
                        child: ElevatedButton.icon(
                          onPressed: generateCard,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.red,
                            foregroundColor: Colors.white,
                            padding: <span class="hljs-keyword">const</span> EdgeInsets.symmetric(
                              horizontal: <span class="hljs-number">24</span>,
                              vertical: <span class="hljs-number">16</span>,
                            ),
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(<span class="hljs-number">8</span>),
                            ),
                          ),
                          icon: <span class="hljs-keyword">const</span> Icon(Icons.auto_awesome),
                          label: <span class="hljs-keyword">const</span> Text(
                            <span class="hljs-string">"Generate Card"</span>,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
</code></pre>
<p>As the most complex component in the architecture, this widget aggregates all inputs by utilizing a <code>LayoutBuilder</code> to monitor parent constraints, dynamically switching the <code>Flex</code> direction between <code>Axis.horizontal</code> for tablets and web and <code>Axis.vertical</code> for mobile stacking when the <code>maxWidth</code> is less than 600.</p>
<p>To ensure a seamless layout across devices, it leverages <code>Expanded</code> on large screens to fill the available space while using <code>SizedBox(width: double.infinity)</code> on smaller screens to force inputs to the full width of the device, all while maintaining clean code by integrating the <code>ColorPickerList</code> and <code>StaticListData</code>.</p>
<h2 id="heading-adding-your-own-widgets-to-the-genui-catalog">Adding Your Own Widgets to the GenUI Catalog</h2>
<p>So far in this project, we’ve relied entirely on the widgets provided by <code>CoreCatalogItems</code>. These include common UI building blocks like <code>Text</code>, <code>Column</code>, <code>Container</code>, and <code>Image</code>, which are enough to get surprisingly rich results.</p>
<p>But GenUI really shines when you teach the AI about <strong>your own domain-specific widgets</strong>.</p>
<p>In our case, we’re not just generating arbitrary UI – we’re generating high-end, personalized Christmas cards. That makes this a perfect candidate for a custom catalog item.</p>
<p>Instead of hoping the AI assembles the perfect layout every time from primitive widgets, we can introduce a first-class “Holiday Card” widget and let the model generate data for it.</p>
<h3 id="heading-why-add-a-custom-widget">Why Add a Custom Widget?</h3>
<p>In the current implementation, the AI generates festive UIs using general-purpose widgets, which works but leads to inconsistent card structure, repeated styling instructions, and excessive layout freedom.</p>
<p>By introducing a custom widget into the catalog, layout and styling decisions are encoded directly in Flutter. This allows the AI to focus on content and personalization while producing more predictable, production-ready results.</p>
<h3 id="heading-step-1-adding-jsonschemabuilder">Step 1: Adding <code>json_schema_builder</code></h3>
<p>To define a custom widget, GenUI needs to know what data it accepts. You can tell it this using a JSON Schema.</p>
<p>Add <code>json_schema_builder</code> as a dependency, using the same repository reference as GenUI:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">json_schema_builder:</span>
    <span class="hljs-attr">git:</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://github.com/flutter/genui.git</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">packages/json_schema_builder</span>
</code></pre>
<p>This ensures schema compatibility with the GenUI runtime.</p>
<h3 id="heading-step-2-defining-the-holiday-card-schema">Step 2: Defining the Holiday Card Schema</h3>
<p>A Christmas card in our app needs a few core pieces of data:</p>
<ul>
<li><p>The recipient’s name</p>
</li>
<li><p>The relationship (friend, spouse, family, and so on)</p>
</li>
<li><p>The message body</p>
</li>
<li><p>A closing signature</p>
</li>
</ul>
<p>Using <code>json_schema_builder</code>, we can define this explicitly:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> holidayCardSchema = S.object(
  properties: {
    <span class="hljs-string">'recipientName'</span>: S.string(
      description: <span class="hljs-string">'Name of the person receiving the card'</span>,
    ),
    <span class="hljs-string">'relationship'</span>: S.string(
      description: <span class="hljs-string">'Relationship to the recipient (friend, spouse, family)'</span>,
    ),
    <span class="hljs-string">'message'</span>: S.string(
      description: <span class="hljs-string">'Main heartfelt holiday message'</span>,
    ),
    <span class="hljs-string">'signature'</span>: S.string(
      description: <span class="hljs-string">'Closing signature for the card'</span>,
    ),
  },
  <span class="hljs-keyword">required</span>: [
    <span class="hljs-string">'recipientName'</span>,
    <span class="hljs-string">'relationship'</span>,
    <span class="hljs-string">'message'</span>,
    <span class="hljs-string">'signature'</span>,
  ],
);
</code></pre>
<p>This schema becomes the contract between your Flutter app and the AI.</p>
<h3 id="heading-step-3-creating-the-catalogitem">Step 3: Creating the CatalogItem</h3>
<p>Each custom widget is registered as a <code>CatalogItem</code>. This ties together:</p>
<ul>
<li><p>A <strong>name</strong> (used by the AI)</p>
</li>
<li><p>The <strong>schema</strong></p>
</li>
<li><p>A <strong>widget builder</strong> that renders Flutter UI</p>
</li>
</ul>
<p>Here’s what a <code>HolidayCard</code> catalog item might look like:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> holidayCardItem = CatalogItem(
  name: <span class="hljs-string">'HolidayCard'</span>,
  dataSchema: holidayCardSchema,
  widgetBuilder: (context) {
    <span class="hljs-keyword">final</span> name = context.dataContext.subscribeToString(
      context.data[<span class="hljs-string">'recipientName'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Object?</span>&gt;?,
    );
    <span class="hljs-keyword">final</span> message = context.dataContext.subscribeToString(
      context.data[<span class="hljs-string">'message'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Object?</span>&gt;?,
    );
    <span class="hljs-keyword">final</span> signature = context.dataContext.subscribeToString(
      context.data[<span class="hljs-string">'signature'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Object?</span>&gt;?,
    );

    <span class="hljs-keyword">return</span> ValueListenableBuilder&lt;<span class="hljs-built_in">String?</span>&gt;(
      valueListenable: name,
      builder: (context, recipientName, _) {
        <span class="hljs-keyword">return</span> ValueListenableBuilder&lt;<span class="hljs-built_in">String?</span>&gt;(
          valueListenable: message,
          builder: (context, body, _) {
            <span class="hljs-keyword">return</span> ValueListenableBuilder&lt;<span class="hljs-built_in">String?</span>&gt;(
              valueListenable: signature,
              builder: (context, signOff, _) {
                <span class="hljs-keyword">return</span> Container(
                  margin: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">24</span>),
                  padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">24</span>),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(<span class="hljs-number">20</span>),
                    border: Border.all(color: Colors.redAccent),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      <span class="hljs-keyword">const</span> Text(
                        <span class="hljs-string">'🎄 Merry Christmas 🎄'</span>,
                        style: TextStyle(
                          fontSize: <span class="hljs-number">24</span>,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">16</span>),
                      Text(
                        <span class="hljs-string">'Dear <span class="hljs-subst">${recipientName ?? <span class="hljs-string">''</span>}</span>,'</span>,
                        style: <span class="hljs-keyword">const</span> TextStyle(fontSize: <span class="hljs-number">18</span>),
                      ),
                      <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">12</span>),
                      Text(
                        body ?? <span class="hljs-string">''</span>,
                        textAlign: TextAlign.center,
                      ),
                      <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">24</span>),
                      Text(
                        signOff ?? <span class="hljs-string">''</span>,
                        style: <span class="hljs-keyword">const</span> TextStyle(fontWeight: FontWeight.w600),
                      ),
                    ],
                  ),
                );
              },
            );
          },
        );
      },
    );
  },
);
</code></pre>
<p>Notice how <strong>no state is stored in the widget itself</strong>. Everything comes from the GenUI data model.</p>
<h3 id="heading-step-4-registering-the-widget-in-your-app">Step 4: Registering the Widget in Your App</h3>
<p>Now we’ll plug the custom widget into your existing setup.</p>
<p>In your <code>initState</code>, instead of using only <code>CoreCatalogItems</code>, extend the catalog:</p>
<pre><code class="lang-dart">_a2uiMessageProcessor = A2uiMessageProcessor(
  catalogs: [
    CoreCatalogItems.asCatalog().copyWith([
      holidayCardItem,
    ]),
  ],
);
</code></pre>
<p>This makes <code>HolidayCard</code> available to the AI.</p>
<h3 id="heading-step-5-teaching-the-ai-to-use-the-widget">Step 5: Teaching the AI to Use the Widget</h3>
<p>Finally, we’ll update the system instruction so the AI knows when and how to use the new widget.</p>
<p>In your existing <code>FirebaseAiContentGenerator</code>, the instruction can be refined like this:</p>
<pre><code class="lang-text">      _contentGenerator = FirebaseAiContentGenerator(
      catalog: CoreCatalogItems.asCatalog(),
      systemInstruction: '''
          You are an expert Festive UI Designer and Holiday Copywriter.

          YOUR GOAL: Generate a high-end, visually appealing Christmas card using the `surfaceUpdate` tool, suitable for printing or digital sharing. The card should feel personalized, warm, and festive.

          DESIGN GUIDELINES:
          - Layout: Use a vertical Column inside a Container with rounded corners, generous padding, and a border. Fill the Container with a color that **mixes Red with $selectedColorName ** to create a rich, holiday-themed background.
          - Typography: Use distinct font weights (Bold for headers, normal for body). Center all text.
          - Visuals: Include seasonal icons (🎄, ✨, ❄️) as decorative elements. Place a Christmas tree emoji strategically without overcrowding the layout.
          - Personalization: Display the recipient's name prominently in the middle of the card in a visually striking way.

          COPYWRITING GUIDELINES:
          - Create a deeply personal, heartfelt holiday message (3-4 sentences) that matches the relationship type (fun for friends, romantic for spouse, warm for family).
          - Include a proper closing/signature.
          - NEVER use placeholders. Always generate the **final text ready to display**.

          OUTPUT INSTRUCTIONS:
          - Use the `surfaceUpdate` tool to construct the UI.
          - Ensure all elements (Container, text, emojis) are visually aligned and harmonious.
          - The card must feel festive, elegant, and balanced. When generating a Christmas card, always use the HolidayCard widget.
          ''',
    );
</code></pre>
<p>Now the AI isn’t guessing – it’s explicitly guided toward your custom widget.</p>
<h3 id="heading-how-this-fits-into-your-existing-screen">How This Fits into Your Existing Screen</h3>
<p>This integration requires <strong>no structural changes</strong> to your existing <code>CardGeneratorScreen</code>: <code>GenUiConversation</code> continues to manage the interaction lifecycle, <code>GenUiSurface</code> still handles rendering, and your input form remains fully responsible for shaping the prompt. The only change is what the AI is allowed to generate, which significantly improves control and consistency.</p>
<p>By adding custom widgets to the GenUI catalog, your application moves from AI assembling loosely defined UI fragments to AI populating structured, production-ready components, resulting in a cleaner interface, stronger visual identity, reduced prompt engineering, and far more predictable outputs. This is the point where GenUI stops feeling like a demo and starts functioning as a real product framework.</p>
<h2 id="heading-screenshots">Screenshots:</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766152202325/f14bf403-1b72-4e71-b6de-e0966cd51da2.png" alt="App Screenshot 1 - Entry" class="image--center mx-auto" width="1842" height="1732" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766155136764/bd14dd42-43cd-4897-a881-274376258935.png" alt="App Screenshot 2 - Error State" class="image--center mx-auto" width="1722" height="1854" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766152181735/adc30203-f4ce-4228-9d52-4edc15c62731.png" alt="App Screenshot 3 - Color Choosing" class="image--center mx-auto" width="1842" height="1732" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766157240628/e31a49db-4143-4e70-9d69-e63a81e722d0.png" alt="App Screenshot 4 - Loading State" class="image--center mx-auto" width="1722" height="1854" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766157250344/64938d48-e73f-405f-8050-c20dfc6ecd6a.png" alt="App Screenshot 1 - Successfuly showing the christmas card" class="image--center mx-auto" width="1722" height="1854" loading="lazy"></p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>This project demonstrates how you can take advantage of GenUI in its most practical form: not merely as a tech demo, but as a functional Flutter paradigm that bridges the gap between static code and user intent.</p>
<p>By shifting the responsibility of layout orchestration from the developer to an intelligent agent, we unlock a level of personalization that was previously not possible in mobile development.</p>
<p>Once you master the Conversation Loop (how the AI thinks), Surfaces (how the AI draws), and Catalog Boundaries (what the AI is allowed to use), GenUI becomes a transformative addition to your Flutter toolkit. It allows you to build interfaces that aren't just "responsive" to screen sizes, but "responsive" to human needs.</p>
<p>As an early adopter, you are on the cutting edge of AI-Generated User Interfaces. Your explorations and feedback will help shape the future of how we build apps in the era of generative intelligence. You can find the <a target="_blank" href="https://github.com/Atuoha/christmas-card-genui-flutter">complete project on Github here</a>.</p>
<h2 id="heading-references">References</h2>
<ol>
<li><p>Flutter Team. <em>GenUI: Build AI-powered user interfaces in Flutter</em>. GitHub repository.<br> Available at: <a target="_blank" href="https://github.com/flutter/genui/">https://github.com/flutter/genui/</a></p>
</li>
<li><p>Flutter Documentation. <em>Getting started with GenUI</em>.<br> Available at: <a target="_blank" href="https://docs.flutter.dev/ai/genui/get-started">https://docs.flutter.dev/ai/genui/get-started</a></p>
</li>
<li><p>Dart &amp; Flutter Ecosystem. <em>genui package</em>. pub.dev.<br> Available at: <a target="_blank" href="https://pub.dev/packages/genui">https://pub.dev/packages/genui</a></p>
</li>
<li><p>Dart &amp; Flutter Ecosystem. <em>genui_firebase_ai package</em>. pub.dev.<br> Available at: <a target="_blank" href="https://pub.dev/packages/genui_firebase_ai">https://pub.dev/packages/genui_firebase_ai</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
