SODA Engineering Blog
🌹

AI x FlutterでリアルタイムにUIを生成する

に公開

スニダンを開発している@natsuk4zeです!
今回は生成UIというリアルタイムにUIを生成する技術を、Flutterで試してきたので、ご紹介します✨

GitHubもぜひフォーローお願いします!
https://github.com/natsuk4ze

デモ動画

今回はこのようなデモを用意してみました。
ローティングを挟んだ後に表示される沖縄に関する画面は丸ごとAIが生成しています。

設計

ユーザーの入力を元に、AIがJSON形式でWidgetを生成し、アプリはそのJSONをWidgetに変換して表示しています。

ユーザー入力画面

ここでは、ユーザーが入力したテキストを、プッシュ遷移で生成UI画面に渡しているのみで、特別なことはしていません。

/// ユーザー入力画面
class UserInputScreen extends HookWidget {
  const UserInputScreen({super.key});

  
  Widget build(BuildContext context) {
    final textController = useTextEditingController();

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextField(controller: textController),
          const Gap(16),
          FilledButton(
            // テキストを生成UI画面に渡す
            onPressed: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => GenUIScreen(
                  inputText: textController.text,
                ),
              ),
            ),
            // 省略
          ),
        ],
      ),
    );
  }
}

生成UI画面

ここでは、ユーザー入力画面から受け取ったTextを元に、APIからJsonを取得し、StacでWidgetに変換して表示しています。

ScaffoldCard等のWidgetが一切登場していないことから、一画面丸ごとJsonから生成されていることが分かります。

/// 生成UI画面
class GenUIScreen extends ConsumerWidget {
  const GenUIScreen({super.key, required this.inputText});

  final String inputText;

  
  Widget build(BuildContext context, WidgetRef ref) {
    final json = ref.watch(stacJsonProvider(text: inputText));

    return switch (json) {
      AsyncData(:final value) => Stac.fromJson(value, context),
      _ => const Center(child: CircularProgressIndicator()),
    }!;
  }
}

Stacについて

Stacとはpub.devで公開中のServer Driven UI Frameworkパッケージです!
JSON形式でWidgetを定義、表示できることが大きな特徴です。
https://stac.dev/

全てのWidgetに対応している訳ではない点や、Widget to JSONは開発中である点には注意が必要です。

Gemini API

ここでは、ユーザーから入力されたTextを元に、Gemini APIでJson形式のWidgetを生成しています。生成されたJsonは非同期Providerで公開されています。


Future<Json> stacJson(Ref ref, {required String inputText}) async {
  final dio = Dio();
  const apiKey = String.fromEnvironment('GEMINI_API_KEY');

  final response = await dio.post<Json>(
    'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$apiKey',
    data: {
      "system_instruction": {
        "parts": [
          {
            "text": "1. あなたは Flutter の Stac パッケージ向け JSON API ジェネレーターです。"
          },
          {
            "text": "2. ルートの \"type\" は必ず \"scaffold\"。"
          },
          {
            "text": "3. Material Design 3 準拠のデザインを使用すること。"
          },
         ,,, // その他指示
        ]
      },
      "contents": [
        {
          "parts": [
            {"text": inputText}
          ]
        }
      ]
    },
  );

  /* 
  整形
   */

  return jsonDecode(extractedJson);
}

モデルは、gemini-2.0-flashを使用しています。無料で簡単に使えることと、Json系を扱うのが上手なイメージがあったからです!

system_instructionにAIへの指示を渡しています。ここでJsonジェネレータとして振る舞うように指示したり、UIに関する制約を指示しています。

contentsにユーザーが入力したtext(今回であれば「沖縄のおすすめスポット」)を渡しています。

結果

というわけで、今回はGeminiとStacを組み合わせたFlutterの生成UIにチャレンジしてみました!

生成UIによって、ユーザー単位でのUI表示が可能になるので、よりパーソナライズされた体験を届けることができそうです✨

今後は画面単位ではなく、コンポーネント単位で活用したり、loadingで生成してる感を演出したりしても面白そうです...!

(Webで実装した方が速そうではある...笑)

ではではー👋

SODA Engineering Blog
SODA Engineering Blog

Discussion