
Flutter GenUI SDK: 처음부터 AI가 생성한 UI 구축하기 (완벽 초보자 튜토리얼 2026)
요약
Flutter GenUI SDK를 사용하여 Gemini 모델이 런타임에 사용자 맞춤형 UI를 실시간으로 생성하는 앱을 구축하는 방법을 소개합니다. 개발자가 위젯 카탈로그를 정의하면 AI가 이를 조합하여 동적인 인터페이스를 구성하는 새로운 패러다임을 다룹니다.
핵심 포인트
- Flutter GenUI SDK는 Gemini가 런타임에 UI를 작성할 수 있는 스캐폴딩 제공
- 개발자는 위젯 어휘집을 정의하고 AI가 위젯을 조립하는 방식
- 구조화된 JSON을 실시간 Flutter 위젯으로 점진적 변환
- 반응형 데이터 모델을 통해 상태 업데이트를 효율적으로 처리
상상해 보세요. 언어 학습 앱을 열었습니다. Gemini 모델이 당신이 아랍어 동사 변화에 어려움을 겪고 있다는 것을 읽고, 2초도 안 되는 시간에 맞춤형 수업을 구성합니다: 문화적 배경 설정 카드, 가족 내 모든 단어를 보여주는 삼자 어근 다이어그램, 드래그 앤 드롭 모음글자 배치 연습, 그리고 동사 변화표. 이 화면들 중 어느 것도 미리 설계된 것이 아니었습니다. 어떤 개발자가 이 세션을 위해 이것들을 연결해 놓은 것도 아닙니다. 인터페이스는 모델 자체에 의해 당신을 위해 런타임(runtime)에 구성되었습니다.
그 앱이 Kalaam · كلام이며, 이를 가능하게 하는 기술이 바로 Flutter GenUI SDK입니다.
_Kalaam의 홈 화면: 학습 목표를 아무거나 입력하거나(
GenUI는 그 모델을 뒤집습니다. 개발자가 UI를 정의하는 대신, 개발자는 **위젯 어휘집 (vocabulary of widgets)**을 정의하고, AI 모델이 사용자의 현재 필요에 따라 어떤 위젯을, 어떤 순서로, 어떤 데이터와 함께 조립할지 결정합니다.
Flutter GenUI SDK (pub.dev의 genui, 버전 0.9.2, labs.flutter.dev 발행)는 A2UI v0.9 프로토콜의 공식 Flutter 구현체입니다. Flutter 3.44와 함께 Google I/O 2026에서 발표된 이 SDK는 Gemini가 런타임 (runtime)에 UI를 작성하는 앱을 구축할 수 있는 스캐폴딩 (scaffolding)을 제공합니다.
대부분의 설명이 놓치고 있는 핵심 통찰은 다음과 같습니다: 이 SDK는 사용자를 대신해 Gemini를 호출하지 않습니다. SDK는 Firebase, API 키 또는 네트워킹에 대해 알지 못합니다. SDK가 수행하는 작업은 다음과 같습니다:
- 사용자의 위젯 정의 카탈로그를 가져와 그 스키마 (schema)를 Gemini 시스템 프롬프트 (system prompt)에 임베딩합니다. 이를 통해 모델은 어떤 위젯을 생성할 수 있는지, 그리고 각 위젯이 어떤 속성 (properties)을 허용하는지 알게 됩니다.
- Gemini가 스트리밍 (streaming) 방식으로 다시 보내는 구조화된 JSON을 파싱 (parse)하여, 전체 응답이 도착하기 전에 실시간 Flutter 위젯으로 점진적으로 변환합니다.
- 위젯이 바인딩 (bind)할 수 있는 반응형 데이터 모델 (reactive data model)을 유지하여, AI가 전체 화면을 다시 빌드하지 않고도 상태 업데이트를 푸시할 수 있도록 합니다.
AI는 레이아웃 명세 (layout spec)를 작성합니다. SDK는 이를 렌더링 (render)합니다. 당신은 위젯을 작성합니다.
{} 버튼을 누르고 Gemini가 실시간으로 UI를 작성하는 것을 지켜보세요. 해당 JSON 패널의 모든 필드는 그 위의 화면에 위젯으로 나타납니다.
A2UI 프로토콜: 내부에서 실제로 일어나는 일
기존의 모든 GenUI 튜토리얼은 JSON 프로토콜을 블랙박스 (black box)로 취급합니다. 한 번만 열어보면 SDK의 전체 구조가 명확해집니다.
앱이 Gemini에게 메시지를 보낼 때, 모델은 일반 텍스트로 응답하지 않습니다. GenUI가 생성하는 시스템 프롬프트 (system prompt) 때문에, 모델은 A2UI v0.9 와이어 포맷 (wire format)을 따르는 구조화된 JSON 메시지로 응답합니다. 이 메시지들은 SDK에 무엇을 렌더링할지 알려줍니다. 여기에는 네 가지 유형이 있습니다.
createSurface — Gemini가 새로운 UI 영역을 생성하고 있습니다 (예: 레슨의 새로운 단계).
{
"type": "createSurface",
"surfaceId": "turn_1",
...
surfaceUpdate — Gemini가 기존의 서피스 (surface)를 제자리에서 수정하고 있습니다. 화면 재빌드 (rebuild)나 전환 (transition)은 발생하지 않습니다.
{
"type": "surfaceUpdate",
"surfaceId": "turn_1",
...
dataModelUpdate — Gemini가 반응형 데이터 스토어 (reactive data store)에 새로운 상태 (state)를 밀어 넣고 있습니다. 해당 경로에 바인딩된 모든 위젯 (widget)은 자동으로 재빌드됩니다.
{
"type": "dataModelUpdate",
"updates": {
...
deleteSurface — 레슨 단계가 완료되면 서피스를 제거합니다.
{
"type": "deleteSurface",
"surfaceId": "turn_1"
...
이 네 가지 메시지가 GenUI가 이해하는 전부입니다. SDK의 역할은 Gemini의 스트림 (stream)을 경청하고, 토큰 (token)이 도착할 때마다 (전체 응답이 완료되기 전에) 이 메시지들을 파싱 (parse)하여 위젯 트리 (widget tree)에 적용하는 것입니다. 이제 와이어 레벨 (wire level)에서 어떤 일이 일어나고 있는지 알게 되었으므로, SDK의 모든 클래스가 즉시 이해될 것입니다.
레슨 중간의 Kalaam 라이브 인스펙터 (Live Inspector). 왼쪽에 있는 CREATE turn_2 배지는 createSurface 메시지입니다. 그 아래의 필드들 — "version": "v0.9", "surfaceId": "turn_2" — 는 루트 익스플로러 (Root Explorer) 컴포넌트 JSON의 시작 부분입니다. 패널 위에 렌더링되어 보이는 루트 익스플로러가 바로 해당 필드들이 변환된 결과물입니다.
8단계 상호작용 사이클
GenUI 앱의 모든 상호작용은 동일한 루프 (loop)를 따릅니다:
- 사용자가 무언가를 입력하거나 탭합니다.
- 앱이
conversation.sendRequest(content)를 호출합니다. Conversation이A2uiTransportAdapter의onSend콜백을 트리거합니다.- 콜백이 Gemini를 호출하고 각 스트리밍 토큰 (streaming token)을
_transport.addChunk(chunk)로 전달합니다. A2uiParserTransformer가 스트리밍되는 JSON을 실시간으로 파싱 (parse) 합니다.- 파싱된
A2uiMessage객체들이SurfaceController.handleMessage()로 전달됩니다. SurfaceController가DataModel을 업데이트하면 해당Surface위젯들이 다시 빌드 (rebuild) 됩니다.- 사용자가 생성된 위젯을 탭하면,
UserActionEvent가 디스패치 (dispatch) 되고,SurfaceController.onSubmit이 방출(emit)되며,Conversation이 이를 새로운 사용자 턴 (user turn)으로 감싸서 단계 2부터 사이클이 다시 시작됩니다.
여러분이 수행하는 모든 API 호출은 이 루프의 한 단계에 매핑됩니다. 아래 섹션들을 읽는 동안 이 사이클을 염두에 두시기 바랍니다.
사용자 입력
│
▼
...
사전 요구 사항 및 프로젝트 설정
GenUI 코드를 작성하기 전에 다음 사항을 확인하세요:
- Flutter 3.44+ (
flutter --version을 실행하여 확인) - Dart 3.9+
- AI Logic이 활성화된 Firebase 프로젝트 — Gemini Developer API는 무료 티어 (free tier)에서 제공됩니다.
flutterfire_cli도구:dart pub global activate flutterfire_cli
종속성 설치
pubspec.yaml에 다음을 추가하세요:
dependencies:
flutter:
sdk: flutter
...
flutter pub get을 실행합니다.
Firebase 구성
flutterfire configure를 실행하고 안내를 따르세요. 이 과정에서 lib/firebase_options.dart가 생성됩니다. 이 파일에는 앱의 자격 증명 (credentials)이 포함되어 있으므로 절대 커밋 (commit)해서는 안 됩니다. 지금 바로 .gitignore에 다음 줄을 추가하세요:
lib/firebase_options.dart
android/app/google-services.json
ios/Runner/GoogleService-Info.plist
iOS 및 macOS 네트워크 권한 (대부분의 튜토리얼이 생략하는 단계)
iOS 또는 macOS를 타겟으로 하는 경우, 이 권한 (entitlement) 없이는 앱이 Gemini에 도달하지 못하고 조용히 실패하게 됩니다. 한 번만 추가해 두면 됩니다.
ios/Runner/Runner.entitlements 파일에 다음을 추가하세요:
<key>com.apple.security.network.client</key>
<true/>
macos/Runner/DebugProfile.entitlements 및 Release.entitlements 파일에 다음을 추가하세요:
<key>com.apple.security.network.client</key>
<true/>
Firebase 초기화 (Initialize Firebase)
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
...
반드시 이해해야 할 5가지 핵심 개념
1. CatalogItem — AI가 구축할 수 있는 항목 정의하기
CatalogItem은 GenUI의 기본 단위입니다. 이는 SDK(그리고 따라서 Gemini)에게 X라는 이름의 위젯이 존재하며, 어떤 JSON 속성(properties)을 허용하는지, 그리고 이를 Flutter 위젯으로 어떻게 렌더링하는지를 알려줍니다.
다음은 최소한의 구성이지만 완전한 예시인 간단한 아랍어 플래시카드입니다:
import 'package:genui/genui.dart';
import 'package:json_schema_builder/json_schema_builder.dart';
...
여기서 세 가지 작업이 일어나고 있으며, 세 가지 모두 중요합니다.
**dataSchema**는 모델이 이 위젯을 생성하고자 할 때 반드시 제공해야 하는 JSON을 정의합니다. json_schema_builder 패키지는 JSON Schema를 작성하기 위한 타입 안전(type-safe) DSL(S.object, S.string, S.list, S.boolean)을 제공합니다. GenUI는 이 스키마를 시스템 프롬프트(system prompt)에 포함시켜 Gemini가 어떤 필드가 유효하고 필수적인지 알 수 있게 합니다.
**exampleData**는 하나 이상의 예시 JSON 스니펫을 포함합니다. GenUI는 이를 시스템 프롬프트 내에서 퓨샷 예시(few-shot examples)로 사용하여, 모델에게 단순히 설명만 하는 것이 아니라 시연을 통해 올바른 형식을 학습시킵니다. 이는 모델로부터 일관되고 유효한 출력을 얻어내기 위한 가장 강력한 수단입니다. 좋은 예시 하나는 상세한 설명보다 더 가치 있습니다.
**widgetBuilder**는 Flutter 측 로직입니다. 이는 ctx.data(파싱된 JSON), ctx.dataContext(반응형 데이터 모델에 대한 접근), 그리고 ctx.dispatchEvent(...)(사용자 상호작용을 Gemini로 다시 보내기 위한 함수)를 포함하는 CatalogItemContext를 전달받습니다. 어떤 Flutter 위젯이든 반환하면 됩니다. Column과 Row에는 mainAxisSize: MainAxisSize.min을 유지하세요. GenUI는 사용자의 위젯을 동적 크기의 표면(surface) 내부에 삽입하므로, 제한 없는 높이 제약 조건(unbounded height constraints)이 있으면 크래시가 발생할 수 있습니다.
실제 CatalogItem이 렌더링된 모습은 다음과 같습니다 — createSurface 메시지의 단일 RootExplorer 컴포넌트로부터 생성된 Kalaam의 Root System Explorer입니다:

모든 노드, 커넥터 라인(connector line), 레이블은 createSurface 메시지에서 Gemini가 제공한 JSON으로부터 생성됩니다. widgetBuilder는 해당 JSON을 이 방사형 다이어그램(radial diagram)으로 변환합니다.
2. Catalog — CatalogItem 그룹화하기
Catalog는 CatalogItem들의 이름이 지정된 컬렉션입니다. 여러분의 커스텀 아이템을 내장된 프리미티브(primitives)와 결합할 수 있습니다:
final appCatalog = Catalog(
items: [
flashcardItem,
...
BasicCatalogItems.all()을 사용하면 17개의 내장 위젯을 무료로 사용할 수 있습니다: Button, Column, Row, Card, Text, TextField, AudioPlayer, Tabs, List, Image, Icon, Divider, Slider, ChoicePicker, CheckBox, DateTimeInput, 그리고 Modal입니다. Gemini는 여러분의 커스텀 위젯과 프리미티브를 동일한 레이아웃 내에서 모두 사용할 수 있습니다. 단일 createSurface 메시지가 ArabicFlashcard 다음에 Button이 이어지는 Column을 구성할 수도 있습니다.
Kalaam은 13개의 모든 커스텀 아랍어 교육 위젯과 모든 내장 프리미티브가 결합된 카탈로그를 제공하며, 이는 lib/features/session/catalog/catalog.dart에 정의되어 있습니다.
3. SurfaceController — 런타임 엔진
SurfaceController는 GenUI 세션의 두뇌입니다. 이는 들어오는 A2uiMessage 객체를 처리하고, 반응형(reactive) DataModel을 관리하며, 사용자가 생성된 위젯과 상호작용할 때 이벤트를 브로드캐스트(broadcast)합니다.
final controller = SurfaceController(catalogs: [appCatalog]);
Surface 위젯은 특정 surface ID에 대해 모델이 구축한 결과물을 렌더링합니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
