パート 4 - 複数のプラットフォームを処理する
プラットフォームの相違の処理と機能
相違は単に 'クロスプラットフォーム' の問題ではありません。'同じ' プラットフォームのデバイスでも、異なる機能があります (特に、使用できる種類が多い Android デバイス)。 最もわかりやすく基本的なものは画面サイズですが、デバイスの他の属性もさまざまで、アプリケーションで特定の機能をチェックし、その存在 (または非存在) に基づいて動作を変えることが必要になる場合があります。
つまり、すべてのアプリケーションは、機能の正常な低下に対処する必要があり、そうしないと魅力的でない最低限度のセットを提示することになります。 Xamarin は各プラットフォームのネイティブ SDK と緊密に統合されており、アプリケーションはプラットフォーム固有の機能を利用できるので、それらの機能を使うようにアプリを設計することは理にかなっています。
プラットフォームによる機能の違いの概要については、プラットフォーム機能のドキュメントを参照してください。
プラットフォームの相違の例
プラットフォームに関係なく存在する基本的な要素
モバイル アプリケーションが共通して備える特性がいくつかあります。 これらは、一般的にすべてのデバイスに当てはまる上位の概念であるため、アプリケーションの設計の基礎となることがあります。
- タブまたはメニューによる機能の選択
- データの一覧とスクロール
- データの単一ビュー
- データの単一ビューの編集
- 戻るナビゲーション
上位レベルの画面フローを設計するときは、これらの概念を基にして共通のユーザー エクスペリエンスを作成できます。
プラットフォーム固有の属性
すべてのプラットフォームに存在する基本要素に加えて、プラットフォームの主要な違いに設計で対処する必要があります。 次のような違いに考慮する (そして、処理するためのコードを具体的に記述する) ことが必要になる場合があります。
- 画面サイズ – 一部のプラットフォーム (iOS や以前の Windows Phone バージョンなど) では、ターゲットにするのが比較的簡単な画面サイズが標準化されています。 Android デバイスの画面の寸法は非常に多様であり、アプリケーションでサポートするにはさらに多くの労力が必要です。
- ナビゲーションのメタファー – プラットフォーム間 (ハードウェアの '戻る' ボタン、パノラマ UI コントロールなど) およびプラットフォーム内 (Android 2 と 4、iPhone と iPad) で異なります。
- キーボード – 一部の Android デバイスには物理キーボードがありますが、それ以外はソフトウェア キーボードだけです。 ソフト キーボードが画面の一部を隠していることを検出するコードでは、これらの違いを認識する必要があります。
- タッチとジェスチャ – オペレーティング システムによるジェスチャ認識のサポートはさまざまであり、以前のバージョンの各オペレーティング システムでは特にそうです。 以前のバージョンの Android では、タッチ操作のサポートが非常に限られています。つまり、古いデバイスをサポートするには、別のコードが必要になる場合があります
- プッシュ通知 – プラットフォームごとに機能と実装が異なります (Windows でのライブ タイルなど)。
デバイス固有の機能
アプリケーションに必要な最小機能、または各プラットフォームで利用する追加機能を決定するときを確認します。 コードで機能を検出して、機能を無効にするか、代替手段を提供する必要があります (たとえば、地理的な場所の代わりに、ユーザーが場所を入力したり、地図から選んだりできるようにする)。
- カメラ – 機能は、デバイスによって異なります。カメラが付いていないデバイスもあれば、前面と背面の両方にカメラが付いているものもあります。 一部のカメラでは動画を記録できます。
- 地理位置とマップ – GPS または Wi-Fi の場所のサポートは、すべてのデバイスにありません。 また、アプリでは、各方法でサポートされる異なるレベルの精度に対応する必要があります。
- 加速度計、ジャイロスコープ、コンパス – これらの機能は、各プラットフォームでも特定のデバイスだけが備えていることがよくあるため、ほとんどの場合、アプリではハードウェアがサポートされていないときのフォールバックを提供する必要があります。
- Twitter と Facebook – それぞれ iOS5 と iOS6 にのみ '組み込まれて' います。 以前のバージョンや他のプラットフォームでは、各サービスの API を使って、独自の認証機能とインターフェイスを直接提供する必要があります。
- 近距離無線通信 (NFC) – (一部の) Android フォンのみ (執筆時点)。
プラットフォームの相違への対処
同じコード ベースから複数のプラットフォームをサポートするには 2 つの異なる方法があり、それぞれに固有の長所と短所があります。
- プラットフォーム抽象化 – ビジネス ファサード パターンは、プラットフォーム間で統一されたアクセスを提供し、特定のプラットフォームの実装を単一の統合 API に抽象化します。
- 異なる実装 – インターフェイスと継承や条件付きコンパイルなどのアーキテクチャ ツールを使用した、異なる実装による特定のプラットフォーム機能の呼び出し。
プラットフォームの抽象化
クラスの抽象化
共有コードで定義され、プラットフォーム固有のプロジェクトで実装または拡張された、インターフェイスまたは基底クラスの使用。 クラスの抽象化を使用した共有コードの記述と拡張は、使用できるフレームワークのサブセットが限られており、プラットフォーム固有のコード ブランチをサポートするためのコンパイラ ディレクティブを含むことができないため、移植可能なクラス ライブラリに特に適しています。
インターフェイス
インターフェイスを使うと、やはり共有ライブラリに渡して共通コードを利用できるプラットフォーム固有のクラスを実装できます。
インターフェイスは共有コードで定義され、パラメータまたはプロパティとして共有ライブラリに渡されます。
このようにすると、プラットフォーム固有のアプリケーションでインターフェイスを実装し、共有コードを利用してそれを '処理' できます。
長所
実装は、プラットフォーム固有のコードを含むことができ、プラットフォーム固有の外部ライブラリを参照することさえできます。
短所
実装を作成して共有コードに渡すことが必要。 インターフェイスが共有コードの深い部分で使われている場合、複数のメソッド パラメータを介して渡されるか、さもなければ呼び出しチェーンを介してプッシュダウンされることになります。 共有コードで多くの異なるインターフェイスが使われている場合、それらをすべて作成して、共有コードのどこかで設定する必要があります。
継承
共有コードでは、1 つ以上のプラットフォーム固有のプロジェクトで拡張できる抽象または仮想クラスを実装できます。 これはインターフェイスを使うのと似ていますが、一部の動作は既に実装されています。 インターフェイスまたは継承のどちらが設計の選択として優れているかについては、さまざまな観点があります。特に、C# では単一の継承のみが許されるため、以降の API の設計方法に影響する可能性があります。 継承は慎重に使用してください。
インターフェイスの長所と短所は継承にも同じように当てはまり、それに加えて、基底クラスに実装コードを含めることができるという長所があります (場合によっては、必要に応じて拡張できる、プラットフォームに依存しない実装全体)。
Xamarin.Forms
Xamarin.Forms のドキュメントをご覧ください。
その他のクロスプラットフォーム ライブラリ
次のライブラリでも、C# 開発者向けのクロスプラットフォーム機能が提供されています。
- Xamarin.Essentials – 共通機能用のクロスプラットフォーム API。
- SkiaSharp – クロスプラットフォーム 2D グラフィックス。
条件付きコンパイル
それでも、共有コードの動作をプラットフォームごとに変えることが必要な場合があり、動作が異なるクラスや機能にアクセスする可能性があります。 条件付きコンパイルは、異なるシンボルが定義されている複数のプロジェクトで同じソース ファイルが参照されている、共有アセット プロジェクトで最適に機能します。
Xamarin プロジェクトでは、iOS と Android 両方のアプリケーション プロジェクトの両方に対して true である __MOBILE__
が常に定義されています (これらのシンボルの前と後が 2 個のアンダースコアであることに注意してください)。
#if __MOBILE__
// Xamarin iOS or Android-specific code
#endif
iOS
Xamarin.iOS では __IOS__
が定義されており、iOS デバイスの検出に使用できます。
#if __IOS__
// iOS-specific code
#endif
ウォッチとテレビに固有のシンボルもあります。
#if __TVOS__
// tv-specific stuff
#endif
#if __WATCHOS__
// watch-specific stuff
#endif
Android
Xamarin.Android アプリケーションでのみコンパイルする必要があるコードでは、次を使用できます
#if __ANDROID__
// Android-specific code
#endif
各 API バージョンでは新しいコンパイラ ディレクティブも定義されているため、次のようなコードを使うと、新しい API が対象の場合に機能を追加できます。 各 API レベルには、すべての '下位' レベルのシンボルが含まれます。 この機能は、複数のプラットフォームをサポートする場合にはあまり役に立ちません。通常は、__ANDROID__
シンボルで十分です。
#if __ANDROID_11__
// code that should only run on Android 3.0 Honeycomb or newer
#endif
Mac
Xamarin.Mac で定義されている __MACOS__
を使うと、macOS だけのコンパイルを行うことができます。
#if __MACOS__
// macOS-specific code
#endif
ユニバーサル Windows プラットフォーム (UWP)
WINDOWS_UWP
を使用してください。 Xamarin プラットフォーム シンボルのような文字列を囲むアンダースコアはありません。
#if WINDOWS_UWP
// UWP-specific code
#endif
条件付きコンパイルの使用
条件付きコンパイルの簡単なケース スタディの例は、SQLite データベース ファイルでファイルの場所を設定する場合です。 3 つのプラットフォームでは、ファイルの場所の指定に関する要件が若干異なります。
- iOS – Apple では、ユーザー以外のデータは特定の場所 (Library ディレクトリ) に置くことが好まれますが、このディレクトリに対するシステム定数はありません。 正しいパスを構築するには、プラットフォーム固有のコードが必要です。
- Android –
Environment.SpecialFolder.Personal
によって返されるシステム パスは、データベース ファイルを格納できる場所です。 - Windows Phone – 分離ストレージ メカニズムでは、完全なパスを指定することはできず、相対パスとファイル名のみを指定できます。
- ユニバーサル Windows プラットフォーム –
Windows.Storage
API を使います。
次のコードでは、条件付きコンパイルを使って、DatabaseFilePath
がプラットフォームごとに正しくなるようにしています。
public static string DatabaseFilePath
{
get
{
var filename = "TodoDatabase.db3";
#if SILVERLIGHT
// Windows Phone 8
var path = filename;
#else
#if __ANDROID__
string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
#if __IOS__
// we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms
// (they don't want non-user-generated data in Documents)
string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
string libraryPath = Path.Combine (documentsPath, "..", "Library");
#else
// UWP
string libraryPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
#endif
#endif
var path = Path.Combine(libraryPath, filename);
#endif
return path;
}
}
その結果であるクラスは、すべてのプラットフォームでビルドして使用でき、SQLite データベース ファイルはプラットフォームごとに異なる場所に配置されます。