mcpField
S
state-and-background-footguns

state-and-background-footguns

github.com/maxrave-dev開発1.7k91 回インストール1 週間前に更新
リポジトリ

state-and-background-footguns について

Kotlin と Compose Multiplatform の開発者向けスキルで、実運用アプリから得たリアクティブ状態とバックグラウンド処理の落とし穴を23件まとめています。各資料で有効なパターン、失敗の原因、コードベースでの検証方法を説明します。

主な機能

  1. 0123件の個別リファレンス
  2. 02リアクティブ状態とFlowのパターン
  3. 03バックグラウンド処理とサービスの指針
  4. 04失敗パターンと原因の説明
  5. 05コードベース向け検証コマンド

活用シーン

  1. 01古い画面状態や不整合の診断
  2. 02バックグラウンド通知の重複や欠落の防止
  3. 03永続化とバックアップ動作の確認
  4. 04デスクトップアプリの起動と実行時の強化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill state-and-background-footguns

インストール後、Claude Code で/state-and-background-footgunsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
app-backup-to-zip-mediastore.md
combine-two-flags-to-gate.md
compose-desktop-runtime-hardening.md
compose-multiplatform-viewmodel-base.md
datastore-driven-workmanager.md
derive-the-flag-dont-store-and-correct-it.md
desktop-single-instance-before-di.md
distinct-by-key-reset-cancel-per-item.md
dual-mode-persist-blocking-or-async.md
flatmaplatest-resubscribe-composite-key.md
glance-layout-vocabulary.md
glance-widget-over-existing-state.md
kmp-logger-facade.md
named-job-lifecycle-discipline.md
on-demand-dictionary-asset.md
one-snapshot-per-period-not-many-flows.md
pending-state-makes-waiting-legible.md
periodic-worker-dedup.md
preset-identity-read-back-from-value.md
readonly-stateflow-cold-to-hot.md
remoteviews-bitmap-budget.md
stateflow-conflation-inverts-state.md
swappable-crash-reporting-and-dialog.md

詳細

リアクティブな状態管理とバックグラウンド処理の落とし穴

本番環境の Kotlin および Compose Multiplatform アプリから集めた23の落とし穴です。それぞれの短いファイルは references/ の下にあります。各ファイルには、機能するパターン、落とし穴(実際に発生する具体的な問題とその理由)、そして ユーザー自身のツリーに対して実行するコマンドを記載した「検証方法」セクションがあります。

このインデックスの使い方。 これから書こうとしているコード、または調査中の症状に照らし合わせて、以下の項目を確認してください。それぞれの項目には、トピックと、その項目を参照すべき症状が記載されています。コードや修正案を提示する前に、該当する可能性のあるファイルをすべて Read ツールで読んでください。各ファイルは150行未満です。検証コマンドにソースプロジェクトのパスが記載されている場合は、ユーザーのツリー内の同等のパスに置き換えてください。

