パート 5 - 実践的なコード共有戦略

このセクションでは、一般的なアプリケーション シナリオでコードを共有する方法の例を示します。

データ レイヤー

データ レイヤーは、情報の読み取りと書き込みを行うストレージ エンジンとメソッドで構成されます。 パフォーマンス、柔軟性、プラットフォーム間の互換性のために、Xamarin クロスプラットフォーム アプリケーションには SQLite データベース エンジンをお勧めします。 これは、Windows、Android、iOS、Mac など、さまざまなプラットフォームで実行されます。

SQLite

SQLite は、オープンソースのデータベース実装です。 ソースとドキュメントは、SQLite.org にあります。SQLite のサポートは、各モバイル プラットフォームで利用できます。

すべてのプラットフォームでデータベース エンジンを使用できる場合でも、データベースにアクセスするためのネイティブ メソッドは異なります。 iOS と Android はどちらも、Xamarin.iOS または Xamarin.Android から使用できる SQLite にアクセスするための組み込みの API を提供しますが、ネイティブ SDK メソッドを使用してもコードを共有することはできません (おそらく SQL クエリ自体以外が文字列として格納されていると仮定した場合を除く)。 iOS の CoreData または Android の SQLiteOpenHelper クラスでのネイティブ データベース機能の検索の詳細については、これらのオプションはクロスプラットフォームではないため、このドキュメントの範囲外です。

ADO.NET

Xamarin.iOS と Xamarin.Android の両方で、System.DataMono.Data.Sqlite がサポートされています (詳細については、Xamarin.iOS のドキュメントを参照してください)。 これらの名前空間を使用すると、両方のプラットフォームで動作する ADO.NET コードを記述できます。 プロジェクトの参照を編集して System.Data.dllMono.Data.Sqlite.dll を含め、ステートメントを使用してこれらをコードに追加します。

using System.Data;
using Mono.Data.Sqlite;

その後、次のサンプル コードが機能します。

string dbPath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
    SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
    // This is the first time the app has run and/or that we need the DB.
    // Copy a "template" DB from your assets, or programmatically create one like this:
    var commands = new[]{
        "CREATE TABLE [Items] (Key ntext, Value ntext);",
        "INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
    };
    foreach (var command in commands) {
        using (var c = connection.CreateCommand ()) {
            c.CommandText = command;
            c.ExecuteNonQuery ();
        }
    }
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
    contents.CommandText = "SELECT [Key], [Value] from [Items]";
    var r = contents.ExecuteReader ();
    while (r.Read ())
        Console.Write("\n\tKey={0}; Value={1}",
                r ["Key"].ToString (),
                r ["Value"].ToString ());
}
connection.Close ();

ADO.NET の実際の実装は、明らかに異なるメソッドとクラスに分割されます (この例はデモンストレーションのみを目的としています)。

SQLite-NET – クロスプラットフォーム ORM

ORM (またはオブジェクト リレーショナル マッパー) は、クラスでモデル化されたデータの格納を簡略化しようとします。 CREATE TABLE を実行する、または、クラスのフィールドとプロパティから手動で抽出されたデータを SELECT、INSERT、DELETE する SQL クエリを手動で記述するのではなく、ORM は、これらの処理を実行するコードのレイヤーを追加します。 リフレクションを使用してクラスの構造を調べると、ORM は、自動的に、クラスに一致するテーブルと列を作成し、データの読み取りと書き込みを行うクエリを生成できます。 これにより、アプリケーション コードはオブジェクト インスタンスを ORM に送信して取得するだけになり、ORM は内部ですべての SQL 操作を処理します。

SQLite-NET は、SQLite でクラスを保存および取得できる単純な ORM として機能します。 コンパイラ ディレクティブとその他のテクニックを組み合わせることで、クロス プラットフォーム SQLite アクセスの複雑さが隠されます。

SQLite-NET の機能:

  • テーブルは、Model クラスに属性を追加することによって定義されます。
  • データベース インスタンスは、SQLite-Net ライブラリのメイン クラスである SQLiteConnection のサブクラスによって表されます。
  • データは、オブジェクトを使用して挿入、クエリ、削除できます。 SQL ステートメントは必要ありません (ただし、必要に応じて SQL ステートメントを記述できます)。
  • 基本的な Linq クエリは、SQLite-NET によって返されるコレクションに対して実行できます。

SQLite-NET のソース コードとドキュメントは GitHub の SQLite-Net で入手でき、両方のケーススタディで実装されています。 SQLite-NET コードの簡単な例 (Tasky Pro のケース スタディから) を次に示します。

まず、TodoItem クラスは属性を使用して、データベースの主キーとなるフィールドを定義します。

