Skip to content

モバイル画面の状態設計:読み込み中・オフライン・古いデータ

isLoading 一つでは実際の画面を表せない。更新中でも以前のデータは有用な場合があり、成功した空の一覧と通信失敗も別物だ。オフラインでキャッシュがある場合とない場合も表示を分けたい。

データとリクエストを分離する ​

snapshot、lastSyncedAt、requestState、error を保持し、表示状態をそこから導く。スナップショットがなければ骨格表示または再試行できるエラーを出す。データがあるなら更新中も本文を残し、失敗は非ブロッキングな通知にする。真の空状態は信頼できる成功応答からのみ確定する。

データ通信画面の表示
なし初回読込骨格表示と戻る操作
なし失敗エラーと再試行
あり更新中本文を残して進捗を表示
あり失敗本文と同期時刻、控えめなエラー
空の一覧成功本当の空状態と作成導線

判定規則を画面から切り離す ​

RN なら selector、Flutter なら ViewModel に deriveScreenState(snapshot, request) のような純粋な規則を置く。初回表示、引っ張って更新、前景復帰、通知からの更新は同じデータ入口を使い、更新理由だけを区別する。

低速回線、連続更新、アカウント切り替え、オフライン起動を検証する。失敗で既存の一覧を消さず、前の利用者のデータは新しいアカウントの画面に一瞬も表示しない。

空状態には根拠が要る ​

メッセージ一覧を考える。初回表示でキャッシュがなければ initialLoading であり、正常な応答で 0 件と確認できて初めて confirmedEmpty になる。以前に 20 件を確認済みなら、引き下げ更新中も一覧を読める refreshingWithData にする。更新失敗時は staleWithError とし、20 件と最終成功時刻を残す。これを loading | success | error にまとめると、更新のたびに内容を消すか、失敗を空一覧と誤認する。

データ層には {accountId, items, lastSuccessAt, revision, pending} のような事実を保持し、表示状態をそこから導く。各コンポーネントで独立した真偽値を増やさない。items=[] は配列が空というだけで、真の空状態には「現在のアカウントで取得に成功した」という根拠が必要だ。アカウント切替時は旧データを隔離してから新しいキャッシュを読み、旧ユーザーの内容を一瞬でも見せない。

イベント保持するデータ画面の表示
初回取得が失敗なしエラー画面と再試行
キャッシュありで更新失敗確認済み一覧画面内エラー、同期時刻、再試行
正常に 0 件を取得現アカウントの空スナップショット明確な空状態
アカウント切替新アカウントのキャッシュのみスケルトンまたは新しい一覧

画面より状態遷移を試す ​

reducer や状態機械のテストで、成功(20件) → 更新 → タイムアウト は 20 件を保持し、成功(20件) → 成功(0件) で初めて空状態になることを確認する。Aを取得 → Bへ切替 → Aの応答 では A の結果を捨てる。UI テストでは再試行、読み上げ、古いデータの表示も見る。静止画ではなく利用者が経験する変化を検証できる。

MIT Licensed