相互参照。 ファイル内のバッククォートで囲まれた落とし穴の名前がここに記載されていない場合は、同じ領域の別のスキルにあります。../*/references/<name>.md を glob して開いてください。

リアクティブな状態:Flow、StateFlow、ViewModel

  • stateflow-conflation-inverts-state — 値を集約する状態ホルダーはスロットを1つだけ保持するため、イベントごとにコールバックが複数回書き込む場合、書き込み順序が正しさを左右します。コレクターに見えるのは最後の書き込みだけです。コンテンツがすでに読み込まれているのにスピナーが重なって表示される場合、ローディングフラグが不適切なタイミングでクリアされる場合、または画面に1つ前の状態が表示される場合に使用します。
  • named-job-lifecycle-discipline — 懸念事項ごとに var xJob: Job? フィールドを1つ設け、すべての起動箇所で「再起動前にキャンセルする」ことを不変条件とし、キャンセルによって途中で止まらないよう、終了処理での書き込みをラップします。1ティック中に定期更新が何度も届く場合、前の項目の古い結果が現在の項目を上書きする場合、または長寿命オブジェクトが解放後も動作し続ける場合に使用します。
  • readonly-stateflow-cold-to-hot — ミュータブルなホルダーをアップキャストするのではなく、asStateFlow() を通じて状態を公開します。また、コールドソースフローを stateIn(scope, WhileSubscribed(timeout), initial) でホットに変換することで、コレクターの増減があっても存続し、永久に実行され続けることも防ぎます。画面の回転やナビゲーションのたびに画面がソースを再クエリする場合、最後のコレクターが離れた後も処理が続く場合、またはオーナーの外部にあるものが読み取り専用のはずの状態に書き込んでいる場合に使用します。
  • flatmaplatest-resubscribe-composite-key — flatMapLatest を使ってキーのフローが変わるたびに下流のフローを再購読し、その後、distinctUntilChanged で高頻度のストリームを複合キーにまとめてから、高コストな単発処理を実行します。進捗のティックごとにフェッチが再実行される場合、高速切り替え後に誤った項目に対して実行される場合、または入力の1つが最後に届くため副作用が一度も実行されない場合に使用します。
  • distinct-by-key-reset-cancel-per-item — distinctUntilChangedBy で項目IDをキーにする項目ごとのパイプラインを使い、次の項目を開始する前に前の項目の進行中の処理をキャンセルし、値を設定する前に表示状態をリセットします。これにより、新しい項目の下に前の項目のデータが表示されることを防ぎます。詳細画面に前の項目のアートワークやテキストが一瞬表示される場合、遅いレスポンスが新しいレスポンスを上書きする場合、または値のない項目に移動してもフィールドに値が残る場合に使用します。
  • combine-two-flags-to-gate — combine + distinctUntilChanged + collectLatest で、複数の独立した条件フローを1つのオン/オフゲートにまとめます。開始と終了処理の両方を冪等にし、終了処理はキャンセルされないように実行します。サブシステムが設定完了前に起動する場合、前提条件の1つが取り下げられた後も動作し続ける場合、または高速な切り替えの後に中途半端な状態で起動してしまう場合に使用します。
  • compose-multiplatform-viewmodel-base — Compose Multiplatform 用の共有 ViewModel 基底クラスです。コンテナーを認識するため、サブクラスは依存関係をコンストラクター経由で渡さずに追加取得でき、すべての画面でローディングとエラーの表示方法を統一できます。また、このような基底クラスでほぼ必ず生じる、リソース検索をブロッキング実行してしまう危険についても扱います。すべての画面で個別にローディングダイアログを実装している場合、一部のサブクラスだけが使う依存関係を基底クラスに持たせる必要がある場合、または何も描画される前にメインスレッドでアプリの起動が一時停止する場合に使用します。
  • dual-mode-persist-blocking-or-async — 1つの suspend 永続化処理を2通りの方法で実行します。スコープが破棄される前に書き込みを完了する必要がある終了処理ではブロッキング実行し、定期的なティックではファイア・アンド・フォーゲットで実行することで、乖離していく2つの処理を別々に持たずに済みます。アプリの実行中は保存状態が正しいのに強制終了後は誤っている場合、終了処理での保存が何もせずに終わる場合、または2つの保存関数のガード条件が異なってきた場合に使用します。
  • pending-state-makes-waiting-legible — 「要求したが、まだ応答がない」状態を独立したフィールドとしてモデル化します。そうしないと、拒否された要求と、相手がまだ画面を確認していない状態を区別できず、どちらも何も起きていないように見えるためです。1回のセットに対して複数回クリアする場合、応答が返ってこない状況でローカルにクリアする場合、ユーザーに抜け道を用意する場合について扱います。アクションが何もしていないように見える場合、画面がいつまでも待機状態になる場合、またはエラーメッセージを表示する場所がない場合に使用します。
  • preset-identity-read-back-from-value — ラベルを値の隣に保存するのではなく、現在適用されている値とプリセットを比較して「どのプリセットか」を導き出します。これにより、編集すると自動的にカスタムに切り替わり、元に戻すと再選択されます。また、プリセットごとのフィールドで、プリセット自体の数値から求められるものは、行ごとに書き出すのではなく導き出します。プリセット選択欄に古い名前が表示され続ける場合、プリセットが再選択されない場合、または20行中の1行だけで行ごとの定数がずれている場合に使用します。
  • one-snapshot-per-period-not-many-flows — 画面側で揃える必要がある多数の独立したフローではなく、1回の suspend 呼び出しから、期間全体の数値を1つの不変スナップショットとして返します。2つの期間を比較する画面では、各期間のデータに一貫性が必要です。別々に値が通知されると、今期の件数と前期の合計が並んで表示されることがあります。単一通知のフローが実質的に suspend 関数である場合、導出値をどこまでスナップショットに含めるべきか、また、率の分母には人が意図するものを使うべき理由について扱います。再読み込み中に比較画面で一時的に数値が一致しない場合、1つの画面のリポジトリに10個目のフローを追加しようとしている場合、または2か所で同じ平均値を異なる方法で計算している場合に使用します。
  • derive-the-flag-dont-store-and-correct-it — すでに収集している状態から純粋に導けるブール値でも、独自の mutableStateOf に保存し、推測値で初期化したうえで LaunchedEffect により1フレーム後に修正してしまうことがあります。そのため最初のフレームでは推測値が描画されます。また、rememberSaveable に古い値がすでに保存されていると、シード値だけを変更しても何も変わりません。コールドスタート時にUI要素が表示、非表示、再表示と目に見えてちらつく場合や、remember/rememberSaveable の初期化式を編集しても、すでに起動しているアプリで表示が変わらない場合に使用します。

バックグラウンド処理、サービス、プラットフォームのランタイム

  • app-backup-to-zip-mediastore — ストレージ権限を使わずに、アプリを1つのZIPファイルにバックアップしてユーザー自身のDownloadsフォルダーに保存し、最新のN個のアーカイブだけを保持します。ファイルをコピーする前にデータベースの先行書き込みログをチェックポイント処理すること、まずキャッシュ内でアーカイブを組み立てること、システムのメディアストア経由で挿入すること、同じコレクションへのクエリで古いアーカイブを削除することを扱います。Android専用です。復元したバックアップに最新の書き込みが含まれていない場合、バックアップファイルがユーザーのファイルマネージャーに表示されない場合、または古いバックアップがいつまでも蓄積する場合に使用してください。
  • datastore-driven-workmanager — 設定ストアから定期的なバックグラウンド処理を直接制御し、切り替えや間隔の変更が再起動なしですぐに反映されるようにします。複数の設定フローを1つのスケジュール決定にまとめる方法、変更した間隔が実際に反映される更新時のエンキューポリシー、無効化時に一意の名前でキャンセルする方法、ワーカー自身が同じ設定を再読み込みする必要がある理由を扱います。Android専用です。バックアップや同期の間隔を変更しても再インストールするまで反映されない場合、ユーザーがオフにした後も処理が実行され続ける場合、または2つのスケジュールが重複して登録される場合に使用してください。
  • periodic-worker-dedup — 実行が遅れても項目を見逃さず、1つの項目を重複処理する可能性が、限られた1つの時間枠内での強制終了だけに限られる定期通知機能を構築します。「処理済み」の記録をワーカーではなくデータベースに保存すること、スケジュール間隔より意図的に広い時間範囲を走査して、実行がスキップされても追いつけるようにすること、実行が中断された場合にランダムな一部ではなく最新の項目が失われるよう、古い順に処理することを扱います。Android専用です。デバイスの再起動後に通知が重複するとユーザーから報告される場合、デバイスがアイドル状態の間に項目が見逃される場合、または初回実行で過去の項目がすべてユーザーに大量に通知される場合に使用してください。
  • desktop-single-instance-before-di — デスクトップアプリの起動処理を順序付けし、依存関係コンテナーやディスク上の状態を開く処理より前に、単一インスタンスのガードを実行します。2回目の起動時の引数を実行中のインスタンスに転送して終了する方法、UIフレームワーク外からの復元要求を実行中のウィンドウに橋渡しする方法、2回目の起動でそもそも2つ目のプロセスが生成されないプラットフォームについて扱います。アプリを2回目に起動するとクラッシュしたり設定が破損したりする場合、アプリの実行中に開いたリンクが何も起こさない場合、または2つ目のウィンドウが1つ目のウィンドウのファイルを奪う場合に使用してください。
  • compose-desktop-runtime-hardening — Composeデスクトップのエントリーポイントが最初のウィンドウを作成する前に実行すべき、プローブ、システムプロパティ、プラットフォームゲートの順序付きリストです。ネイティブライブラリの読み込み前にJDKのデスクトップ統合APIを事前に初期化すること、起動時に一度だけ読み取られるレンダラーと相互運用のプロパティ、待機によってUIスレッドが停止する場合の垂直同期の無効化、仮想マシンの検出に応じた透過表示とカスタムタイトルバーの制御、デスクトップ環境でエントリーを正しく関連付けられるようLinuxのウィンドウクラス名をリフレクションで設定することを扱います。ウィンドウを別のモニターに移動すると音声は再生され続けるのにUIがフリーズする場合、仮想マシンでウィンドウがまったく表示されない場合、またはLinuxのドックにアプリ名ではなくクラス名が表示される場合に使用してください。
  • swappable-crash-reporting-and-dialog — インターフェースではなく3つのトップレベル関数を公開するモジュールを切り替えることで、1つのコードベースからクラッシュレポート機能ありとなしのビルドを作成します。呼び出し箇所のバイトコードが同一になり、トラッキングなしのビルドにレポートコードが一切含まれないことを証明できます。また、最新のUI自体がクラッシュの原因である可能性を考慮した、古いウィジェットツールキットによるデスクトップのクラッシュダイアログと、そのツールキットのイベントスレッドへの処理の受け渡しも扱います。プライバシー重視のビルドにレポート機能の依存関係を一切含めてはならない場合、切り替えた実装が対応する実装と乖離している場合、またはアプリが目に見えるエラーもユーザーが詳細を送る手段もないまま終了する場合に使用してください。
  • kmp-logger-facade — 共有モジュールに小さなロギングオブジェクトを1つ置き、すべての呼び出し箇所とロギングライブラリの間に挟みます。これにより、ログの多いサブシステムを1行で静かにし、呼び出し箇所に触れずにライブラリを置き換えられます。ミュート対象のタグのセット、実行時に重要度を選ぶ呼び出し元向けの値としてのレベルを表す列挙型、ライブラリを直接インポートする箇所が1つでもあるとどちらの効果も損なわれる理由を扱います。あるサブシステムのログが他のログを埋もれさせている場合、ロギングライブラリの交換やアップグレードのために何百ものファイルを編集する必要がある場合、またはタグをミュートしても一部の出力に反映されない場合に使用してください。
  • glance-widget-over-existing-state — アプリ内の既存の状態ホルダーを別個のコピーではなく描画するホーム画面ウィジェットを構築します。アプリがすでに使用している共有状態オブジェクトと長期間有効なスコープを同じものを注入し、その状態が変わるたびにウィジェットの更新を再要求します。ウィジェットのボタンからアプリ独自のUIイベントを送信する方法、注入対象を画面スコープではなくシングルトン定義にする必要がある理由、ウィジェット全体をタップ対象にする方法、ウィジェットが読み込むアートワークのハードウェアビットマップを無効にする方法、ウィジェットのprovide-glanceコールバックからコレクターを開始する際に避けるべきリークを扱います。Android専用です。ウィジェットに古い再生状態やセッション状態が表示される場合、アートワークが空白になる場合、タップするとランチャーのメニューが開いたり何も起こらなかったりする場合、またはボタンに独自の重複したロジックが必要な場合に使用してください。
  • remoteviews-bitmap-budget — ホーム画面ウィジェットが描画するビットマップはすべて、プロセス境界を越えて渡されるRemoteViewsペイロードにコピーされます。ビットマップが固定の上限を超える更新はプラットフォームに拒否されるため、描画サイズに合わせて各画像をデコードすることは最適化ではなく、ウィジェットを画面に表示し続けるために必要なことです。何枚の画像を格納できるかを決めるピクセル数の計算、システムログ以外では失敗が見えない理由、同じ画像を読み込むすべての領域でキャッシュキーとデコードサイズの両方を一致させる必要がある理由を扱います。Android専用です。ウィジェットにフレームワークのエラー用プレースホルダーが表示される場合、あるデバイスでは描画されるのに別のデバイスでは描画されない場合、またはタイルを1つ追加するとウィジェット全体が空になる場合に使用してください。
  • glance-layout-vocabulary — コンポーザブルなウィジェットツールキットは、インポート先が違うだけのComposeではありません。アスペクト比の指定がなく、weightは常に1なので均等分割しかできず、角の半径が適用されるのはAPI 31以降です。また、ウィジェットは常にランチャーのセル全体に広がるため、余った高さの使い方を意識する必要があります。正方形タイルを正方形に保つweighted-spacerのテクニックと、fill修飾子が帯全体を占有してしまう箇所を扱います。Android専用です。正方形のタイルが長方形に描画される場合、ウィジェットのコンテンツの下に何もない色の領域が表示される場合、またはデバイスによって角が丸くなったり四角くなったりする場合に使用してください。
  • on-demand-dictionary-asset — トークナイザー、スペルチェッカー、またはアナライザーが、ほとんどのユーザーが使わない機能のためにインストール全体を肥大化させる数メガバイトの辞書を必要とする場合、オプトイン時に一度だけ通常のディレクトリへダウンロードし、フラグを信頼するのではなく、すべての利用側がファイルシステムに「準備できているか」を確認するようにします。言語ごとのアセットによって特定のターゲットだけでパッケージが肥大化する場合、ダウンロードが途中で終わったアセットをインストール済みと誤認してはならない場合、またはダウンロード成功が報告されても機能が壊れたままの場合に使用してください。

コメント