Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Flutter_Kaigi_2025.pdf
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
須永高弘
September 17, 2026
2
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutter_Kaigi_2025.pdf
須永高弘
September 17, 2026
More Decks by 須永高弘
See All by 須永高弘
AI時代のエンジニアのキャリア戦略_CAREER SUMMIT 2026登壇資料
takahirosunaga
0
4
ドメインを組織の資産にする 少人数で複数プロダクトを支えるTypeScript運用の実践_TSKaigi2026登壇資料(須永高弘)
takahirosunaga
0
33
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
How to make the Groovebox
asonas
2
2.5k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
580
How to Think Like a Performance Engineer
csswizardry
28
2.8k
The Language of Interfaces
destraynor
162
27k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
510
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
280
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.8k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
590
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Transcript
顧客価値を実現するFlutter: SLO・KPIから考えるキャリア支援アプリのUI/UX設計 ©ASSIGN Inc. All Right Reserved.
自己紹介 須永 ⾼弘 Takahiro Sunaga 執⾏役員 CTO ⼤学卒業後、総合コンサルファームに⼊社し、 主に製造やメディアの顧客に対して 戦略、業務、ITに掛かる多様なプロジェクトを経験
• 若⼿転職⽀援アプリ「ASSIGN」の開発・運⽤、 LLM の事業活⽤、エンジニア組織開発等に従事 • ©ASSIGN Inc. All Right Reserved. 1
会社概要 ©ASSIGN Inc. All Right Reserved. 1
アプリの概要 ©ASSIGN Inc. All Right Reserved. 1
これまでの開発:Flutter導入の歴史 2018年6月~ SwiftでiOSアプリを開発・運用 2021年4月 AndroidアプリをFlutterで新規開発 省リソースでiOS、Andoroid両プラットフォーム展開 2022年12月 iOSアプリをSwiftからFlutterへ完全移行 開発体験とユーザー体験の一貫性を担保 ©ASSIGN
Inc. All Right Reserved. 1
これからの挑戦 足元の転職支援から長期的なキャリア支援プラットフォームへ ©ASSIGN Inc. All Right Reserved. 1
技術的な変更 ©ASSIGN Inc. All Right Reserved. 1
Flutterの本質的な価値 エコシステム全体で、プロダクト・体験を底上げする要素が揃っている!! ©ASSIGN Inc. All Right Reserved. 1
Flutterを再定義する ©ASSIGN Inc. All Right Reserved. 1
顧客価値をFlutterに繋げる ©ASSIGN Inc. All Right Reserved. 1
顧客の体験を実現するための3本柱 ©ASSIGN Inc. All Right Reserved. 1
技術的な柱①:信頼性 ユーザーからの信頼を獲得する設計 ©ASSIGN Inc. All Right Reserved. 1
信頼性:ユーザー情報を預けられる安心感 目指すユーザー体験 自身の情報を安心して入力・管理できる。 アプリから安定して情報や機会を獲得することができる。 追求する指標 (SLO・KPI) 1. セッションあたりのFatalエラー率 ≦ 0.1%
2. フォーム途中離脱の低減 定義:セッション中に発生するFatalエラーの割合 定義:フォーム入力中離脱ユーザー / 全ユーザー 目的:アプリの突然の終了を防ぎ、ユーザーの思考と作業 を中断させない 目的:自己分析や経歴入力を完了させ、その後の情報や提 案を受けられるようにする ©ASSIGN Inc. All Right Reserved. 1
なぜ Fatalエラー率 / session ≦ 0.1% を目指すのか? 内部データからの仮説 クラッシュ経験者は非経験者に比べ、リテンションが顕著に低下 クラッシュはアプリ使用の動機付けを直接的に奪う
©ASSIGN Inc. All Right Reserved. 1
なぜ Fatalエラー率 / session ≦ 0.1% を目指すのか? 外部ベンチマーク ストア品質基準では、一定のアプリクラッシュ率を超えると発見性や評価に悪影響 Google
Play Consoleでは、ユーザーが知覚するクラッシュ率が1.09%が不正な動作の閾値 先行事例でも 0.1%〜0.05% を目標に据えるケースが一般的 モバイルアプリのクラッシュフリーセッション率の 目標値は99.95%が基本的な基準であり、トップク ラスのアプリは99.99%を達成している。一方、 99.77%を下回るとユーザー離れのリスクが高ま る。 出典: alphabin, "Mobile App Crash Rate Benchmarks 2025: What's a Good Crash Rate?" ©ASSIGN Inc. All Right Reserved. 1
なぜフォーム離脱率に着目するのか? 内部データからの仮説 新卒向けアプリの自己分析ワークなど長文フォームの入力画面で、離脱率が高い傾向 入力が消えるかもしれないという不安や煩わしさが、再開のハードルに 選択回答形式の設問の 平均離脱率 1.5% ©ASSIGN Inc. All
Right Reserved. 長文フォーム形式の設問の 平均離脱率 11.8% 1
信頼性を実現する2つの設計パターン 追求するSLO・KPI 設計パターン Fatalエラー率 ≦ 0.1% ① レイヤーごとの適切なエラーハンドリング ② Driftによる入力の即時ローカル保存
フォーム離脱率の低減 ©ASSIGN Inc. All Right Reserved. 1
レイヤーごとの適切なエラーハンドリング 課題 Flutterの仕様上、UIスレッドで発生した多くの例外はフレームワーク内で捕捉され、アプリプロセスが即時 に終了することは少ない。 例外が発生しても「アプリが落ちない」ケースが多く、ユーザーには気づかれにくいが、実際にはUI破綻やロ ジック不整合が起きていることがある。 解決策 レイヤーごとに適したハンドリングを実装し、Unhandled Exceptionに発展させず、 修正可能な形で記録・可視化する。
©ASSIGN Inc. All Right Reserved. 1
レイヤーごとの適切なエラーハンドリング 基本的な考え方 区分 定義 想定内の例外 アプリ設計上、起こり得る制御可 状態で表現(AsyncValue / Result<T, Failure>)し、
能な例外(例:通信エラー、入力 UIで安全に処理 ミス) 想定外の例外 想定していない異常(例:Null参 try-catchで捕捉し、CrashlyticsへNon-fatalとして送 照、データ不整合、型不一致) 信(category付与) Unhandled Exception どのレイヤーでも捕捉されずアプ runZonedGuarded / PlatformDispatcher.onErrorで リ全体に伝搬 捕捉し、CrashlyticsへFatalとして送信 ©ASSIGN Inc. All Right Reserved. 対応方針 1
レイヤーごとの適切なエラーハンドリング レイヤー 主な責務 行うこと AsyncValue.whenで状態を網羅し、 ErrorWidget.builderでUI破綻を防ぐ 想定内はResultで返し、想定外はtry-catchで捕捉して UseCase層 業務ロジック category=logic_unexpectedとして送信
で通信やパースを保護し、 Repository層 通信・DBアクセス try-catch 想定外はcategory=infra_unexpectedとして送信 をtry-catchで捕捉し、 Platform層 ネイティブ連携 PlatformException 必要に応じてcategory=native_bridgeとして送信 UI層 表示・状態管理 ©ASSIGN Inc. All Right Reserved. 1
レイヤーごとの適切なエラーハンドリング ©ASSIGN Inc. All Right Reserved. 1
レイヤーごとの適切なエラーハンドリング Non-fatalで扱うCategoryの設定 カスタムキー 意味 category=ui_break UI破綻(ErrorWidget, AsyncValue のerrorなど) category=logic_unexpected ビジネスロジックの想定外
category=infra_unexpected 通信・DB・ファイルなどのインフラ例外 category=native_bridge Platform Channel/ネイティブ連携の例外 想定される修正内容の例 • レイアウト制約不足 • 非同期状態での不正Build • Null/空リスト想定漏れ • ドメインルール未考慮 • UseCase例外漏れ • 非同期競合による状態不整合 • APIスキーマ変更/デシリアライズ失敗 • ネットワーク切断・タイムアウト • キャッシュ不整合 • 戻り値型不一致 • PlatformException未捕捉 • スレッド制約違反 監視体制 Google Cloud Monitoring Alert fatal_rate >= 0.1% または ui_break >= 0.3% で通知 ©ASSIGN Inc. All Right Reserved. 1
Drift 課題 による入力の即時ローカル保存 長文フォーム入力中の 通信エラー/OS kill/画面遷移 で入力が失われうる。 消える不安が入力継続を阻害し、途中離脱率を押し上げる。 解決策 入力は
即時に Drift へ短期一時保存 Drift:Flutter向けのリアクティブなSQLiteラッパー(ローカルストレージ) 暗号化対応(sqlcipher統合)、型安全、ストリーム対応 一定期間のTTLまたはログアウトなどのアクションで未送信ドラフトを自動クリーンアップ。 ©ASSIGN Inc. All Right Reserved. 1
Drift による入力の即時ローカル保存 長文フォーム入力画面でも安心して中断できる体制を作る ©ASSIGN Inc. All Right Reserved. 1
Drift による入力の即時ローカル保存 1. 入力フォームからDriftへの保存 ポイント デバウンス保存: 入力500ms後に自動 保存(過度な書き込みを防止) フィールド単位: formId
+ fieldId で細 かく管理 復元機能: initState時に既存ドラフトを 読み込み TTL設定: 7日間の有効期限を付与 ©ASSIGN Inc. All Right Reserved. void _onTextChanged() { _debounce .cancel(); _debounce = Timer(Duration(milliseconds: 500), () async { await ref.read(draftRepositoryProvider).saveDraft( formId: 'self_analysis', fieldId: 'motivation', value: _controller.text, ttl: DateTime.now().add(Duration(days: 7)), ); }); } Future<void> _loadDraft() async { final draft = await ref.read(draftRepositoryProvider) .loadDraft(formId: 'self_analysis', fieldId: 'motivation'); if (draft != null) _controller.text = draft.value; } 1
Drift による入力の即時ローカル保存 2. API登録・更新成功時に削除 ポイント 成功時即削除: API成功後、該当フォー ムのドラフトを全削除 エラー時保持: 送信失敗時はドラフトを
残し、再試行を可能に 分析イベント: 送信成功・失敗を記録 try { // API await ref.read(apiServiceProvider).upsertSelfAnalysis(formData); へ送信 送信成功 ドラフトを削除 // → await ref.read(draftRepositoryProvider) .deleteDraftsByFormId('self_analysis'); analytics.logEvent('form_submit_success'); context.go('/self-analysis/complete'); } catch (e) { // showErrorDialog(' } エラー時はドラフトを保持(再試行可能に) 送信に失敗しました。時間を置いて再度お試しください。'); ©ASSIGN Inc. All Right Reserved. 1
Drift による入力の即時ローカル保存 3. TTLによる自動クリーンアップ ポイント TTL管理: 7日経過したドラフトを自動 Future<void> cleanupExpiredDrafts() async
{ final now = DateTime.now(); 削除 起動時クリーンアップ: アプリ起動時に 期限切れをチェック ログアウト時: 全ドラフトを削除 切れのドラフトを削除 // TTL final deletedCount = await (_database.delete(_database.formDrafts) ..where((tbl) => tbl.ttl.isSmallerThanValue(now)) ).go(); if (deletedCount > 0) { analytics.logEvent('draft_cleanup', parameters: {'deleted_count': deletedCount}); } } ログアウト時は全削除 // Future<void> cleanupAllDrafts() async { await _database.delete(_database.formDrafts).go(); analytics.logEvent('draft_cleanup', parameters: {'trigger': 'logout'}); } ©ASSIGN Inc. All Right Reserved. 1
技術的な柱②:応答性 ユーザーの今と好機に応える設計 ©ASSIGN Inc. All Right Reserved. 1
応答性:機会の鮮度を逃さない、即時的な反応 目指すユーザー体験 スムーズなUI反応や情報提示ができ、興味や動機をそのままにアプリを操作することができる。 追求する指標 (SLO・KPI) 1. INP(体感レイテンシ) ≤ 200ms 2.
TTIの短縮(目標 3,000ms以下) 定義:ユーザーが操作してUIが「反応した」と感じるまで の時間 定義:画面遷移 / プッシュ通知タップから目的画面が使え る状態になるまでの時間 目的:認識遅延を防ぎ、ユーザーの行動意欲が落ちる前に 次の情報を届ける 目的:通知タップ時の高いモチベーションを失わせず、機 会を確実に活かしてもらう ©ASSIGN Inc. All Right Reserved. 1
なぜ体感レイテンシ ≤ 200ms を目指すのか? 外部ベンチマーク Googleの推奨基準(INP: Interaction to Next Paint)では、ユーザーインタラクションから次の描画までの
応答時間は200ms以下が良好と提示 良好なレスポンシブネスを提供するためには、ペー ジ読み込みの75パーセンタイルで測定されるINPが 200ミリ秒以下であることが推奨される。 200ms〜500msは改善が必要、500ms以上は不 良なレスポンシブネスを示す。 ©ASSIGN Inc. All Right Reserved. 1
なぜ TTI ≤ 3,000ms を目指すのか? 外部ベンチマーク Googleの推奨基準(LCP: Largest Contentful Paint)では、ページの主要コンテンツが表示されるまでの時
間は2.5秒以下が良好 2.5秒〜4.0秒は改善が必要、4.0秒以上は不良とされる モバイルアプリにおいても、この基準を満たす速度が期待される 良好なユーザー体験を提供するためには、サイトの LCP(最大コンテンツの描画)が2.5秒以下である ことを目指すべきである。この目標値は、ページ読 み込みの75パーセンタイルで測定される。 ©ASSIGN Inc. All Right Reserved. 1
応答性を実現する設計パターン 追求するSLO・KPI 設計パターン 体感レイテンシ ≤ 200ms ① 画面遷移ファーストな設計 ② Optimistic
UI ③ 差分ロード・キャッシュ活用 TTIの短縮(目標 3,000ms以下) ©ASSIGN Inc. All Right Reserved. 1
体感レイテンシ ≤ 200ms を実現する設計 課題 ユーザーが操作してから画面反応があるまでの遅延が長いと、 興味・集中が途切れ、次の行動に繋がりにくくなる。 特に「画面遷移」や「送信アクション」は遅延の体感が強い。 解決策 パターン①:画面遷移ファーストな設計
通信完了を待たずに「意味のある最小UI」を即描画(メタ情報・スケルトン先出し) パターン②:Optimistic UI サーバー応答を待たずにUIを即時更新 失敗時のみロールバックし、体感200ms以内の反応を保証 ©ASSIGN Inc. All Right Reserved. 1
画面遷移ファーストな設計 ©ASSIGN Inc. All Right Reserved. 1
画面遷移ファーストな設計 ポイント 即時描画: 通知ペイロードや前回キャッシュから ヘッダー情報を即表示 Shimmer / Skeleton: 本文領域は骨組みUIで先出し、認知的 な安心感を生む
©ASSIGN Inc. All Right Reserved. 1
Optimistic UI による体感速度向上 ポイント StateNotifierで状態管理: class MessageNotifier extends StateNotifier<List<Message>> {
MessageNotifier(this.api) : super([]); final MessageApi api; 明示的な状態更新で必要な箇所だけ再 描画 即時UI反映: isSending: true で吹き出しを即座 に追加 成功時確定: API成功でインジケーター非表示 失敗時ロールバック: エラー時は吹き出しを削除 Future<void> sendMessage(String text) async { final tempMessage = Message( id: 'temp_${DateTime.now().millisecondsSinceEpoch}', text: text, isSending: true, // ); messagesState = [...messagesState, tempMessage]; // 1. UI try { final confirmed = await api.sendMessage(text); // 2. → state = state.map((m) => m.id == tempMessage.id ? confirmed.copyWith(isSending: false) : m ).toList(); } catch (e) { // 3. → state = state.where((m) => m.id != tempMessage.id).toList(); showErrorSnackBar(' '); } } サークルインジケーター表示 即時 に追加 成功 インジケーター非表示 失敗 ロールバック 送信に失敗しました ©ASSIGN Inc. All Right Reserved. } 1
Optimistic UI による体感速度向上 ポイント StateNotifierで状態管理: 明示的な状態更新で必要な箇所だけ再 描画 即時UI反映: isSending: true
で吹き出しを即座 に追加 成功時確定: API成功でインジケーター非表示 失敗時ロールバック: エラー時は吹き出しを削除 ©ASSIGN Inc. All Right Reserved. 1
補足:Optimistic UIの使い所 基本的な考え方 状態変更が軽量で、失敗してもデメリットが少ないものに限定する。 操作の取り消しが困難または不可能で、失敗時の影響がクリティカルなものや、 トランザクションの整合性が求められる操作には適用しない。 私たちのアプリで適用するケース メッセージの送信 求人のお気に入り/ 興味なし登録
サクサクと情報整理を進め、思考の流れを止めない ©ASSIGN Inc. All Right Reserved. 適用しないケースと代替案 スカウト・求人へのエントリー 代替案: 処理中、エントリー完了の状態を管理し、UIに も明確にフィードバックする。 ユーザーに安心感を与え、二重応募などの混乱 を防ぐ。 1
TTIを短縮する設計 課題 通知タップ後や画面復帰時、操作できるようになるまでが遅い と ユーザーは体感で「重いアプリ」と感じる。 解決策 差分ロード、キャッシュ活用 差分ロード:チャットルームの最新5件をローカルDBに保存し、差分のみ取得 キャッシュ優先:ネットワーク応答を待たず、キャッシュ済みデータを即描画 バックグラウンド更新:新鮮な情報は非同期で取得し差分反映
©ASSIGN Inc. All Right Reserved. 1
差分ロード、キャッシュ活用 ©ASSIGN Inc. All Right Reserved. 1
差分ロード、キャッシュ活用 ポイント ローカルDB保存: 最新5件をDriftに保存 差分取得: lastUpdatedAt 以降のデータのみ取 得 キャッシュ優先描画: 画像をキャッシュから即表示
©ASSIGN Inc. All Right Reserved. 1
応答性向上に関する計測 検証したい仮説 応答性向上(INP、TTI短縮)によって、ユーザーのタスク完了率や継続操作率が改善するか? 検証のためのイベント設計 No. 計測イベント名 タイミング パラメータ(例) 目的、計測するもの ①
action_start 操作開始 action_type: 'open_scout' screen_id: 'chat_room_list' ユーザーが操作を開始 ② feedback_shown UI反応 action_type: 'open_scout' ③ content_interactive コンテンツ 操作可能 screen_id: 'chat_room' ④ task_complete タスク完了 task_name: 'entry_scout' from_action: 'open_scout' ©ASSIGN Inc. All Right Reserved. [INP] 操作からUIが反応するまでの 体感レイテンシ [TTI] 遷移開始から操作可能になるまでの時間 [効果] 最終的なタスク完了に至ったか 1
応答性向上に関する計測:INP ©ASSIGN Inc. All Right Reserved. 1
応答性向上に関する計測:TTI ポイント AsyncValue.whenの data 節で発火 データ取得完了後を検知 addPostFrameCallbackで描画完了を待つ content_interactiveイベント発火 データ取得を伴う画面 //
class JobDetailPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final jobState = ref.watch(jobDetailProvider); return jobState.when( loading: () => Skeleton(), error: (e, _) => ErrorView(), data: (job) { // WidgetsBinding.instance.addPostFrameCallback((_) { analytics.logEvent('content_interactive', parameters: 'screen_id': 'job_detail', }); }); return JobDetailView(job: job); }, ); データ取得完了後(操作可能時) } } ©ASSIGN Inc. All Right Reserved. 1
計測データから仮説を検証する 分析①:体感レイテンシの長さと、継続操作率の相関 INPが短いユーザー群と長いユーザー群で、 task_complete 率に差はあるか? 分析②:TTIの長さと、その後のタスク完了率の相関 TTIが短いユーザー群と長いユーザー群で、セッション内の次のアクション発生率に差はあるか? ©ASSIGN Inc. All
Right Reserved. 1
技術的な柱③:継続可能性 時間を経た後でも使い続けやすい設計 ©ASSIGN Inc. All Right Reserved. 1
継続可能性:キャリアのあらゆるフェーズで支援する 目指すユーザー体験 転職活動・就業中・キャリア形成など、ユーザーのライフステージが変化してもアプリが文脈を維持し、再び 役立つ存在であり続ける その瞬間の課題解決ツールではなく、キャリアのどのタイミングでもサポートできる長期的な伴走者となる 追求する指標 (SLO) 1. ディープリンク遷移成功率 ≥
99.0% 2. 文脈完全性率 ≥ 99.5% 定義:通知や外部リンクからアプリを開いた際に、期待さ れたルートへ正常に遷移できた割合 定義:遷移後の画面が提案内容(求人ID・イベントID・分 析結果など)と一致して正しく描画される割合 目的:「アプリに戻ってきたとき、必ず目的地にたどり着 ける」という復帰体験の信頼性を維持すること 目的:提案された内容が正しく再現されるというサポート 体験の一貫性を担保すること ©ASSIGN Inc. All Right Reserved. 1
なぜこの2つのSLOに着目するのか? 長期利用ユーザーに価値ある提案を届けるために ©ASSIGN Inc. All Right Reserved. 1
なぜこの2つのSLOに着目するのか? 長期利用ユーザーに価値ある提案を届けるために ©ASSIGN Inc. All Right Reserved. 1
なぜこの2つのSLOに着目するのか? 長期利用ユーザーに価値ある提案を届けるために ©ASSIGN Inc. All Right Reserved. 1
なぜこの2つのSLOに着目するのか? 目指す方向性 長期利用ユーザーに対する継続的提案と、その結果によるFB獲得できる土台作り そのために必要なこと 通知・メールからの復帰時に、提案内容を確実に届ける 1. 復帰動線の確実性 ディープリンクを確実に成功させる 2. 文脈維持の一貫性
提案内容通りの画面・情報にアクセスさせる ©ASSIGN Inc. All Right Reserved. 1
継続可能性を実現する設計パターン SLO・KPI 設計パターン ディープリンク遷移成功率 ≥ 99.0% ① 単一路線ハンドラ + ガード
② 目的画面における文脈検証 文脈完全性率 ≥ 99.5% ©ASSIGN Inc. All Right Reserved. 1
単一路線ハンドラ + ガード 課題 初期リンク、通知復帰、外部URL…入口が複数 → 処理が分散すると整合性が崩れる リンクパラメータ不正・未ログイン状態での再訪 → 目的画面にたどりつけない
解決策 入口は DeepLinkHandler に一本化 認証や前提条件の検証は go_router.redirect に集約 初期リンク 通知 DeepLinkHandler 外部URL ©ASSIGN Inc. All Right Reserved. URI parse 内部ルートへ変換 router.go go_router redirect OK 目的地へ遷移 NG ログイン/一覧へフォールバ ック (認証/前提条件) 1
単一路線ハンドラ + ガード ポイント 初期リンク / 通知 / 外部URL →
DeepLinkHandler で受付 Handler はURIの解析、遷移先を解決、 軽いバリデーションに専念 認証チェック / リソース存在チェックは go_router.redirect に一本化 フォールバック遷移も redirect が担当するためロ ジックが分散しない ©ASSIGN Inc. All Right Reserved. (責務:外部 をアプリ内部ルートに変換) /// DeepLinkHandler URI class DeepLinkHandler { Future<void> handle(Uri uri) async { final route = _parseUri(uri); // DeepLink → アプリ内ルートに変換 if (route == null) { // router.go('/login'); // return; } analytics.logEvent('deep_link_route', { 'success': true, 'uri': uri.toString(), }); router.go(route.path, extra: route.params); // ディープリンク失敗のイベント送信を行う ログイン画面へフォールバック } 目的地へ遷移 } (責務:認証・前提条件ガードの一元化) /// go_router final router = GoRouter( routes: [...], redirect: (context, state) { final isAuth = ref.read(authProvider); if (!isAuth && state.matchedLocation != '/login') { return '/login?redirect=${state.matchedLocation}'; } return null; }, ); 1
単一路線ハンドラ + ガード ポイント 初期リンク / 通知 / 外部URL →
DeepLinkHandler で受付 Handler はURIの解析、遷移先を解決、 軽いバリデーションに専念 認証チェック / リソース存在チェックは go_router.redirect に一本化 フォールバック遷移も redirect が担当するためロ ジックが分散しない ©ASSIGN Inc. All Right Reserved. 1
単一路線ハンドラ + ガード ポイント 初期リンク / 通知 / 外部URL →
DeepLinkHandler で受付 Handler はURIの解析、遷移先を解決、 軽いバリデーションに専念 認証チェック / リソース存在チェックは go_router.redirect に一本化 フォールバック遷移も redirect が担当するためロ ジックが分散しない ©ASSIGN Inc. All Right Reserved. 1
目的画面における文脈検証 課題 目的画面に着いても、必須パラメータ不足や不正値で文脈が崩れる タブ/クエリ/フィルタなどサブ文脈が欠落しても、続きを再開できない 解決策 画面のinitStateで必須パラメータ検証し、NGなら即フォールバック 検証結果(OK/NG)をイベントで記録 ©ASSIGN Inc. All
Right Reserved. 1
目的画面における文脈検証 画面アクセス時に文脈検証し、不正/欠落は上位画面にフォールバック 通知 チャットルーム詳細 データ存在検証 // Push → class ChatRoomScreen
extends ConsumerStatefulWidget { final String roomId; final String? messageId; // } final room = await ref.read( // 2. chatRoomProvider(widget.roomId).future ).catchError((_) => null); スクロール先メッセージ if (room == null) { isValid = false; reason = 'room_not_found'; } class _ChatRoomScreenState extends ConsumerState { @override void initState() { super.initState(); _validateContext(); } analytics.logEvent('context_fidelity', parameters: { // 3. 'ok': isValid, 'route': 'chat_room', 'reason': reason, }); イベント送信 Future<void> _validateContext() async { bool isValid = true; String? reason; if (widget.roomId.isEmpty) { // 1. isValid = false; reason = 'room_id_missing'; } 必須パラメータ検証 ©ASSIGN Inc. All Right Reserved. フォールバック チャット一覧へ if (!isValid && mounted) { // 4. router.go('/chat/rooms'); // } } } 1
継続可能性の計測 継続可能性のSLOを検証するため、2つのイベントで計測 No. 計測イベント名 タイミング パラメータ(例) 目的、計測するもの ① ディープリンク処理完了 時
uri: 'https://...' success: true reason: 'not_found' [遷移成功率] 画面入場時の 文脈検証完了時 ok: true route: 'chat_room' reason: 'room_id_missing' [文脈完全性] ② deep_link_route context_fidelity ディープリンクが正常に処理されたか 遷移先画面で文脈が正しく維持されてい るか SLO目標値 ディープリンク遷移成功率: success: true の件数 / 全リンク処理回数 ≥ 99.0% 文脈完全性率: ok: true の件数 / 全画面遷移回数 ≥ 99.5% ©ASSIGN Inc. All Right Reserved. 1
まとめ:顧客価値を実現する柱と設計パターン 3つの柱 信頼性 SLO・KPI Fatalエラー率 ≤ 0.1% 応答性 体感レイテンシ ≤
200ms TTIの短縮(目標 3,000ms以下) 柱 フォーム離脱率の低減 ≥ 99.0% 継続可能性 ディープリンク遷移成功率 文脈完全性率 ≥ 99.5% ©ASSIGN Inc. All Right Reserved. 設計パターン ① レイヤーごとの適切なエラーハンドリング ② Driftによる入力の即時ローカル保存 ① 画面遷移ファーストな設計 ② Optimistic UI ③ 差分ロード・キャッシュ活用 ① 単一路線ハンドラ + ガード ② 目的画面における文脈検証 1
SLOの先にある、複合的な価値実現を目指す 組み合わせ 上位KPI 価値 信頼性 × 応答性 登録動線の完了率向上 クラッシュや入力喪失がなく、即時反応するUIが最後まで やり切る体験を支える
軽快に動作し、文脈を保ったまま再開できる体験が 応答性 × 継続可能性 再訪率・復帰ユーザーの 完了率向上 戻ってきたくなる動機を作る 信頼性 × 継続可能性 長期的な利用継続 ・LTV向上 「データが安全に残る」「再開が正確」の積み重ねで ブランド信頼を作る 継続して行うこと 重要KPIに直結するSLOの選定・強化 定義済みSLOのより高い基準設定(例:99.9→99.95) ©ASSIGN Inc. All Right Reserved. 1
顧客価値を実現するアプリケーション開発 顧客価値とSLOの往復によって、技術と体験の深化の循環を生み出す 1. 顧客価値の理解 アプリケーションが果たすべき責務を明 確にする 4. 測定と検証 データで効果を検証し、次の改善へつな げる
©ASSIGN Inc. All Right Reserved. 2. 仮説と定量目標の設定 ↻ SLO・KPIを定義し、技術選定の基準を 明確にする 3. 体験の実装 適切な実装をもって、洗練された 体験としてユーザーに価値を届ける 1
ご案内 ブースのご案内 ©ASSIGN Inc. All Right Reserved. 採用情報 1
ご清聴ありがとうございました ©ASSIGN Inc. All Right Reserved. 1