OnVisibleに処理が集中していくとどうなる?
前回の記事では、「その画面のことは、その画面自身に任せる」という考え方のもと、遷移先の準備処理をOnVisibleに寄せるアプローチを紹介しました。
これにより、処理の重複や修正漏れのリスクは減りました。しかし、アプリが育っていくと、OnVisibleに処理が集中することで別の課題が見えてきます。
この記事で伝えたいこと
- OnVisibleに処理を寄せたことで起きる「フラグ管理の煩雑化」という課題
- 遷移の「意図」を構造化して渡す Navigation Context パターンによる試行錯誤
前提
- Power Apps のCanvasアプリ
- 商品リスト(MainScreen)を軸に、絞り込み設定画面・商品詳細画面を行き来する構成
- 前回の方針にそって、各画面の初期化処理はOnVisibleに書いている
- AIと協働しながら、より良い設計パターンを試行錯誤中
OnVisibleが担う処理が増えていくケース
開発を進めていくと、画面を表示するたびに毎回同じ処理をすればいい、というケースばかりではないことがわかります。
今回は、商品管理アプリのような、リストとその詳細で構成されるような基本的なアプリを例にします。
アプリの構成は次の通りです。
- InitScreen(アプリの初期化): アプリ全体の初期化だけを行う画面。App.OnStartは推奨されていない※1 こともあり、初期化のためだけの画面を用意することがあります。
-
MainScreen(商品リスト): GalleryのItemsには
colProductsコレクションを参照させる。絞り込み条件が複数項目にまたがるため、Gallery.Itemsに直接Filterを書くより、OnVisibleでClearCollectしたcolProductsをGalleryが参照する構成にします。 - FilterScreen(絞り込み設定画面): カテゴリ・ステータス・価格帯など複数の絞り込み条件を設定する画面。
- DetailScreen(商品詳細画面): 商品の詳細情報を表示する(閲覧のみ)
この構成で、MainScreen.OnVisibleが対応すべきケースが2つ生まれます。
1. 画面表示時の初期化処理:最初にMainScreenが表示されたとき、カテゴリや商品マスタをデータソースから取得してcolProductsにセットします。ただし起動時の1回だけ実行したい。詳細画面を見て戻るたびに再取得するのは無駄です。
2. 絞り込み条件の適用:FilterScreenで絞り込み条件を設定してMainScreenに戻ったとき、その条件でcolProductsをClearCollectし直す必要があります。
※1 Power Apps の App オブジェクト - Power Platform | Microsoft Learn
フラグによる制御と、その煩雑化
「初回だけ実行」と「それ以外は実行しない」を分けるために、まず思いつくのがフラグによる制御です。
MainScreen.OnVisible
// 初回のみ実行する処理をフラグで判断
If(
!locIsInitialized,
ClearCollect(colCategories, Categories);
ClearCollect(colProducts, Products);
// 初期化が終わったらフラグを立てる
UpdateContext({ locIsInitialized: true })
);
ここまではシンプルです。しかし、FilterScreenから戻ったときに絞り込みを反映したいという要件が加わるとどうなるでしょうか。
FilterScreen側では、絞り込み条件をグローバル変数にセットするか、コンテキスト変数を渡してMainScreenに戻ります。MainScreen側では、それを受け取ってcolProductsをClearCollectし直す必要があります。その判断を適切に行うために新たなフラグが必要になります。
FilterScreen の「適用」ボタン(OnSelect)
// フラグを立ててMainScreenに戻る
Navigate(
MainScreen,
ScreenTransition.None,
{ locShouldApplyFilter: true }
);
MainScreen.OnVisible(フラグ追加版)
// 初回のみ
If(
!locIsInitialized,
ClearCollect(colCategories, Categories);
ClearCollect(colProducts, Products);
UpdateContext({ locIsInitialized: true })
);
// 絞り込み条件の適用
If(
locShouldApplyFilter,
ClearCollect(
colProducts,
Filter(
Products,
(IsBlank(gblFilterCategory) || Category = gblFilterCategory)
&& (IsBlank(gblFilterStatus) || Status = gblFilterStatus)
&& Price >= gblFilterPriceMin
&& Price <= gblFilterPriceMax
)
);
UpdateContext({ locShouldApplyFilter: false })
);
何が起きているか
フラグは2つだけですが、すでに次のような問題が生まれています。
- フラグの対管理: FilterScreenでフラグを立て、MainScreen.OnVisibleでリセットする。この対応関係はコードのどこにも明示されておらず、FilterScreenを修正する人がこれを知っていなければなりません。
- 処理の意図が分散: 「FilterScreenから戻ったときに何が起きるか」を把握するには、FilterScreenのOnSelectとMainScreen.OnVisibleの両方を読む必要があります。
- 拡張性への不安: 新しい画面からMainScreenに遷移するケースが増えるたびに、同じ仕組みのフラグを追加していくことになります。
前回の「処理が分散する問題」は解決しましたが、今度は「集中した処理の中で、意図の交通整理ができない」という問題が生まれています。
実際に私も、意図の交通整理がしにくくなることで、細かい意図がコードの中に紛れ込んだようになり、「ここを修正したら意図しない動作をするのでは?」といった不安を抱えることになりました。
解決策を考えてみる:フラグではなく「意図」を渡す
フラグによる制御がなぜ煩雑になるかを考えると、根本的な原因は遷移元が「何をしてほしいか」ではなく「どのフラグを立てるか」で通信していることにあります。
フラグはあくまで処理のトリガーに過ぎません。本当に伝えたいのは、「この遷移は何の目的で行われたのか」という意図です。
この考え方を整理すると、次のようになります。
- 遷移元は、「なぜこの画面に来たのか(action)」を伝える
- 遷移先のOnVisibleは、受け取ったactionに応じて処理を振り分ける
- フラグの立て下ろしは不要になる。actionは遷移のたびに上書きされるため、リセット忘れが構造的に起きない
AIとの試行錯誤の中で形にしたこのアプローチを、 Navigation Context パターン とします。
Navigation Context パターン
Navigate関数の第3引数で渡せるコンテキスト変数を活用し、遷移の意図を構造化して渡してみます。
各遷移元のOnSelect
// ✅ アプリ起動時(InitScreenから遷移)
Navigate(
MainScreen,
ScreenTransition.None,
{ locNavigationContext: { action: "init" } }
);
// ✅ FilterScreenの「適用」ボタン
Navigate(
MainScreen,
ScreenTransition.None,
{ locNavigationContext: { action: "applyFilter" } }
);
// ✅ DetailScreenの「戻る」ボタン
Navigate(
MainScreen,
ScreenTransition.None,
{ locNavigationContext: { action: "back" } }
);
MainScreen.OnVisible
// ✅ navigationContextパターン
// actionに応じて処理を振り分ける
Switch(
locNavigationContext.action,
// アプリ起動時の初期化
"init",
ClearCollect(colCategories, Categories);
ClearCollect(colProducts, Products),
// 絞り込み条件の適用
"applyFilter",
ClearCollect(
colProducts,
Filter(
Products,
(IsBlank(gblFilterCategory) || Category = gblFilterCategory)
&& (IsBlank(gblFilterStatus) || Status = gblFilterStatus)
&& Price >= gblFilterPriceMin
&& Price <= gblFilterPriceMax
)
),
// 詳細画面から戻る(colProductsはそのまま維持)
"back",
// 何もしない
false
);
フラグ管理との比較
フラグ管理の場合、先ほどのコードでは各遷移パターンごとにフラグのセットとリセットの対を管理する必要がありました。Navigation Context パターンでは、この構造が次のように変わります。
| フラグ管理 | Navigation Context | |
|---|---|---|
| 遷移元が渡すもの | 個別のフラグ(locShouldApplyFilter: true) |
意図({ action: "applyFilter" }) |
| OnVisibleの構造 | 複数のIf文の羅列 | 1つのSwitch文 |
| リセット処理 | 各If文内で手動リセットが必要 | 不要(次の遷移で上書きされる) |
| 排他制御 | 全フラグの否定条件を手動で記述 | Switchのデフォルト分岐で自動的に処理 |
| 新しい導線の追加 | フラグ追加 + 既存の排他条件の見直し | Switch文にcaseを追加するだけ |
| 処理の意図の可読性 | フラグ名から推測 | action名で明示的 |
ポイント
Webフレームワークのルーティングとの類似性
このパターンを整理しているとき、AIから「Web開発におけるルーティングの考え方と似ている」と教えてもらいました。
たとえば、React RouterやVue Routerでは、URLのパスやクエリパラメータによって「どのページを表示するか」「どの状態で表示するか」を制御します。
/products?action=search&keyword=カメラ
/products?action=detail&id=123
言われてみれば確かに似ています。URLの代わりにコンテキスト変数を使い、actionで遷移の意図を伝えるという構造は同じです。Power AppsにはURLベースのルーティング機構はありませんが、Navigateの第3引数を活用することで、同様の設計思想を持ち込むことができるのでは?と思っています。
actionの命名について
action名は、「この遷移で何をしたいか」が一目でわかる名前にしておくと良さそうです。
// ✅ 良い例:意図が明確
"init" → アプリ起動時の初期化
"applyFilter" → 絞り込み条件の適用
"back" → 詳細を見て戻るだけ(何もしない)
// ❌ 避けたい例:何をするか曖昧
"mode1"
"flag_A"
"process"
チーム開発の場合、action名はアプリ内で一覧化しておくとさらに見通しがよくなります。
まとめ
前回の「処理を書く場所を意識する」に続き、今回は集中した処理の中身をどう整理するかについて考えました。
OnVisibleに処理を寄せるという方針自体は正しいのですが、遷移パターンが増えるとフラグによる制御が煩雑になります。フラグの立て忘れ・下ろし忘れ、排他条件の見落とし、処理の意図が読みにくくなる、といった課題が生まれます。
Navigation Contextパターンでは、遷移の「意図」を構造化して渡すことで、これらの課題を解消しようとしたものです。
- フラグの管理が不要になる
- Switchによる一元的な振り分けで見通しがよくなる
- 新しい導線の追加が既存処理に影響しにくくなる
「とりあえず動く」→「安心して変更できる」→「意図が読めるから安心して任せられる」。処理の書き方一つで、アプリの育てやすさが変わってきます。
次回予告(その3)
Navigation Contextパターンで、OnVisibleの入口は整理できました。しかし、さらに処理の量が増え複雑になってくると、次の壁にぶつかりました。
「分岐したパターンそれぞれで共通して行いたい処理がある」「何を処理して、何を処理しないかをもっとわかりやすくしたい」
当たり前ですが、遷移パターンごとに、実行すべき処理の組み合わせが違うのです。
次回は、この問題を解決しようとした「Execution Planパターン」を整理してみようと思います。Switchで遷移元を判別した後、どの処理ブロックを実行するかをフラグのセットとして宣言するアプローチです。


