-- Views
October 29, 26
スライド概要
FlutterKaigi 2026
AI 機能の UI はなぜ難しいのか 〜 Five UI States で設計する Flutter × 生成 AI の画面デザイン 〜
の発表スライドと付録です
AI 機能の UI は なぜ難しいのか Five UI States で設計する Flutter × 生成 AI の画面デザイン FlutterKaigi 2026 / LT / @pregum_fox
自己紹介 日野泰志 X:@pregum_fox GitHub:Pregum Flutter でアプリを作っている 株式会社エクストーン所属 狐が好き
こんな経験ありませんか? 実装を始める前は問題なさそうなのに、いざ実装を始めると、決めるこ とが出てくる 実装を始める前 いざ実装を始めると 画面はそろっていて、 → 「読み込み中、ここの UI はどうする?」 「エラーのときは、どう処理する?」 問題なさそうに見える
この画面に、状態を持つ UI はいくつある? A. 1 つ B. 3 つ C. 7 つ以上 旅のアプリ こんにちは、ゲストさん 次の旅⾏まであと 12 ⽇ ☀ 箱根 24℃ 週末は晴れ ✦ AI のおすすめプラン 1 ⽇⽬:ロープウェイで⼤涌⾕へ。 昼は駅前のそば屋がおすすめです。 2 ⽇⽬:美術館をめぐり、 ホテル箱根の森 残り 2 室 ¥12,000〜 ★ 4.5(128 件) ⼝コミの要約(AI) 「部屋からの景⾊がよく、朝⾷が好評。 駅からは少し歩くとの声も」 要約は誤りを含むことがあります 予約する ※ 架空のアプリです
この画面に、状態を持つ UI はいくつある? A. 1 つ B. 3 つ C. 7 つ以上 正解は C。名前・天気・空室・評価・予約ボタンまで、動かな そうな場所も非同期で、それぞれ状態を持つ。 旅のアプリ こんにちは、ゲストさん 1 次の旅⾏まであと 12 ⽇ 2 ☀ 箱根 24℃ 週末は晴れ ✦ 3 AI のおすすめプラン 1 ⽇⽬:ロープウェイで⼤涌⾕へ。 昼は駅前のそば屋がおすすめです。 2 ⽇⽬:美術館をめぐり、 ホテル箱根の森 残り 2 室 ★ 4.5(128 件) ⼝コミの要約(AI) ¥12,000〜 5 4 6 「部屋からの景⾊がよく、朝⾷が好評。 駅からは少し歩くとの声も」 要約は誤りを含むことがあります 予約する ※ 架空のアプリです 7
しかも起きうるのは Loading だけじゃない 領域 1 ユーザー情報 2 天気 3 AI のおすすめ 4 空室・価格 5 レビュー 6 AI の口コミ要約 7 予約ボタン Ideal ● ● ● ● ● ● ● Blank ・ ・ ● ● ● ● ・ Loading ● ● ● ● ● ● ● Error ● ● ● ● ● ● ● Partial ・ ・ ● ・ ● ● ・ ● =この領域で起こりうる状態 Blank=満室・口コミ 0 件/Error=要約の失敗・通信断/Partial=生成の途中・口コミが少ない
しかも起きうるのは Loading だけじゃない 領域 1 ユーザー情報 2 天気 3 AI のおすすめ 4 空室・価格 5 レビュー 6 AI の口コミ要約 7 予約ボタン Ideal ● ● ● ● ● ● ● Blank ・ ・ ● ● ● ● ・ Loading ● ● ● ● ● ● ● Error ● ● ● ● ● ● ● Partial ・ ・ ● ・ ● ● ・ ● =この領域で起こりうる状態 Blank=満室・口コミ 0 件/Error=要約の失敗・通信断/Partial=生成の途中・口コミが少ない 領域 × 5 状態で確認すれば、実装の前に抜けに気づける
1 概念について Five UI States とは 2 本題 AI 機能で、5 状態はどう変わるか 3 Flutter での設計 sealed class と、Partial の書き方 4 まとめ AI 機能の UI と、どう向き合うか
Scott Hurff「The UI Stack」(2015) 1 つの画面が持つ 5 つの状態を、 層として整理した フレームワーク。 この LT では、 ここで整理されている 5 つの状態を Five UI States と 呼んで話す。 図:Scott Hurff 「How to fix a bad user interface」より 著書『Designing Products People Love』(O'Reilly, 2016)からの抜粋記事が原典。 調べるときは「The UI Stack」で検索。
Five UI States Blank Loading Partial Error Ideal まだ中身がない状態(初回起動・0 件・検索ヒットなし) 結果を待っている状態(まだ何も出せない) 空ではないが、まだまばらな状態(価値の一部は見えている) 失敗した状態(ユーザーを行き止まりにしない導線が要る) 意図通りに、価値のある中身が表示できている状態
AI のチャットにも同じ 5 つの状態がある Ideal Blank Error Partial Loading Claude(Anthropic)の実画面 (2026-10-04 時点)
各状態で「何をすべきか」の物差し UI 設計の定番の物差し、ニールセンの 10 Usability Heuristics を、状態 ごとに当てはめていく 1. システム状態の視認性 → 6. 記憶より認識 → Blank Loading / Partial 2. 実世界との一致 3. ユーザーの制御と自由 → Ideal 4. 一貫性と標準 5. エラーの防止 7. 柔軟性と効率性 8. 美的で最小限のデザイン 9. エラーからの回復支援 → Error 10. ヘルプとドキュメント Jakob Nielsen, "10 Usability Heuristics for User Interface Design"(1990 年提唱・1994 年改訂)
1 概念について 2 Five UI States とは 本題 AI 機能で、5 状態はどう変わるか 3 Flutter での設計 sealed class と、Partial の書き方 4 まとめ AI 機能の UI と、どう向き合うか
この順に見ていく Blank → Loading → Partial → Error → Ideal
前提:AI 機能は毎回ちがう答えを返す 今までのシステム 同じ入力なら、 いつも同じ出力 正しい出力を、先に書ける いまどの状態か、 システムが判定できる 生成モデル 数十億のパラメータで、 確率的に答えを引く 中身も長さも、 返るまで分からない 良い答えか、 システムには判定しにくい
前提:比べるのは自作のデモアプリ 旅行の相談ができる AI チャット Flutter で自作(AI の応答はモック) 同じ画面を、2 通りの作り方で比べる before:従来の作り方のまま after:AI 機能向けに直したもの 右上の切り替えで before / after を見せる
Blank:まだ中身がない状態 Blank Loading Partial Error Ideal AI 機能では、何ができるかを伝える場所になる 従来 AI 機能 「まだ投稿がありません」 できることの範囲が画面から見て 取れる 後回しにされやすい 空のテキストフィールドが 1 つあ るだけ できることの幅が広すぎて、逆に 分からない 「で、これは何をしてくれるの?」
Blank の before / after before(従来の作り方のまま) 空の入力欄だけ Blank Loading Partial Error Ideal
Blank の before / after before(従来の作り方のまま) 空の入力欄だけ Blank Loading Partial Error Ideal after(AI 機能向けに直した) できることとプロンプト提案
Loading:結果を待っている状態 Blank Loading Partial Error Ideal AI 機能では、数百 ms から数十秒に延びる 従来 AI 機能 数百 ms 〜 数秒 スピナー 1 個で足りた 待たせていると意識しなくてよか った 10 秒〜数十秒(Deep Research な ら数分) スピナーだけだと、固まったよう に見える 待ち時間そのものを設計する必要 がある
待ち時間を設計する 3 つの工夫 Blank Loading Partial Error Ideal 1. 何をしているか見せる 「検索中…」「3 件の資料を読んでいます」 ChatGPT / Deep Research の経過ログ 2. 進捗を見せる ステップ表示・スケルトンスクリーン(Skeleton Screen) 中身の形を先に灰色で見せる。「終わりが見える」だけで体感が変わる 3. 途中で抜けられる キャンセル可能にする Nielsen #3「ユーザーの制御と自由」
Loading の before / after before:スピナーだけで 25 秒 Blank Loading Partial Error Ideal
Loading の before / after before:スピナーだけで 25 秒 Blank Loading Partial Error Ideal after:何をしているか・経過時間・停止
Blank Loading Partial Error Ideal Partial:空ではないがまだまばらな状態 AI 機能では、「少ない」から「増えていく」に変わる 従来 AI 機能 登録直後で投稿が 1 件だけ プロフィールが埋まっていない 中身の「量」が足りない状態 ストリーミング応答 未完成のまま、数十秒更新され続 ける ユーザーがいちばん長く見る画面
Partial の before / after before:全部そろうまでスピナー Blank Loading Partial Error Ideal
Partial の before / after before:全部そろうまでスピナー Blank Loading Partial Error Ideal after:届いた分から表示・途中で止められる
Blank Loading Partial Error Ideal Loading を Partial に変える ストリーミングなし Loading 25s → Ideal 25 秒間、何も起きない
Blank Loading Partial Error Ideal Loading を Partial に変える ストリーミングなし ストリーミングあり Loading 25s → Ideal Loading 5s → Partial 20s → Ideal 25 秒間、何も起きない 5 秒で「動き出した」と伝わる
Blank Loading Partial Error Ideal Loading を Partial に変える ストリーミングなし ストリーミングあり Loading 25s → Ideal Loading 5s → Partial 20s → Ideal 25 秒間、何も起きない 5 秒で「動き出した」と伝わる 待ち時間が同じでも、 状態の切り分け方で体感が変わる
Blank Loading Partial Error Ideal Error:失敗した状態 AI 機能では、途中まで出てから起きる 従来 AI 機能 通信断 / 404 / 権限なし 一瞬で失敗する 失敗した時点では、まだ何も出て いない タイムアウト / レート制限 / 課金 上限 回答の品質が低い(形が崩れてい る・中身が空) 途中まで出てから、失敗する
失敗から立て直す導線 Blank Loading Partial Error Ideal 失敗しても、そこから立て直す回復の導線を用意する。回答の品質が低い ときも、同じ導線が使える。 もう一度生成する 入力を編集して再送する フィードバックを送る 途中で止める 同じ質問でも毎回答えが変わるので、やり直 最初からやり直さず、会話の文脈を残したま すだけで直ることがある ま直せる 「これは間違い」とユーザーが伝えられる場 明らかに違う方向に進んだら、最後まで待た 所を用意する せない
Error の before / after before:エラーコードだけ Blank Loading Partial Error Ideal
Error の before / after before:エラーコードだけ Blank Loading Partial Error Ideal after:途中を残して再生成・編集へ
Blank Loading Partial Error Ideal Ideal:意図通りに中身が表示できている状態 AI 機能では、正解が 1 つとは限らなくなる 従来 AI 機能 同じ入力 → 同じ画面 「正しい表示」を定義できる テストで検証できる 同じ入力 → 毎回違う結果 良し悪しを決めるのはユーザー ただし、すべてがそうではない
Ideal の before / after before:回答 1 つで終わり Blank Loading Partial Error Ideal
Ideal の before / after before:回答 1 つで終わり Blank Loading Partial Error Ideal after:再生成・編集・良い/悪いを返せる
AI 機能で 5 つの状態はこう変わる 状態 Blank 従来のアプリ 「まだ何もありません」 AI 機能 自由度が高いぶん、説明の意義が上がる → プロンプト提案 数十秒。待ち時間そのものを設計 Loading 数百 ms。スピナー 1 個 → 進捗・経過・キャンセル 生成中に少しずつ増え、いちばん長く見られる Partial 中身が少ない(量の話) → ストリーミング Error 一瞬で失敗。何も出ていない 途中まで出て失敗する・品質が低い → 途中を残す・回復導線・コスト設計 Ideal 正解は 1 つ。テストできる 正解が一意でないことがある → 複数案・再生成・ユーザーが確定する
AI 機能で 5 つの状態はこう変わる 状態 Blank 従来のアプリ 「まだ何もありません」 AI 機能 自由度が高いぶん、説明の意義が上がる → プロンプト提案 数十秒。待ち時間そのものを設計 Loading 数百 ms。スピナー 1 個 → 進捗・経過・キャンセル 生成中に少しずつ増え、いちばん長く見られる Partial 中身が少ない(量の話) → ストリーミング Error 一瞬で失敗。何も出ていない 途中まで出て失敗する・品質が低い → 途中を残す・回復導線・コスト設計 Ideal 正解は 1 つ。テストできる 正解が一意でないことがある → 複数案・再生成・ユーザーが確定する 画面を作るときは、この表で抜けがないかを確かめられる
1 概念について Five UI States とは 2 本題 3 AI 機能で、5 状態はどう変わるか Flutter での設計 sealed class と、Partial の書き方 4 まとめ AI 機能の UI と、どう向き合うか
sealed class で 5 状態をモデリング sealed class AiUiState Blank AiEmpty 頼み方の例 Loading AiLoading Partial AiPartial Error AiFailed Ideal AiIdeal いまの作業 届いた分 失敗の種類 完成した回答 経過時間・進捗 止めたかどうか 途中までの出力
sealed class で 5 状態をモデリング sealed class AiUiState { const AiUiState(); } :何を入力すればいいか分からないユーザーに、頼み方の例を見せる /// Blank final class AiEmpty extends AiUiState { const AiEmpty({required this.suggestedPrompts}); final List<String> suggestedPrompts; } :いま何をしているかを持たせる 「宿を /// Loading final class AiLoading extends AiUiState { final List<String> steps; // 24 final Duration elapsed; final double? progress; // null final bool isCancellable; } 件比べています」 なら不確定
sealed class で 5 状態をモデリング final class AiPartial extends AiUiState { final String textSoFar; final List<String> surfaceIds; // final bool stoppedByUser; // } 組み立て中の画面 止めても失敗にしない final class AiIdeal extends AiUiState { final String text; final List<String> surfaceIds; } final class AiFailed extends AiUiState { // Error final AiFailureKind kind; // network timeout lowQuality final String partialText; // } ・ ・ 途中までの出力は残す デモアプリ demo/ と同じクラス(フィールドは抜粋。書き方は A-23〜A-25、テストは A-8)
Partial を Flutter で書く Blank Loading Partial Error Ideal 届いた部品から順に表示する AI が返した「部品の指示」から Widget を組み立てる。 部品が届くたびに、画面が組み上がる。 旅程のカードが 1 枚ずつ増える 途中で止められる
1 概念について Five UI States とは 2 本題 AI 機能で、5 状態はどう変わるか 3 Flutter での設計 4 sealed class と、Partial の書き方 まとめ AI 機能の UI と、どう向き合うか
なぜ AI 機能の UI は難しいのか
なぜ AI 機能の UI は難しいのか 出力が確率的なので、どんな答えがいつ返ってくるかが、前もっ て分からない
なぜ AI 機能の UI は難しいのか 出力が確率的なので、どんな答えがいつ返ってくるかが、前もっ て分からない Blank Loading Partial Error Ideal AI が入っても、状態の種類はこの 5 つのまま
待たされると不安になり、 失敗すれば理由を知りたくなるのは昔と同じ 画面の作り方は変わっても 人の感じ方から生まれた原則はそのまま使える
AI 機能を作り始める前に つい、こう考えはじめる 「このモデルで 何ができるだろう」
AI 機能を作り始める前に つい、こう考えはじめる 「このモデルで 何ができるだろう」 その前に 「ユーザーは、 どの状態をたどって操作するか」
AI 機能を作り始める前に つい、こう考えはじめる 「このモデルで 何ができるだろう」 その前に 「ユーザーは、 どの状態をたどって操作するか」 実装を始める前に、 5 つの状態とそのつなぎ方を書き出してみてください
ありがとうございました 懇親会や X(@pregum_fox)で、気軽に声をかけてください コードは GitHub で公開中 github.com/Pregum/five-ui-states-flutter-demo
Appendix (補足資料)
A-1. Stream を Partial 状態に変換する
(抜粋)
// demo/lib/agent/travel_agent.dart
Future<void> _onSend(ChatMessage message) async {
try {
await for (final event in backend.run(prompt: lastPrompt, cancel: cancel, ...)) {
switch (event) {
case AgentStep(:final label, :final progress):
_steps.add(label);
// Loading
_progress = progress;
if (!_receiving) _emitLoading();
case AgentChunk(:final text):
_receiving = true;
_transport.addChunk(text);
// GenUI
A2UI
_emitReceiving();
// Partial
}
}
} on AgentException catch (e) {
_error = e;
}
}
:いま何をしているか
が
を解析して画面を育てる
void _finish() {
if (_error case final e?) return _set(AiFailed(partialText: _text, ...)); //
if (cancelled) return _set(AiPartial(stoppedByUser: true, ...)); //
Partial
if (_surfaceIds.isEmpty) return _set(AiFailed(kind: AiFailureKind.lowQuality, ...));
_set(AiIdeal(text: _text, surfaceIds: List.of(_surfaceIds), ...));
}
止めたら
途中を残す
A-2. UI 側の switch
(抜粋)
// demo/lib/ui/states_after.dart
return switch (state) {
AiEmpty(:final suggestedPrompts) =>
_EmptyView(suggestions: suggestedPrompts, onTap: onSuggestion),
AiLoading(:final steps, :final elapsed, :final progress) => _Scroll([
_StepList(steps: steps, elapsed: elapsed, progress: progress),
const Skeleton(), const Skeleton(),
]),
AiPartial(:final textSoFar, :final surfaceIds, :final stoppedByUser) => _Scroll([
Text(textSoFar), ...surfaces(surfaceIds),
if (stoppedByUser) _Banner(actions: [
,
]),
]),
AiIdeal(:final text, :final surfaceIds) => _Scroll([
Text(text), ...surfaces(surfaceIds), _IdealActions(...),
]),
AiFailed(:final kind, :final partialText, :final surfaceIds) => _Scroll([
Text(partialText), ...surfaces(surfaceIds), _Banner(kind: kind, ...),
]),
};
再生成 編集して再送
sealed class なので、状態を足すと コンパイルエラーで漏れが分かる
A-3. 画面全体の状態と部分の状態は別物
1 画面に非同期処理がいくつもあるのは、よくあることです。全部を 1 つの UiState に入れると扱いにくくな
ります。
画面全体の状態(ページ単位)
部分の状態(ウィジェット単位)
初期ロード、認証切れ、致命的エラー。
画面ごと差し替わるものだけを持たせる。
セクションごとの取得、AI 生成、いいね送信。
その部分だけが差し替わる。
class ChatPageState {
final PageStatus page;
//
final AiUiState generation;
// AI
final AsyncValue<List<Doc>> sources; //
}
画面全体(初期ロード・認証切れ など)
生成部分
参照資料リスト(別 API・別タイミング)
片方が Loading でも、もう片方は Ideal を出し続けられる。「画面が丸ごとスピナーになる」を避けるための分割です。
A-4. エラーの受け口をどこに置くか 状態に落とし込む前に、例外をどこで捕まえるかを決めておく必要があります。 Result 型に寄せる 例外を投げず、成功/失敗を戻り値で返す。 握りつぶしをコンパイル時に見つけやすい。 try - catch のまま扱う 既存コードに馴染む。 catch し忘れを仕組みで見つけることはできない。 実プロダクトでの一例:ルートに共通のエラーハンドラを挟む ページのルートにエラーハンドリング用の Widget を 1 枚かぶせておき、 API 由来の例外をそこで捕まえてエラーコードを取り出し、共通 のダイアログ/画面として表示する。 個別の画面に同じ catch を書かずに済み、どの画面でも同じ見え方でエラーを出せます。 このとき、AI 特有の失敗(低品質・途中終了)は共通ハンドラでは判定できないので、その部分だけ画面側の状態として持つ、という住み分けになります。
A-5. 今日の話の範囲(アプリ側とサーバー側) 本編で扱ったのは、アプリ(クライアント)側の状態設計です。 実際のプロダクトでは LLM を直接叩かず、自社サーバーを挟む構成が一般的で、そちらにも設計判断がありま す。 サーバーを挟む理由 UI 設計に効いてくる点 API キーを端末に置かない/プロンプトを差し替え可能にする/課 ストリーミングを SSE / WebSocket のどちらで流すか/中断時にサ 金とレート制限を集約する/監査ログを残す ーバー側の生成も止まるか/リトライをどちら側で持つか 注意したいこと アプリ側でキャンセルしても、自社サーバーが LLM からの受信を続けていれば、最後まで生成・課金されます。 「キャンセルできる UI」 を出す以上、サーバー側でも LLM との接続を切るところまで中断が伝わる設計になっているかを確認しておく必要があります(実測は次 のスライド)。
A-5b. 途中で切ると課金はどうなるか(実測) OpenAI Responses API(gpt-6-luna、推論なし)に「1 から 1200 まで書いて」と頼み、最初の差分から決め た秒数で subscription.cancel() 。各 3 回。 中断のタイミング 受け取った量 課金(利用量 API) 最後まで受信 1199 行・2604 トークン 2604 トークン × 3 回 最初の差分/1 秒後/2 秒後/6 秒後 0〜約 340 トークン 15 回とも 0 2 秒後 + client.close() 約 340 トークン 0 完了前に接続を切ると、受け取った分も含めて計上されなかった(2026-09-29 時点、利用量 API で確認)。 ただし仕様で保証されたふるまいではない。モデル・推論の有無・ほかのサービスでは変わりうるので、使うサービスで確かめる。 副産物:約 155 行までは少しずつ届き、そのあと約 10 秒無音、残りが最後にまとめて届いた。ユーザーには「固まった」ように見える時間がある。 実験コード: experiments/cancel_billing/
A-6. ストリーミング中の再描画コスト Partial を毎チャンク流すと、1 秒に数十回 build が走ります。 チャンクをまとめる 再描画範囲を絞る Markdown を毎回パースしない 自動スクロールを止める条件 16ms(1 フレーム)単位でバッファして emit する 確定した段落はキャッシュし、末尾だけ再パース テキスト部分だけを別 Widget に切り出す ユーザーが手動でスクロールしたら追従をやめる
A-7. 「低品質な応答」をどう扱うか アプリ側で品質判定できる範囲は限られています。それでも打てる手はあります。 構造で弾く:JSON スキーマを強制し、パース失敗を Error として扱う 出典を出す:根拠 URL を並べ、ユーザー自身が検証できるようにする 不確実性を表示する:「誤っている可能性があります」を UI に常設する フィードバックを集める:評価 UI は改善に使えるうえ、ユーザーが「違う」と伝えられる場所にもなる
A-8. 状態設計のテスト
ストリーミングが
の順で流れる', () async {
こんにちは').toList();
test('
Loading → Partial* → Ideal
final states = await notifier.generate('
expect(states.first, isA<AiLoading>());
expect(states.whereType<AiPartial>(), isNotEmpty);
expect(states.last, isA<AiIdeal>());
});
キャンセルすると
ではなく
で終わる
test('
Failed
Partial
', () async {
final states = await runAndCancelMidway();
expect(states.last, isA<AiPartial>());
expect((states.last as AiPartial).isCancellable, isFalse);
});
AI の出力内容はテストできませんが、状態遷移はテストできます
A-9. Blank の実プロダクトの例 ChatGPT(OpenAI)/ 2026-10-04 現在 Claude(Anthropic)/ 2026-10-04 現在 どちらも、挨拶と入力欄だけを置いている。頼み方の例は出していない。 大手のチャットは使い方を知っている人が多いので、これで足 りる。 ただ、初めて触る人が多い自分たちの AI 機能では、それでは足りません。デモアプリの after のように、できることと頼み方の例を出しま す(Nielsen #6「記憶より認識」)。
A-10. Loading の実プロダクトの例 リサーチしています… 2 分 12 秒 12 件の情報源を検索しました 5 件の記事を読みました 内容を突き合わせています… 参照中: example.com / docs.example.jp / blog.example.org 完了したら通知します。この画⾯を閉じても構いません バックグラウンドで実⾏中 停⽌ 経過ログ + 経過時間 + 中断⼿段 ChatGPT Deep Research(OpenAI)/ 再現図 Claude 思考中(Anthropic)/ 2026-10-04 現在 Deep Research は経過ログ・経過時間・参照中のソース・停止手段を全部出す。数分かかるものはバックグラウンド実行+完了通知に逃が す。 Claude は思考過程を折りたたみで見せる。どちらも「内部で何が起きているか」を可視化している。 Labor Illusion と、システム状態の視認性(Nielsen #1)の例です。
A-10b. ただし速ければいいわけでもない CHI 2026 の研究より 待ち時間 2 秒 / 9 秒 / 20 秒 で LLM の応答を比べたところ、 2 秒で返ってきた回答のほうが、思慮深さ・役立ち度をやや低く評価された Tan, Messerschmidt, Yin & Nov, "The Impact of Response Latency and Task Type on Human-LLM Interaction and Perception"(CHI 2026) 米国の成人 240 人(Prolific)・GPT-4o。最初の 1 文字までの待ち時間を固定し、そのあとはストリーミング表示
A-10b. ただし速ければいいわけでもない CHI 2026 の研究より 待ち時間 2 秒 / 9 秒 / 20 秒 で LLM の応答を比べたところ、 2 秒で返ってきた回答のほうが、思慮深さ・役立ち度をやや低く評価された ユーザーは待ち時間を「AI が考えている時間」として解釈していた。 (ただし長いと、14% の人が「通信が切れた?」と不具合を疑った) Tan, Messerschmidt, Yin & Nov, "The Impact of Response Latency and Task Type on Human-LLM Interaction and Perception"(CHI 2026) 米国の成人 240 人(Prolific)・GPT-4o。最初の 1 文字までの待ち時間を固定し、そのあとはストリーミング表示
A-10b. ただし速ければいいわけでもない CHI 2026 の研究より 待ち時間 2 秒 / 9 秒 / 20 秒 で LLM の応答を比べたところ、 2 秒で返ってきた回答のほうが、思慮深さ・役立ち度をやや低く評価された ユーザーは待ち時間を「AI が考えている時間」として解釈していた。 (ただし長いと、14% の人が「通信が切れた?」と不具合を疑った) AI では、速ければよいとは限らない Tan, Messerschmidt, Yin & Nov, "The Impact of Response Latency and Task Type on Human-LLM Interaction and Perception"(CHI 2026) 米国の成人 240 人(Prolific)・GPT-4o。最初の 1 文字までの待ち時間を固定し、そのあとはストリーミング表示
A-11. Partial の実プロダクトの例
1
Stream<AiUiState> generate(prompt) {
2
yield const AiUiState.loading();
3
4
final buffer = StringBuffer();
5
await for (final chunk in _client.stream(prompt)) {
6
buffer.write(chunk);
7
8
yield AiUiState.partial(buffer.toString());
}
Tab 確定
Esc 却下
薄い⽂字で候補を出し、確定と却下をキー 1 つで選ばせる
ChatGPT 生成中(OpenAI)/ 2026-10-04 現在
GitHub Copilot(GitHub)/ 再現図
ChatGPT は、生成が始まると送信ボタンが同じ位置のまま停止ボタンに入れ替わる。ボタンを増やさずに役割だけを切り替えるので、迷
わずに止められる。 Copilot は薄い文字で候補を出し、確定と却下をキー 1 つで選ばせる。
どちらも、生成の途中でユーザーが操作できるようにしています(Nielsen #3「ユーザーの制御と自由」)。
A-12. Error の実プロダクトの例 メッセージの上限に達しました このモデルの利⽤は 15 30 以降に再開できます。 それまでは、別のモデルに切り替えて続けられます。 別のモデルで続ける プランを⾒る ➀ 何が起きたか ➁ いつ戻るか ➂ 今すぐ取れる代替⼿段 …以上が前半の3項⽬です。続いて後半の項⽬について説明 します。4つ⽬は状態の分割で、これは画⾯全体と部分を 分けて持つという話にな (ここで途切れた) 応答の最⼤⻑に達しました ここまでの内容は残ります。続きから⽣成を再開できます。 続きを⽣成 途中まで出たものを消さない = 失敗を「途中まで成功」として扱う 「失敗しました」で終わらせず、復帰時刻と次の⾏動まで出す Partial のまま⽌めて、再開の導線を置く ChatGPT 利用上限(OpenAI)/ 再現図 Claude 応答長の上限(Anthropic)/ 再現図 上限系のエラーでは、何が起きたか、いつ戻るか、いま使える代わりの手段が出る。 応答長で切れた場合も途中までの出力を消さず、続 きから再開させる。 Nielsen #9「エラーからの回復支援」にあたります。
A-13. Ideal の実プロダクトの例 回答案 2/3 再⽣成 状態を 5 つに分けて考えると、実装の抜けが減ります。とくに Partial は⾒落とされやすく、ここを設計するかどうかで体験が ⼤きく変わります。 この考え⽅は 2015 年に提唱されたフレームワークです。 5 つの状態からなり、 国内では 2018 年ごろから普及し ました。 裏付けが⾒つかった 確認できなかった (案 1 はより簡潔、案 3 はコード例つき) 回答を再確認する コピー ↑ アプリ側で品質は判定できない。 だから ユーザー⾃⾝が検証できる⼿段 を⽤意する。 ↓ 正解が 1 つに決まらないなら、複数持って選ばせる 「気づけないエラー」に対する、プロダクト側の回答 Gemini 回答案の切り替え(Google)/ 再現図 Gemini 回答の再確認(Google)/ 再現図 回答案の切り替えは正解が一意でないことに、別の手段での裏取りは中身が正しいとは限らないことに応えています。 どちらも、回答が出たあとにユーザーが次の操作を選べるようにしています。 各プロダクトの画面は頻繁に変わります。登壇前に実機で確認してください。
A-14. チェックリストで終わらせない つい、こうなりがち 「Blank も書いたし、Error も書いた。よし、漏れな し」 状態を数え上げたところで終わってしまう 原典では "these states don't exist in a vacuum" として、各状態を一続きの体験として設計するよう説いています。
A-14. チェックリストで終わらせない つい、こうなりがち 「Blank も書いたし、Error も書いた。よし、漏れな し」 状態を数え上げたところで終わってしまう Hurff が言っていること 「これらの状態は真空の中には存在しない」 状態から状態への移り変わりまで設計する、という 考え方 原典では "these states don't exist in a vacuum" として、各状態を一続きの体験として設計するよう説いています。
A-14. チェックリストで終わらせない つい、こうなりがち 「Blank も書いたし、Error も書いた。よし、漏れな し」 状態を数え上げたところで終わってしまう Hurff が言っていること 「これらの状態は真空の中には存在しない」 状態から状態への移り変わりまで設計する、という 考え方 AI 機能では止める・やり直すが加わるので、移り変わりの設計が増えます 原典では "these states don't exist in a vacuum" として、各状態を一続きの体験として設計するよう説いています。
A-15. なぜ「プロンプト提案」なのか Recall(想起) 「何でも聞いてください」 ユーザーはゼロから思い出して考えないといけない ので、負担が大きい。 NN/g "10 Usability Heuristics for User Interface Design"(#6)/ "Recognition vs. Recall in User Interfaces" nngroup.com/articles/ten-usability-heuristics/ ・ nngroup.com/videos/recognition-vs-recall/
A-15. なぜ「プロンプト提案」なのか Recall(想起) 「何でも聞いてください」 Recognition(認識) 「例:この PDF を要約して」 ユーザーはゼロから思い出して考えないといけない ので、負担が大きい。 ユーザーは見て選ぶだけで済む。「あ、こういうこと ができるのか」と気づける。 NN/g "10 Usability Heuristics for User Interface Design"(#6)/ "Recognition vs. Recall in User Interfaces" nngroup.com/articles/ten-usability-heuristics/ ・ nngroup.com/videos/recognition-vs-recall/
A-15. なぜ「プロンプト提案」なのか Recall(想起) 「何でも聞いてください」 Recognition(認識) 「例:この PDF を要約して」 ユーザーはゼロから思い出して考えないといけない ので、負担が大きい。 ユーザーは見て選ぶだけで済む。「あ、こういうこと ができるのか」と気づける。 Nielsen #6 「記憶より認識(Recognition rather than Recall)」 人間は「思い出す」より「見て選ぶ」ほうが得意。プロンプト提案は、その応用です。 NN/g "10 Usability Heuristics for User Interface Design"(#6)/ "Recognition vs. Recall in User Interfaces" nngroup.com/articles/ten-usability-heuristics/ ・ nngroup.com/videos/recognition-vs-recall/
A-16. チャット UI 以外でも同じ 5 状態 AI 機能 この機能ならではの現れ方 画像生成 Partial=粗い絵から少しずつ鮮明に/Ideal=複数枚出して選ばせる/Error=ポリシー違反で生成拒否 カメラ読み取り レシート・名刺・翻 Blank=撮影ガイド枠そのもの/Error=ピンボケ・光量不足で再撮影を促す/Ideal=認識結果を編集させてか ら確定 訳 音声入力・文字起こ Partial=暫定テキストが確定テキストに置き換わる/Loading=録音中の波形表示 し 検索の AI 要約 検索結果は先に出る=一部は Ideal、要約は後から追いつく=一部は Loading
A-16. チャット UI 以外でも同じ 5 状態 AI 機能 この機能ならではの現れ方 画像生成 Partial=粗い絵から少しずつ鮮明に/Ideal=複数枚出して選ばせる/Error=ポリシー違反で生成拒否 カメラ読み取り レシート・名刺・翻 Blank=撮影ガイド枠そのもの/Error=ピンボケ・光量不足で再撮影を促す/Ideal=認識結果を編集させてか ら確定 訳 音声入力・文字起こ Partial=暫定テキストが確定テキストに置き換わる/Loading=録音中の波形表示 し 検索の AI 要約 検索結果は先に出る=一部は Ideal、要約は後から追いつく=一部は Loading チャット以外の機能でも、状態ごとに考えると設計しやすくなります
A-17. 状態の切り分け方は道具を選ばない デモアプリは ChangeNotifier で書いていますが、 Riverpod でも Bloc でも signals でも、この 5 状態の切り分け方は変わりません。
A-17. 状態の切り分け方は道具を選ばない デモアプリは ChangeNotifier で書いていますが、 Riverpod でも Bloc でも signals でも、この 5 状態の切り分け方は変わりません。 Android で MVI + UiState を書いたことがある方は、ほぼ同じ発想です。 画面の状態を 1 つの型で表して、UI はそれを描画するだけにする。
A-17. 状態の切り分け方は道具を選ばない デモアプリは ChangeNotifier で書いていますが、 Riverpod でも Bloc でも signals でも、この 5 状態の切り分け方は変わりません。 Android で MVI + UiState を書いたことがある方は、ほぼ同じ発想です。 画面の状態を 1 つの型で表して、UI はそれを描画するだけにする。 デモアプリのコードは github.com/Pregum/five-ui-states-flutter-demo にあります。 5 つの状態の型は lib/state/ai_ui_state.dart 、状態の切り替えは lib/agent/travel_agent.dart で す。
A-18. 調査メモ:チャットの外の Five UI States 見直し用メモ(2026-09-26 調査)。製品ごとの詳細と出典は、手元の調査メモにまとめています。 既存の分析 関連ガイドライン 5 状態を非チャットの AI 機能に当てはめた公開分析 状態ごとの指針は Apple HIG(Generative AI)・ は見つからなかった。LT で独自に示せる部分にな Google PAIR・Microsoft HAX に散らばっている る (A-20) 非チャットの Ideal:4 つの型 複数案から選ぶ 確認・修正して保存 受け入れるまで未確定 大きく変わるのは Ideal チャットは「再生成・複数案」。非チャットは「AI の出力は下書き。ユーザーが確定する」が中心 出典で裏取り 画像生成・写真編集 カメラ読み取り・音声 コード補完・文章作成 検索・要約 Midjourney 4 枚、Photoshop 3 案、 Expensify の自動入力+編集、iOS 音 Copilot / Cursor の Tab・Esc、Apple Google の AI による概要、 Vary・Undo 声入力の下線 文章ツールの元に戻す Perplexity、Amazon のレビュー要約 共通点:AI の出力を「確定前」として見た目で区別する(ゴーストテキスト・下線・斜体・AI 生成ラベル)
A-19. 調査メモ:5 状態 × ジャンルの対応表 状態 Ideal 画像生成・写真編集 複数案から選ぶ。派生・部分修正・ Undo。透かしで AI 生成を明示 カメラ読み取り 読み取った項目を画像と並べて確 認・編集してから保存 音声・文字起こし 話者ラベルや語句を後か ら修正。自信のない語に 下線 Blank 範囲指定(タップ・ブラシ・囲む)が 「近づいて」「ゆっくり」など構え マイク待機の表示 入口。消す候補を自動ハイライト 方のコーチング Loading % 表示。数分かかるものは裏で生成 「SmartScanning」。保存は先に済 ライブ中は編集不可、処 ませる 理後に編集 Error 失敗しやすい箇所を事前に明記。ポリ ぼけ・暗さ・反射を示して撮り直 もっともらしい誤認識。 シーのブロックは行き止まりになりが し。失敗時は手入力へ 下線で疑う場所を示す ち Partial 生成途中の画像が見える 認識した項目をハイライト。見続け 暫定テキストが確定テキ るほど精度が上がる ストに書き換わる コード補完・文章作成 Tab で受け入れ、Esc で 拒否。トーン変更・元に 戻す 何も出さない。控えめな 入口 専用の待ち表示はほぼな い 確定前なので無視すれば 済む。一時停止できる 検索・アプリ内要約 出典リンクで裏取り。元デ ータへの絞り込み 役に立つと判断したときだ け出す 議事メモは会議後に生成し メールで届ける 自信がなければ出さない。 要約をオフにする手段をそ の場に ゴーストテキスト。単 途中参加者向けの「ここま 語・行単位で部分受け入 での要約」 れ
A-20. 調査メモ:既存ガイドラインとの対応 Apple HIG(Generative AI / Machine learning) Google People + AI Guidebook(PAIR) Microsoft HAX(CHI 2019) そのほか Loading:処理に時間がかかる前提で設計する。裏で生成して、ユーザーに は作業を続けてもらう。「処理中…」だけで済ませず、何をしているかを具 体的に出す Ideal:別バージョンを提示。編集・元に戻す・再試行を生成物の近くに Error:ハルシネーションを周知。ブロック時はうまくいく入力例を示す Blank:G1 何ができるか/G2 どの程度うまくできるか Error:G9 効率よく修正/G10 自信がないときは範囲を絞る/G11 なぜそう したか Ideal:G8 効率よく却下 Error:background errors = 誰も気づかない誤り(「もっともらしいが間違 い」にいちばん近い) Blank:できること・できないことを最初に伝える Ideal:信頼度が低いときは複数候補で判断させる NN/g「Prompt Suggestions」(2025):Blank の白紙問題への 3 つの対策 Shape of AI:入口の工夫(Blank)と制御(Ideal)のパターン集 AWS Cloudscape:Loading を「処理中」と「生成中」の 2 段階に ※「NN/g 調査で AI 生成ダッシュボードの 92% に空状態なし」は出典不 明。引用しない
A-21. AI 用に状態を足さないのはなぜか 足したくなる状態 入る場所 誰が決めるか 考え中・ツール実行中 Loading(いまやっていることを出す) システム 生成中(ストリーミング) Partial システム ユーザーが途中で止めた Partial(失敗にしない) ユーザー 自信がない・中身が使えない Error(出さない、も選択肢) システム/ユーザー 再生成して答えが変わった Ideal(複数案の 1 つ) ユーザー 実行前の承認待ち(エージェント) Ideal(確定の手前) ユーザー 実行したあと(通知・取り消し) 5 状態の外(時間経過) —
A-21. AI 用に状態を足さないのはなぜか 足したくなる状態 入る場所 誰が決めるか 考え中・ツール実行中 Loading(いまやっていることを出す) システム 生成中(ストリーミング) Partial システム ユーザーが途中で止めた Partial(失敗にしない) ユーザー 自信がない・中身が使えない Error(出さない、も選択肢) システム/ユーザー 再生成して答えが変わった Ideal(複数案の 1 つ) ユーザー 実行前の承認待ち(エージェント) Ideal(確定の手前) ユーザー 実行したあと(通知・取り消し) 5 状態の外(時間経過) — 足したくなる状態も、ほぼ 5 つのどれかに入ります
A-22. Loading と Partial の境目 Loading 出しているのは作業の様子 いま何をしているか、どれだけ経ったか Partial 出しているのは成果物の一部 未完成でも、読んで判断できる 画面 判定 いま止めたら 「宿を 24 件比べています」 Loading 残すものがない 思考の表示 Loading 流れていても、頼んだ答えではない 回答の本文が流れてくる Partial 読んだ分が残る 集めた出典が増えていく どちらにもなる 出典が欲しい人には残る
A-22. Loading と Partial の境目 Loading 出しているのは作業の様子 いま何をしているか、どれだけ経ったか Partial 出しているのは成果物の一部 未完成でも、読んで判断できる 画面 判定 いま止めたら 「宿を 24 件比べています」 Loading 残すものがない 思考の表示 Loading 流れていても、頼んだ答えではない 回答の本文が流れてくる Partial 読んだ分が残る 集めた出典が増えていく どちらにもなる 出典が欲しい人には残る いま止めたときに画面に残す価値があれば Partial です
A-23. Blank を Flutter で書く
Blank Loading Partial Error Ideal
頼み方の例を状態に持たせる プロンプト例は Blank 状態(コードでは AiEmpty )のフィールド。押せばそのまま送れるので、白紙から
考えさせない。
// ai_ui_state.dart
final class AiEmpty extends AiUiState {
const AiEmpty({required this.suggestedPrompts});
final List<String> suggestedPrompts;
}
// states_after.dart
AiEmpty(:final suggestedPrompts) => _EmptyView(
suggestions: suggestedPrompts,
//
onTap: onSuggestion,
//
),
// _EmptyView
「週末、箱根で温泉旅行を計画して」…
押したらそのまま送信
には「できること」「できないこと」も並べる
demo/lib/state/ai_ui_state.dart / demo/lib/ui/states_after.dart
できること・頼み方の例・できないこと
A-24. Error を Flutter で書く
Blank Loading Partial Error Ideal
途中までを残し、使えない応答も成功にしない 失敗しても、途中まで届いた文章は画面に残す。返ってきたのに画面が組めない応答も
「成功」にしない。
:終わり方で状態を決める
// travel_agent.dart
if (_error case final e?) {
_set(AiFailed(
kind: AiFailureKind.network,
partialText: _text,
//
surfaceIds: List.of(_surfaceIds), //
));
return;
}
//
if (_surfaceIds.isEmpty) {
_set(AiFailed(kind: AiFailureKind.lowQuality, ...));
return;
}
_set(AiIdeal(...));
途中までの文章を残す
途中までの画面も残す
返ってきたが画面が組めなかった = 成功にしない
demo/lib/agent/travel_agent.dart
途中を残し、もう一度生成・編集して再送
A-25. Ideal を Flutter で書く
Blank Loading Partial Error Ideal
やり直し・修正・評価の導線を置く 正解が 1 つとは限らないので、結果の近くに「やり直す」「直す」「良し悪しを伝える」を置く。
// states_after.dart
AiIdeal(:final text, :final surfaceIds, :final elapsed) => _Scroll([
UserBubble(prompt),
Text(text),
...surfaces(surfaceIds),
// GenUI
_IdealActions(
onRegenerate: onRegenerate, //
onEdit: onEdit,
//
),
//
]),
が組み立てた旅程と宿
再生成
依頼を直して再送
良い/違う の評価も並べる
// travel_agent.dart
Future<void> regenerate() => send(lastPrompt);
demo/lib/ui/states_after.dart / demo/lib/agent/travel_agent.dart
再生成・編集・良い/違う
A-26. デモ動画:スピナーだけで待つ 25 秒 待ち時間 25 秒
A-27. 調べるきっかけになれば Five UI States は、エンジニアが入りやすい観点の 1 つです。その先に、こんな観点があります。 観点 例 画面の状態 The UI Stack(Hurff, 2015) 操作の原則 10 Usability Heuristics(Nielsen, 1990/94) Illusion(裏の作業が見えると 人の心理・慣れ ヤコブの法則:ほかのアプリと同じ操作感を期待する/Labor 満足度が上がる)/CASA(人は機械にも、人と同じ社会的なルールを当てはめる) 時間 Response Time Limits(Nielsen, 1993) AI: First New UI Paradigm in 60 Years(NN/g, 2023):操作の手順を指示する UI から、欲 AI 固有 しい結果を伝える UI(意図ベース)へ AI 機能では 今日の話(Five UI States) 各状態で何をすべきかの物差し 「ChatGPT のように、送信が停止に変わ るはず」と期待される 数十秒は想定の外 欲しい結果だけを伝えるので、何を頼めば いいか(Blank)が難しくなる AI 機能を作るなら、UI/UX をもう一度学び直すと役に立つと思います
A-28. 誰のせいでもない 画面のどこが非同期で動くかは、 実装して初めてはっきりすることが多いから
A-29. 今回の話の対象 こんな方に向けて AI 機能の実装を控えている・改善したい UI の状態を体系的に考えたい デザインや UI は、つい二の次になりがち 今日話すこと AI 機能で、画面の 5 つの状態の意味がどう変わるかと、 Flutter での作り方 ※ここでの AI = 生成 AI(主に文章を生成する LLM) ※扱うのはアプリ側の状態設計(サーバー側は Appendix A-5)
A-30. AI 機能でとくに抜けやすいこと ・どれくらい待つのか ・途中経過を出せるのか ・どう失敗しうるのか ・やり直しにいくらかかるのか
A-30. AI 機能でとくに抜けやすいこと ・どれくらい待つのか ・途中経過を出せるのか ・どう失敗しうるのか ・やり直しにいくらかかるのか 状態が抜けていると、ユーザーは次に何をすればいいか迷う Five UI States は、 迷わない UI になっているかを確かめるための枠組み
A-31. 「Five UI States」 聞いたことありますか?
A-31. 「Five UI States」 聞いたことありますか? 聞いたことがある方も初めての方も、 まずは中身から見ていく
A-32. AI 以外の画面でも、そのまま使える AI 機能の話をするが、 この 5 つは普通の画面設計でもそのまま使える
A-33. Labor Illusion(労働の錯覚) Blank Loading Partial Error Ideal 裏で作業している様子を見せると、同じ待ち時間でも満足度が上がるという研究がある。AI の「思考中…」の 表示も、この効果を使っている例。 リサーチしています… 2 分 12 秒 12 件の情報源を検索しました 5 件の記事を読みました 内容を突き合わせています… 参照中: example.com / docs.example.jp / blog.example.org 完了したら通知します。この画⾯を閉じても構いません バックグラウンドで実⾏中 停⽌ 経過ログ + 経過時間 + 中断⼿段 ChatGPT Deep Research(OpenAI)/ 再現図 Buell & Norton「The Labor Illusion」(Management Science, 2011) Claude 思考中(Anthropic)/ 2026-10-04 現在
Blank Loading Partial Error Ideal A-34. Hurff の分類と、この LT の Partial Hurff によると Partial は 価値の一部を見せて理想の状態へ導く状態 Hurff の分類だと、生成途中の表示は Loading に近い この LT では、読めて止められるなら Partial として扱う
A-35. 正解を定義できる AI 機能もある Blank Loading Partial Error Ideal 分類・抽出・スキーマ固定の構造化出力・翻訳など、期待する形が決まっているものは従来通りテストでき る。 定義が難しいのは、要約・提案・文章生成のような「良い答えが一意に決まらない」タスクのほう。
A-35. 正解を定義できる AI 機能もある Blank Loading Partial Error Ideal 分類・抽出・スキーマ固定の構造化出力・翻訳など、期待する形が決まっているものは従来通りテストでき る。 定義が難しいのは、要約・提案・文章生成のような「良い答えが一意に決まらない」タスクのほう。 ユーザーに「選ばせる・やり直させる・確定させる」余地が要る
Blank Loading Partial Error Ideal A-36. アプリなら待ち時間を画面の外に逃がせる 生成は裏で続け、使える中身が出た(Partial)・完了したら、通知で画面に呼び戻す。 例:Claude の「応答したときに通知を受け取りますか?」、ChatGPT Deep Research の完了通知(どちらも 完了時の通知)
A-37. 便利なところも、画面に落とし込みにくいとこ ろも、この性質から来る 便利なところも画面に落とし込みにくいところも、 この性質から来る
A-38. やり直しにはお金がかかる リトライも再生成も、そのぶんトークンを消費する。 途中キャンセルの扱いはサービスによって違う(詳しくは A-5b)。 「失敗したら黙って自動リトライ」にすると、 ユーザーの利用料やレート制限を、 気づかないうちに使ってしまうことがある。 Blank Loading Partial Error Ideal
A-38. やり直しにはお金がかかる Blank Loading Partial Error Ideal リトライも再生成も、そのぶんトークンを消費する。 途中キャンセルの扱いはサービスによって違う(詳しくは A-5b)。 「失敗したら黙って自動リトライ」にすると、 ユーザーの利用料やレート制限を、 気づかないうちに使ってしまうことがある。 再試行の回数上限、キャンセル時にどこまで課金されるかの明示、失敗時に消費分を戻すか(アプリ内クレジット等)も、UI と一緒に決 めておく。
A-39. 実際のプロダクトの 5 つの状態 状態 Blank Loading Partial Error Ideal プロダクト Claude ChatGPT ChatGPT Claude ChatGPT 実際の表示 挨拶と入力欄だけを出す 読んだ資料の数と経過時間を流す 送信ボタンが停止ボタンに変わる 「送信に失敗しました」と再試行・破棄を出す 再生成した回答を切り替えて比べられる
A-39. 実際のプロダクトの 5 つの状態 状態 Blank Loading Partial Error Ideal プロダクト Claude ChatGPT ChatGPT Claude ChatGPT 実際の表示 挨拶と入力欄だけを出す 読んだ資料の数と経過時間を流す 送信ボタンが停止ボタンに変わる 「送信に失敗しました」と再試行・破棄を出す 再生成した回答を切り替えて比べられる どれも、5 つの状態のどれかに向けた工夫
A-40. Riverpod なら AsyncValue でも書ける
Riverpod 3 の AsyncValue も sealed class で、3 つの型に分かれる
return switch (ref.watch(answerProvider)) {
AsyncData(:final value) => AnswerView(value),
AsyncError(:final error) => ErrorView(error),
AsyncLoading() => const LoadingView(), // progress
};
で進み具合も持てる
簡単な画面なら、これで十分。Blank は data の中身で、Partial は data を流し続けることで表せる。
途中か完了か、止めたのか失敗かを見分けたくなったら、自前の sealed class に分ける。
riverpod 3.x(3.0 は 2025 年 9 月)。 AsyncValue.loading / data / error で作り、 AsyncLoading(progress: 0.5) のように進み具合も渡せる
A-41. Loading を Flutter で書く Blank Loading Partial Error Ideal 経過を「状態」に持たせる いま何をしているか・経過時間・進み具合を、Loading 状態のフィールドにする。 画面は switch で書くだけ。 経過ステップ・経過時間 進捗バー・スケルトン・停止
A-42. Loading を Flutter で書く
:届いたイベントを状態に写す
// travel_agent.dart
case AgentStep(:final label, :final progress):
_steps.add(label);
//
24
_progress = progress;
//
null
if (!_receiving) _emitLoading();
「宿を 件比べています」
分からなければ (不確定)
:状態ごとの
が、そのまま画面の設計図
// states_after.dart
switch
AiLoading(:final steps, :final elapsed, :final progress) => _Scroll([
UserBubble(prompt),
_StepList(steps: steps, elapsed: elapsed, progress: progress),
const Skeleton(height: 28),
//
const Skeleton(),
]),
中身の置き場所を先に見せる
demo/lib/agent/travel_agent.dart / demo/lib/ui/states_after.dart
Blank Loading Partial Error Ideal
A-43. 全画面のローディング UI を表示するだけで良 かった 全画面のローディング UI を 表示するだけで良かった
A-44. Five UI States = 1 つの画面が持つ 5 つの状態 画面を作るとき、つい「データがちゃんと表示された画面」だけを思い浮かべがち。 でも実際にユーザーが見る画面は、それだけではない。
A-45. 状態から状態へのつなぎ方まで設計する 5 つの状態を考え、状態から状態への つなぎ方まで設計するのが Five UI States 状態は領域ごとに持つ。 一部だけ読み込み中なら、そこが Loading で、ほかは Ideal。 Loading と Partial の境目は、中身がもう表示されているかどうか。
A-46. 応答時間には 3 つの限界がある Blank Loading Partial Error Ideal 0.1s 1.0s 10s 直接操作している感覚 でも遅延には気づく 他のことを始めてしまう 「即座」と感じる 思考は途切れない Jakob Nielsen "Response Times: The 3 Important Limits"(1993, Usability Engineering 第 5 章) 元は Miller (1968) と Card et al. (1991) の研究。30 年以上前の数字だが、今もよく使われている。 注意の限界
A-47. Partial を Flutter で書く : の断片を、部品の指示として解析する // travel_agent.dart LLM case AgentChunk(:final text): _receiving = true; _transport.addChunk(text); // A2uiTransportAdapter _emitReceiving(); // → AiPartial 届いた文章と部品を、 が解析する の中身に写す // Partial case ConversationContentReceived(:final text): _text += text; case ConversationSurfaceAdded(:final surfaceId): _surfaceIds.add(surfaceId); 止めたら、失敗ではなく「途中で終わった Partial」として残す // void stop() => _cancel .cancel(); demo/lib/agent/travel_agent.dart / Stream を状態に写す流れの全体は A-1・A-2 Blank Loading Partial Error Ideal
Appendix ここまで 他に気になることがあれば、なんでもどうぞ