The developer experience (DX) companion for the official Google Generative UI (GenUI) ecosystem in Flutter.
GenUI code generation automatically extracts data schemas and generates type-safe builders for your widgets at compile time. Instead of manually writing JSON schemas and writing repetitive catalog adapters, you can focus on building standard Flutter widgets.
- Zero-Boilerplate Catalogs: Simply decorate your Flutter widget with
@generativeUI. The build system automatically handles schema mapping and widget instantiation. - Compile-Time Introspection: Uses Dart's AST (Abstract Syntax Tree) and
build_runnerto extract property constraints, avoiding runtime reflection overhead. - Two-Phase Code Generation: Automatically generates highly optimized local component schemas (
CatalogItems) AND assembles a centralgenui_registry.g.dartcatalog index. No manual wiring required. - Automatic Event Mapping: Event callback properties (like
VoidCallback) are automatically mapped to dispatch A2UI events (dispatchEvent(UserActionEvent)), sending widget states back to the LLM. - Seamless Integration: Built directly on top of the official
package:genuiandpackage:json_schema_builder.
- Zero Boilerplate: Decorate your Flutter widget with
@generativeUI. - Auto-Discovery: You don't need to manually register your widgets into a massive list. The
genui_buildercrawls your project and creates a singleglobalGenUICatalogcontaining everything. - Fully Type-Safe: Automatically casts
itemContext.datavalues to your widget constructor fields, preventing type mismatches at runtime.
This monorepo utilizes Dart Pub Workspaces and Melos to ensure clean dependency graphs.
| Package | Description |
|---|---|
genui_annotations |
Lightweight package containing the @generativeUI and @GenerativeUI annotations. |
genui_builder |
The AST code generator using build_runner and analyzer to extract component schemas and build the global catalog index at compile time. |
example |
The playground Flutter app demonstrating the integration with Google's official package:genui Surface and SurfaceController. |
Ensure you have Dart SDK ^3.12.0 or Flutter SDK installed. You also need Melos activated globally.
# Activate Melos globally
dart pub global activate melosClone the repository and resolve dependencies:
git clone https://github.com/vicajilau/genui.git
cd genui
# Link dependencies natively across the workspace
flutter pub getTo generate your UI schemas and the global registry, run:
# Triggers code generation across all workspace packages
melos run build_runnerImport the annotations package, decorate your widget, and include the .genui.g.dart part directive.
import 'package:flutter/material.dart';
import 'package:genui/genui.dart';
import 'package:genui_annotations/genui_annotations.dart';
import 'package:json_schema_builder/json_schema_builder.dart';
part 'user_card.genui.g.dart';
@generativeUI
class UserCard extends StatelessWidget {
final String username;
final String role;
const UserCard({
super.key,
required this.username,
required this.role,
});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(username),
subtitle: Text(role),
),
);
}
}Run the code generator. GenUI will automatically compile your widget into a CatalogItem and export it to a global Catalog in lib/genui_registry.g.dart.
import 'package:flutter/material.dart';
import 'package:genui/genui.dart';
// Auto-generated by GenUI Builder
import 'genui_registry.g.dart';
void main() {
// Inject the global catalog into the official SurfaceController
// Note: globalGenUICatalog automatically includes Google's official layout elements (Row, Column, Text, etc.) alongside your custom widgets!
final controller = SurfaceController(catalogs: [globalGenUICatalog]);
runApp(MainApp(controller: controller));
}
class MainApp extends StatelessWidget {
final SurfaceController controller;
const MainApp({super.key, required this.controller});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
// Render dynamic surfaces automatically using your catalog!
child: Surface(
surfaceContext: controller.contextFor('demo_surface'),
),
),
),
);
}
}If your widget defines interactive callbacks (like VoidCallback onToggle), you can parse and process them cleanly using GenUiEvent and auto-generated event constants:
import 'package:genui_annotations/genui_annotations.dart';
// Import the generated events:
import 'widgets/user_card.genui.g.dart';
void handleWidgetEvent(String eventJson) {
final event = GenUiEvent.parse(eventJson);
if (event == null) return;
if (event.name == UserCardEvents.onToggle) {
print('UserCard clicked for component ID: ${event.sourceComponentId}');
print('Widget properties context: ${event.context}');
}
}Note: For callbacks that take arguments (e.g. ValueChanged<bool> onChanged), parameter names and values are automatically appended to the event's context map (e.g. event.context['value']).
GenUI supports exporting your UI component schemas in a framework-agnostic way. This is ideal if you are orchestrating your AI models in a separate Node.js, Go, or Python backend (like Google Genkit).
If your AI runs locally on the device or if you have a Dart backend, you can fetch pre-formatted schemas directly from genui_registry.g.dart:
// 1. Raw Dart Map of JSON Schemas:
Map<String, Map<String, dynamic>> schemas = globalGenUISchemas;
// 2. Pre-formatted Markdown list ready for LLM System Instructions:
String systemInstruction = '''
You are a GenUI assistant. You must respond using components defined here:
$globalGenUISchemasPromptDescription
''';To use your schemas in a separate backend repository (e.g., Node.js with Genkit), you can export the schemas to a static JSON file.
Because the generated registry transitively imports package:flutter (which has native graphic/engine bindings), running a standalone Dart script via dart run in the console will fail. Instead, we run a headless script using the flutter test runner, which resolves all Flutter engine bindings correctly.
To set this up in your own Flutter project:
- Create the export script as a test file (e.g.,
test/genui_schemas_export_test.dart):
import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app_name/genui_registry.g.dart'; // Import your generated registry
void main() {
test('Export GenUI schemas to JSON', () {
final schemas = globalGenUISchemas;
final jsonString = const JsonEncoder.withIndent(' ').convert(schemas);
// Auto-detect the project root and write to build/genui_schemas.json
final envPath = Platform.environment['GENUI_EXPORT_PATH'];
final file = envPath != null && envPath.isNotEmpty
? File(envPath)
: File('build/genui_schemas.json');
file.parent.createSync(recursive: true);
file.writeAsStringSync(jsonString);
print('β Schemas exported to: ${file.absolute.path}');
});
}- Trigger the export automatically by appending it to your build command in your
pubspec.yamlscripts or Melos tasks:
# Run build_runner, then run the test script to write the JSON schema contract
dart run build_runner build && flutter test test/genui_schemas_export_test.dartBy default, this will write the schema to build/genui_schemas.json relative to your project's root.
If you want to copy the file directly to a custom directory (e.g., to a sibling folder containing your Node.js Genkit backend), you can use the GENUI_EXPORT_PATH environment variable:
GENUI_EXPORT_PATH=../my-genkit-backend/src/genui_schemas.json flutter test test/genui_schemas_export_test.dartThis project is licensed under the MIT License - see the LICENSE file for details.