public class TodoItem : IBusinessEntity
{
    public TodoItem () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

これにより、SQLiteConnection インスタンスに対して次のコード行を使用して (SQL ステートメントなし)、TodoItem テーブルを作成できます。

CreateTable<TodoItem> ();

テーブル内のデータは、(ここでも、SQL ステートメントを必要とせずに) SQLiteConnection の他のメソッドで操作することもできます。

Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection

完全な例については、ケース スタディのソース コードを参照してください。

ファイル アクセス

ファイル アクセスは、確実に、すべてのアプリケーションの重要な部分です。 アプリケーションに含まれる可能性があるファイルの一般的な例を次に示します。

  • SQLite データベース ファイル。
  • ユーザーが生成したデータ (テキスト、画像、サウンド、ビデオ)。
  • キャッシュ用にダウンロードされたデータ (画像、html ファイル、PDF ファイル)。

System.IO 直接アクセス

Xamarin.iOS と Xamarin.Android の両方で、System.IO 名前空間のクラスを使用してファイル システムへのアクセスを許可します。

各プラットフォームには、それぞれに考慮する必要があるアクセス制限があります。

  • iOS アプリケーションは、ファイルシステム アクセスが厳密に制限されたサンドボックスで実行されます。 さらに、Apple は、バックアップされる特定の場所 (およびバックアップされない場所) を指定することで、ファイル システムの使用方法を指示しています。 詳細については、Xamarin.iOS でのファイル システムの操作に関連するガイドを参照してください。
  • Android でも、アプリケーションに関連する特定のディレクトリへのアクセスが制限されますが、外部メディア (SD カードなど) と、共有データへのアクセスもサポートされます。
  • Windows Phone 8 (Silverlight) では、ファイルへの直接アクセスは許可されません。ファイルは IsolatedStorage を使用してのみ操作できます。
  • Windows 8.1 WinRT および Windows 10 UWP プロジェクトでは、Windows.Storage API を介した非同期のファイル操作のみが提供されます。これは、他のプラットフォームとは異なります。

iOS と Android の例

テキスト ファイルを書き込んで読み取る簡単な例を次に示します。 Environment.GetFolderPath を使用すると、iOS と Android で同じコードを実行できます。各コードは、ファイルシステム規則に基づいて有効なディレクトリを返します。

string filePath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));

iOS 固有のファイルシステム機能の詳細については、Xamarin.iOS のファイル システムの操作に関するドキュメントを参照してください。 クロスプラットフォームのファイル アクセス コードを記述するときは、一部のファイルシステムでは大文字と小文字が区別され、ディレクトリ区切り記号が異なることに注意してください。 ファイル パスまたはディレクトリ パスを構築するときは、常にファイル名と Path.Combine() メソッドで同じ大文字と小文字の組み合わせを使用することをお勧めします。

Windows 8 および Windows 10 用 Windows.Storage

Xamarin.Forms を使用したモバイル アプリの作成に関する書籍の第 20 章「非同期 I/O とファイル I/O」には、Windows 8.1 と Windows 10 のサンプルが含まれています。

DependencyService とサポートされている API を使用して、これらのプラットフォーム上のファイルを読み取ってファイルを作成できます。

StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
                                        CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");

詳細については、書籍の第 20 章を参照してください。

Windows Phone 7 および 8 の分離ストレージ (Silverlight)

分離ストレージは、すべての iOS、Android、および以前の Windows Phone プラットフォームでファイルを保存および読み込むための一般的な API です。

これは、一般的なファイルアクセス コードを記述できるようにするために Xamarin.iOS および Xamarin.Android で実装されている Windows Phone (Silverlight) のファイル アクセスの既定のメカニズムです。 System.IO.IsolatedStorage クラスは、共有プロジェクト内の 3 つのプラットフォームすべてで参照できます。

詳細については、Windows Phone の分離ストレージの概要の説明を参照してください。

分離ストレージ API は、ポータブル クラス ライブラリでは使用できません。 PCL の代替手段の 1 つは、PCLStorage NuGet です

PCL でのクロスプラットフォーム ファイル アクセス

また、Xamarin でサポートされているプラットフォームと最新の Windows API のクロスプラットフォーム ファイル アクセスを促進する、PCL 互換の NuGet (PCLStorage) もあります。

ネットワーク運用担当者

ほとんどのモバイル アプリケーションには、次のようなネットワーク コンポーネントがあります。

  • 画像、ビデオ、オーディオ (サムネイル、写真、音楽など) のダウンロード。
  • ドキュメント (HTML、PDF など) のダウンロード。
  • ユーザー データ (写真やテキストなど) のアップロード。
  • Web サービスまたはサード パーティ API (SOAP、XML、JSON を含む) へのアクセス。

