※https://docs.flutter.dev/ui/adaptive-responsive/capabilities のリファレンスブログです。
実際のアプリケーションの多くは、異なるデバイスやプラットフォームの機能やポリシーに適応する必要があるそうです。 このページでは、あなたのコードでこのようなシナリオを処理する方法について提案されています。
1.各デバイスタイプの強みを生かした設計
さまざまなデバイスの長所と短所を考慮する。 画面サイズやタッチ、マウス、キーボードなどの入力だけでなく、他にどんなユニークな機能を活用できるだろうか? Flutterはあなたのコードをさまざまなデバイスで実行できるようにしますが、強力なデザインはコードを実行するだけではありません。 それぞれのプラットフォームが得意とすることを考え、活用できるユニークな機能があるかどうかを確認しよう。
例えば アップルのApp StoreとグーグルのPlay Storeでは、アプリが遵守すべきルールが異なる。 ホストとなるオペレーティングシステムが異なれば、その機能も異なる。
もう一つの例は、ウェブの共有の障壁が極めて低いことを活用することだ。 ウェブアプリをデプロイする場合、どのようなディープリンクをサポートするかを決め、それを念頭に置いてナビゲーションルートを設計する。
これらのユニークな能力に基づいて異なる動作を処理するためのFlutterの推奨パターンは、アプリのCapabilityクラスとPolicyクラスのセットを作成することです。
1-1.Capability(機能)
Capability クラスは、アプリやデバイスが 何ができるか を定義します。たとえば、APIの存在確認や、ハードウェアの有無、OSの制限などが該当します。アプリが特定の機能を実行する前に、その機能がデバイスで利用可能かどうかを確認するために使います。
(Capabilityの例)
・APIの存在確認: ある機能がOSによって提供されているかを確認する。
・OSが強制する制限
・物理的なハードウェア要件(カメラなど): メラやGPSなどのデバイスが存在するかを確認する。
bool hasCamera = checkCameraAvailability();
if (hasCamera) {
// カメラ機能を実行
}
1-2.Policy(ポリシー)
Policy クラスは、アプリが 何をすべきか を定義します。例えば、アプリストアのガイドラインやビジネスルールに基づいて、特定の動作を制限したり、サーバー側で有効にされている機能を制御します。
(Policyの例)
・アプリストアのガイドライン:
iOSでは特定の購入リンクを表示しないなど、プラットフォームに応じた制限を設ける。
・デザイン設定
・ホストデバイスを参照するアセットまたはコピー
・サーバー側で有効化された機能:
サーバーの設定に応じて、アプリ内の機能を有効・無効にする。
bool shouldAllowPurchaseClick() {
return !Platform.isIOS; // iOSでは購入リンクを表示しない
}
(補足)CapabilitiesとPoliciesの使い分け
・Capability
デバイスやプラットフォームが持っている機能やAPIの利用可能性を判断する。
・Policy
外部のガイドラインやビジネスルールに基づき、アプリがどう振る舞うべきかを決定する。
この2つを分けて設計することで、コードが柔軟かつ保守しやすくなります。たとえば、ある機能が特定のプラットフォームでサポートされていない場合は Capability で制御し、アプリストアのポリシーに従って表示する内容を変えたい場合は Policy を使用します。
(まとめ)
・Capability は、アプリが「できること」を管理するクラスです。例えば、ハードウェアやAPIの利用可能性をチェックするために使います。
・Policy は、アプリが「すべきこと」を管理するクラスです。外部のルールやビジネス要件に従って、アプリの振る舞いを制御します。
このように、Capability と Policy クラスを分けて設計することで、異なるプラットフォームやデバイスに対応した柔軟なアプリケーションを作成できます。
1-3.ポリシーコード構成方法
最も単純で機械的な方法は、Platform.isAndroid、Platform.isIOS、kIsWebです。 これらのAPIは、機械的にコードがどこで実行されているかを知ることができますが、アプリが実行できる場所を拡大したり、ホスト・プラットフォームが機能を追加したりすると、いくつかの問題が発生します。
以下のガイドラインでは、アプリの機能とポリシーを開発する際のベストプラクティスを説明します。
(1)Platform.isAndroid(デバイス判定)
Platform.isAndroidや同様の関数を使用して、レイアウトを決定したり、デバイスができることを推測したりすることは避けて下さい。
その代わりに、何を分岐させたいかをメソッドに記述します。
例えば、アプリの某機能(ウェブサイトで何かを購入するためのリンク表示)iOSデバイスに(ポリシー上の理由から)そのリンクを表示したくない、という場合を想定しましょう。
以下のコードは、iOS デバイスでの購入リンクの表示を制限するロジックを実装したものです。具体的には、Apple App Store のガイドラインに従って、iOS デバイス上ではブラウザ内で購入リンクを表示させないようにしています。
//この関数は、購入リンクを表示してよいかどうかを決定します。
bool shouldAllowPurchaseClick() {
//Platform.isIOS は、
//アプリが iOS デバイス上で実行されているかどうか
//を確認するための条件です。
//true を返す場合、iOS デバイス上でアプリが動作しています。
return !Platform.isIOS;
}
...
TextSpan(
text: 'Buy in browser',
style: new TextStyle(color: Colors.blue),
recognizer: shouldAllowPurchaseClick ? TapGestureRecognizer()
..onTap = () { launch('<some url>') : null;
} : null,
(補足)
TextSpan
・TextSpan は、リッチテキストの一部を定義します。
・この場合、「Buy in browser」(ブラウザで購入)というテキストが表示されます。
TapGestureRecognizer
・TapGestureRecognizer は、テキストがタップされた際のアクション(タップジェスチャー)を処理します。
・この場合、リンクがクリックされたときに指定された URL を開く処理を行います。
shouldAllowPurchaseClick ? TapGestureRecognizer() … : null
・ここでは、shouldAllowPurchaseClick の結果が true であれば TapGestureRecognizer を設定し、テキストがクリック可能となります。つまり、iOS 以外のプラットフォームではリンクをタップしてブラウザを開くことができます。
・もし shouldAllowPurchaseClick が false(iOS で実行中)の場合、recognizer に null が設定されるため、リンクをクリックしても何も起きません。
(2)間接層(indirection layer)の追加
Policy クラスを使ってポリシーを管理することで、コードの構造がシンプルになり、テストやメンテナンスが容易になります。つまり、間接層(indirection layer)を追加することで、コードの明確さと再利用性が向上する という概念が、説明されています。
①間接層
文章の冒頭では、「間接層(indirection layer)」を追加することで、コードの意味がより明確になると述べています。この場合、shouldAllowPurchaseClick() メソッドが分岐処理をカプセル化し、なぜこの分岐が存在するのかが明確に示されています。例えば、App Store のガイドラインに基づいて iOS では購入リンクを無効化している理由がメソッド内で明確になります。
②再利用性の向上
この shouldAllowPurchaseClick() メソッドをクラス内に移動することで、他の部分でも同じチェックを行う必要がある場合に、そのロジックを簡単に再利用できるようになります。このように、特定のポリシーやビジネスロジックを一箇所にまとめておくことで、コードの冗長性を減らし、変更が必要な場合に一箇所を修正するだけで済むため、メンテナンスが簡単になります。
③テストのしやすさ
Policy クラスにメソッドをまとめることで、テストの際に Policy().shouldAllowPurchaseClick() をモック(偽装)して、プラットフォームに依存しない形で機能を確認できます。たとえば、iOS での動作をシミュレートする必要がある場合、その環境を再現せずにモックによってその結果を確認することが可能です。
④コード変更の柔軟性
将来的に、たとえば Android ユーザーにも同様の制限を設けたい場合でも、このクラスのロジックを変更するだけで対応可能です。Policy クラスに分岐処理をまとめることで、特定の動作を複数の箇所で簡単に適用したり、テストしたりすることが可能になります。
class Policy {
bool shouldAllowPurchaseClick() {
//Apple App Store ガイドラインによって、
//iOSでは購入リンクを無効にする
return !Platform.isIOS;
}
}
このコードを使えば、どの場所でも Policy クラスの shouldAllowPurchaseClick() メソッドを呼び出して、購入リンクの表示を制御できます。もし今後、Android でも同様のガイドラインが適用された場合、ロジックを変更してもテストコードや他のコードはそのままにすることができます。
このアプローチにより、ビジネスロジックを一箇所に集中させることで、コードの可読性や保守性が向上し、テストの容易さも実現できます。
2.Capabilities(機能)
ここではFlutterアプリケーション開発における“Capabilities”(能力や機能)の概念について述べています。
Capabilitiesは、アプリケーションが特定のプラットフォームやデバイスで実行できる機能やAPIに依存する部分です。
(1)Capabilities(能力)の定義
Capabilitiesは、「アプリが何をできるか」を定義します。これは、プラットフォームやデバイスが提供するAPIやハードウェア機能に依存する部分です。
例えば、カメラ、マイク、GPS、特定のAPIなど、プラットフォーム固有の機能を使いたい場合、該当するAPIや機能がそのデバイスでサポートされているかを確認する必要があります。
(2)CapabilitiesとPolicyの違い
文章で述べているように、**”Capabilities”は「アプリができること」であり、“Policy”**は「アプリがすべきこと」を定義します。
Policyはビジネスルールやアプリストアのガイドラインによって決まりますが、Capabilitiesはデバイスやプラットフォームの技術的な制約に依存します。
これらを分けて考えることが、アプリの大規模なプロジェクトで重要です。例えば、新しいAPIがプラットフォームに追加されたり、プラットフォームが提供する機能が更新された場合、アプリの機能を柔軟に対応させるためにCapabilitiesが役立ちます。
(3)具体例(プラットフォーム間のAPIの違い)
説明文では、特定のプラットフォームに新しい権限が追加された場合の例を挙げています。例えば、新しいプラットフォーム(Platform 1)がユーザーに許可を求めるダイアログを表示する必要があるAPIを導入したとします。この場合、開発チームはそのプラットフォーム用に新しい「能力」(requirePermissionDialogFlow)を実装します。
もし将来的に他のプラットフォーム(Platform 2)が類似した制限を導入する場合でも、その能力を再利用できます。具体的には、requirePermissionDialogFlow メソッドがAPIのバージョンやプラットフォームを確認し、適切に動作するようになります。これにより、一度実装したロジックを他のプラットフォームにも適用することができ、開発効率を向上させます。
(4)なぜCapabilitiesとPolicyを分けるのか
Flutterのようなマルチプラットフォームアプリケーションでは、さまざまなプラットフォームごとの違いに対応する必要があります。CapabilitiesとPolicyを分けることで、以下のような利点があります。
1.コードの明確化
アプリが「できること」と「すべきこと」を明確に分離することで、コードの意味が明瞭になります。
2.テストの容易さ
個別にテストやモックを行うことができ、プラットフォーム固有の依存関係からテストを独立させることができます。
3.将来の変更への柔軟性
プラットフォームが新しい機能や制約を追加しても、既存のCapabilitiesを拡張することで対応できます。
(5)まとめ
この文章は、FlutterアプリにおけるCapabilitiesの役割を強調しています。アプリが実行されるデバイスやプラットフォームの能力に基づいて機能を分岐させるために、Capabilitiesクラスを使うことで、将来の変更や機能追加に柔軟に対応できるアーキテクチャを構築することができるという考え方です。
3.Policy(ポリシー)
アプリケーションにおけるポリシーの判断をどのように設計・実装するかについて、3つの方法を提案しています。
アプリの成長に伴ってポリシーの管理が複雑になる可能性があるため、Policy クラスを導入しておくことが推奨されており、ポリシーの変更頻度や重要度に応じて、適切な実装方法を選択することが重要です。
(1)初期的な Policy クラスの導入
最初は、ポリシーに関する判断があまり必要でないと感じても、Policy クラスを導入することが推奨されています。
なぜなら、アプリが成長し、複雑さが増すにつれて、ポリシーに関する判断も増える可能性があるからです。
クラスが複雑になるか、入力が増えた場合には、機能ごとやその他の基準に基づいて Policy クラスを分割することが有効です。
(2)ポリシーの実装方法
ポリシーの実装には主に3つの方法があります。
①コンパイル時のチェック
これは、特定のプラットフォームで設定が変更される可能性が低い場合に適しています。間違って値が変更されると重大な結果を招く可能性がある場合に特に有効です。たとえば、あるプラットフォームがPlayストアへのリンクを禁止していたり、特定の支払いプロバイダーを使用することがアプリの内容に応じて求められる場合です。
②実行時のチェック
実行時にデバイスの機能を確認したい場合に有効です。たとえば、タッチスクリーンが使用できるかどうかを確認する場合です。Androidではタッチスクリーンの有無を確認するための機能があり、ウェブ実装では最大タッチポイントの数を確認することができます。
③RPC ( リモートプロシージャコール ) を使ったポリシー変更
機能の段階的なロールアウトや、後で変更される可能性があるポリシー判断に適しています。サーバー側からポリシーや機能の状態を確認・変更することができ、必要に応じてユーザー体験を段階的に調整することが可能です。
4.まとめ
Capabilityクラスを使って、コードに何ができるかを定義する。 APIの存在、OSによる制限、物理的なハードウェア要件(カメラなど)をチェックすることができる。 ケイパビリティは通常、コンパイルやランタイムのチェックを伴います。
Policyクラス(または複雑さに応じて複数のクラス)を使用して、App Storeのガイドライン、デザイン設定、ホストデバイスを参照する必要があるアセットやコピーに準拠するためにコードが何をすべきかを定義します。 Policyは、コンパイル、ランタイム、またはRPCチェックを組み合わせることができます。
能力やポリシーが変更されてもウィジェットのテストが変更されないように、Capability クラスや Policy クラスをモックすることで分岐コードをテストする。
capabilitiesクラスとpolicyクラスのメソッド名は、デバイスの種類ではなく、何を分岐させようとしているかに基づいて命名する。