.NET Framework には、ネットワーク リソースにアクセスするためのいくつかの異なるクラス ( HttpClientWebClientHttpWebRequest) が用意されています。

HttpClient

System.Net.Http 名前空間の HttpClient クラスは、Xamarin.iOS、Xamarin.Android、ほとんどの Windows プラットフォームで使用できます。 この API をポータブル クラス ライブラリ (および Windows Phone 8 Silverlight) に取り込むのに使用できる Microsoft HTTP クライアント ライブラリ NuGet があります。

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);

WebClient

WebClient クラスは、リモート サーバーからリモート データを取得するための簡単な API を提供します。

Xamarin.iOS と Xamarin.Android で同期操作 (バックグラウンド スレッドで実行できる) がサポートされている場合でも、ユニバーサル Windows プラットフォーム操作は非同期である必要があります

単純な非同期 WebClient 操作のコードは次のとおりです。

var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
    var resultString = e.Result;
    // do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));

WebClient には、バイナリ データを取得するための DownloadFileCompletedDownloadFileAsync もあります。

HttpWebRequest

HttpWebRequest では、WebClient よりも多くのカスタマイズが提供されるため、結果として、より多くのコードを使用する必要があります。

単純な同期 HttpWebRequest 操作のコードは次のとおりです。

var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    if (response.StatusCode != HttpStatusCode.OK)
        Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        var content = reader.ReadToEnd();
        // do something with downloaded string, do UI interaction on main thread
    }
}

Web サービスのドキュメントに例があります。

Reachability

モバイル デバイスは、高速の Wi-Fi または 4G 接続から、低品質の受信エリアや低速 EDGE データ リンクまで、さまざまなネットワーク条件下で動作します。 このため、リモート サーバーへの接続を試みる前に、ネットワークが使用可能かどうかを確認し、使用可能な場合はそのネットワークの種類を検出することをお勧めします。

このような状況でモバイル アプリが実行する可能性があるアクションは次のとおりです。

  • ネットワークが使用できない場合は、ユーザーに知らせます。 手動で無効にした場合 (例: 機内モードまたは Wi-Fi をオフにした) は、ユーザーが問題を解決できます。
  • 接続が 3G の場合、アプリケーションの動作が異なる可能性があります (たとえば、Apple では、20 MB を超えるアプリを 3G 経由でダウンロードすることを許可しません)。 アプリケーションでは、この情報を使用して、大きなファイルを取得するときにダウンロード時間が長くなることについてユーザーに警告することができます。
  • ネットワークが使用可能な場合でも、他の要求を開始する前に、ターゲット サーバーとの接続を確認することをお勧めします。 このようにすることで、アプリのネットワーク操作が繰り返しタイムアウトするのを防ぎ、より有益なエラー メッセージをユーザーに表示することもできます。

WebServices

Xamarin.iOS を使用した REST、SOAP、WCF エンドポイントへのアクセスについて説明している、Web サービスの操作に関するドキュメントを参照してください。 Web サービス要求を手動で作成し、応答を解析することはできますが、Azure、RestSharp、ServiceStack など、これを簡単にするために使用できるライブラリがあります。 基本的な WCF 操作にも Xamarin アプリでアクセスできます。

Azure

Microsoft Azure は、データ ストレージと同期、プッシュ通知など、モバイル アプリ向けにさまざまなサービスを提供するクラウド プラットフォームです。

azure.microsoft.com にアクセスして無料で試すことができます。

RestSharp

RestSharp は、Web サービスへのアクセスを簡略化する REST クライアントを提供するためにモバイル アプリケーションに含めることができる .NET ライブラリです。 これは、データを要求し、REST 応答を解析する単純な API を提供することで役立ちます。 RestSharp は便利です

RestSharp Web サイトには、RestSharp を使用して REST クライアントを実装する方法に関するドキュメントが含まれています。 RestSharp は、GitHub で Xamarin.iOS と Xamarin.Android の例を提供します。

Web サービス ドキュメントには Xamarin.iOS のコード スニペットもあります。

ServiceStack

RestSharp とは異なり、ServiceStack は、Web サービスをホストするためのサーバー側のソリューションであると同時に、それらのサービスにアクセスするためにモバイル アプリケーションに実装できるクライアント ライブラリでもあります。

ServiceStack の Web サイトでは、プロジェクトの目的と、ドキュメントとコード サンプルへのリンクについて説明しています。 例には、Web サービスの完全なサーバー側の実装と、それにアクセスできるさまざまなクライアント側アプリケーションが含まれます。

WCF

Xamarin ツールは、一部の Windows Communication Foundation (WCF) サービスを使用するのに役立ちます。 一般に、Xamarin では、Silverlight ランタイムに付属する WCF の同じクライアント側サブセットがサポートされます。 これには、WCF の最も一般的なエンコードとプロトコルの実装である、BasicHttpBinding を使用した HTTP トランスポート プロトコル経由のテキストエンコードされた SOAP メッセージが含まれます。

WCF フレームワークのサイズと複雑さが原因で、現在および将来のサービス実装の一部が、Xamarin のクライアントサブセット ドメインでサポートされるスコープには含められない可能性があります。 さらに、WCF のサポートでは、プロキシを生成するために Windows 環境でのみ使用できるツールの使用が求められます。

スレッド化

アプリケーションの応答性はモバイル アプリケーションにとって重要です。ユーザーはアプリケーションの迅速な読み込みと実行を期待します。 アプリケーションがクラッシュしたことを示すために、ユーザー入力の受け入れを停止した "フリーズ状態の" 画面が表示されるため、ネットワーク要求や低速なローカル操作 (例: ファイルの解凍) などの実行時間の長い呼び出しブロックに UI スレッドを関連付けないようにすることが重要です。 特に、スタートアップ プロセスには実行時間の長いタスクを含めないようにする必要があります。すべてのモバイル プラットフォームでは、読み込みに時間がかかりすぎるアプリが強制終了されます。

つまり、ユーザー インターフェイスに、"進行状況インジケーター" またはすばやく表示できる "使いやすい" UI を実装し、バックグラウンド操作を実行するために非同期タスクを実装する必要があることを意味します。 バックグラウンド タスクを実行するにはスレッドを使用する必要があります。つまり、バックグラウンド タスクは、進行状況や完了日時を示すためにメイン スレッドに戻って通信する方法が必要です。

並列タスク ライブラリ

並列タスク ライブラリを使用して作成されたタスクは、非同期的に実行して呼び出し元のスレッドで戻ることができるため、ユーザー インターフェイスをブロックせずに実行時間の長い操作をトリガーする場合に非常に便利です。

単純な並列タスク操作は次のようになります。

using System.Threading.Tasks;
void MainThreadMethod ()
{
    Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
        t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
    );
}

キーは TaskScheduler.FromCurrentSynchronizationContext() で、そのスレッドへの呼び出しをマーシャリングする方法として、メソッドを呼び出すスレッド (ここでは、MainThreadMethod を実行しているメイン スレッド) の SynchronizationContext.Current を再利用します。 つまり、メソッドが UI スレッドで呼び出されると、UI スレッドに戻って ContinueWith 操作が実行されます。

コードが他のスレッドからタスクを開始している場合は、次のパターンを使用して UI スレッドへの参照を作成します。タスクは、その後 UI スレッドにコールバックできます。

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

UI スレッドでの呼び出し

並列タスク ライブラリを使用しないコードの場合、各プラットフォームには、操作を UI スレッドに戻してマーシャリングするための独自の構文があります。

  • iOSowner.BeginInvokeOnMainThread(new NSAction(action))
  • Androidowner.RunOnUiThread(action)
  • Xamarin.FormsDevice.BeginInvokeOnMainThread(action)
  • WindowsDeployment.Current.Dispatcher.BeginInvoke(action)

iOS と Android の両方の構文では、"context" クラスを使用できる必要があります。つまり、コードはこのオブジェクトを UI スレッドのコールバックを求める任意のメソッドに渡す必要があります。

共有コードで UI スレッドを呼び出すには、IDispatchOnUIThread の例に従います (@follesoe 提供)。 共有コード内の IDispatchOnUIThread インターフェイスを宣言してプログラミングし、次に示すようにプラットフォーム固有のクラスを実装します。

// program to the interface in shared code
public interface IDispatchOnUIThread {
    void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly NSObject owner;
    public DispatchAdapter (NSObject owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.BeginInvokeOnMainThread(new NSAction(action));
    }
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly Activity owner;
    public DispatchAdapter (Activity owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.RunOnUiThread(action);
    }
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
    public void Invoke (Action action) {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }
}

Xamarin.Forms 開発者は、共通コード (共有プロジェクトまたは PCL) で Device.BeginInvokeOnMainThread を使用する必要があります。

プラットフォームとデバイスの機能と低下

さまざまな機能を扱う具体的な例については、プラットフォーム機能に関するドキュメントを参照してください。 アプリが最大限にその潜在能力を発揮できない場合でも、優れたユーザー エクスペリエンスを提供するために、さまざまな機能を検出し、アプリケーション機能を適切に低下させる方法を扱います。