tafuji's blog

C#, Xamarin, Azure DevOps を中心に書いています。

Xamarin.Essentials 入門 - #1 Xamarin.Essentials とは

はじめに

この記事は、Xamarin Advent Calendar 2019 の 23日目の記事です。 Xamarin.Essentials 入門というタイトルで、Xamarin.Essentials の使い方と読み方について記載しています。

Xamarin.Essentials とは

Xamarin.Essentials とは、何かを簡単に説明します。Xamarin.Essentials とは、プラットフォーム固有の機能(位置情報、ネットワークとの接続状態、デバイス情報など)を利用するためのクロスプラットフォーム API です。Microsoft により提供されていて、v1.3 では 33 の機能が提供されています。

「Xamarin とは」という記事や書籍でよく紹介されますが、Xamarin を使ってアプリケーションを開発する方法として、二つの方法が紹介されることがよくあります。

xamarin-style

この図で述べられていることは、ユーザーインターフェース層におけるコードの共通化について述べられているのであって、図の中の Shared C# App Logic に相当する部分は、.NET Standard で実装できるように見えてしまいます。しかしながら、プラットフォーム固有の機能は、Xamarin.iOS や Xamarin.Android のクラスライブラリで提供されているため、.NET Standard で提供されるクラスライブラリだけではアプリを作ることは現実的ではありません。従って、「Shared C# App Logic」の部分からプラットフォーム固有の機能を利用するために、主に以下のような手法が用いられてきました。

  • Bait and Switch の手法を利用する
  • Interface を作成し、DependencyService / DI Container 経由で呼び出す処理を実装する

これまで上記のような手法で実装されてきたプラットフォーム固有の機能を利用するためのライブラリが、Microsoft から提供されるようになったというわけです。

shared-logic-essentials

Xamarin.Essentials の使い方

使う前の事前準備は、以下の通りです。

  • NuGet で Xamarin.Essentials をインストールする
    • Visual Studio 2019 で作成されたプロジェクトでは、最初からインストールされているようです
    • インストールする場合には、すべてのプロジェクトにインストールが必要
      • 例えば、Xamarin.Froms の場合は .NET Standard (Forms のプロジェクト)、iOSAndroid、UWP のプロジェクトにインストールが必要
  • 機能によっては、設定が必要な場合がある

各機能でクラス・メソッドが提供されているので、それを利用します。例えば、Preferences 機能(設定の取得・保存機能)には、Preferences クラスが提供されていて、利用するクラスに参照を追加して、Preferences クラスのメソッドを利用します。

using Xamarin.Essentials;

// set value
Preferences.Set("my_key", "my_value");

// get value
var myValue = Preferences.Get("my_key", "default_value");

Xamarin.Essentials のコードを読む

コードを読むと何かよいことがあるのか?

Xamarin.Essentials を利用するだけでなく、ぜひコードを読んでほしいと思っています。理由は、Xamarin.Essentials のソースコードは、プラットフォーム固有の機能を利用するときに、どのようなネイティブ API を利用したらよいかを学ぶのに役立つと思うからです。

例えば、Xamarin.Essentials の Preferences 機能(設定値の取得・保存用の API)のソースコードを読むと、iOS では NSUserDefaultsAndroid では SharedPreferences、UWP では ApplicationData を利用していることが分かります。

Xamarin.Essentials のソースコードは、GitHub に公開されています。

ここでは、Xamarin.Essentials のソースコードを「読む」ときのポイントとなる事柄について説明します。

どのように読み進めればよいのか?

Xamarin.Essentials のコードは、機能単位でフォルダにまとめられています。

essentials-top

プラットフォーム固有の機能でどのようなネイティブの API が利用されているかは、機能に該当するフォルダの中身のコードを読めばよいことになります。ここでは、各機能のコードを読んでいく際に役に立つポイントを記載しておきます。ポイントは以下の三つです。

  • マルチターゲットプロジェクト
  • どうやって複数のフレームワークのコードをビルドしているのか
  • どうやってプラットフォーム固有の処理を書いているのか

マルチターゲットプロジェクト

Xamarin.Essentials はマルチターゲットのクラスライブラリです。つまり、一つのクラスライブラリのプロジェクトで複数のプラットフォーム向けのアセンブリを作ることができます。Xamarin.Essentials のプロジェクトファイルを見てみましょう。ターゲットとなるフレームワークの設定部分を以下に抜粋しています。

<Project Sdk="MSBuild.Sdk.Extras/2.0.54">
    <PropertyGroup>
        <TargetFrameworks>netstandard1.0;netstandard2.0;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid60;MonoAndroid70;MonoAndroid71;MonoAndroid80;MonoAndroid81;MonoAndroid90;MonoAndroid10.0;tizen40;</TargetFrameworks>
        <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);uap10.0.16299;</TargetFrameworks>
    </PropertyGroup>
    <!-- .... -->
</Project>

<TargetFrameworks> タグの中にセミコロン区切りで指定されている文字列(Xamarin.iOS10, MonoAndroid10.0 など)は、プロジェクトをビルドするときのターゲットとなるプラットフォームを意味します。 Xamarin.Essentials は、以下のプラットフォームをターゲットとしていることが分かります。

プラットフォーム ターゲットフレームワークの設定値
.NET Standard netstandard1.0 または、netstandard2.0
Android MonoAndroid60 などの MonoAndroidxx (xx は数字) の形式。
iOS Xamarin.iOS10
UWP uap10.0.16299
tvOS Xamarin.TVOS10
watchOS Xamarin.WatchOS10
tvOS Xamarin.TVOS10
Tizen tizen40

どうやって複数のフレームワークのコードをビルドしているのか

Xamarin.iOS や Xamarin.Android のような異なるプラットフォームの API を利用するコードを、どのようにビルドしているのか? それを知るには、

<ItemGroup>
    <!-- .... -->
    <Compile Include="**\*.shared.cs" />
    <Compile Include="**\*.shared.*.cs" />
</ItemGroup>

<Compile Include="**\*.shared.cs" /><Compile Include="**\*.shared.*.cs" /> はファイル名に .shared. が含まれるファイルをコンパイルするということを意味しており、この条件にマッチするソースコードはすべてのプラットフォームにおいてコンパイル対象になることが分かります。

<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
    <Compile Include="**\*.ios.cs" />
    <Compile Include="**\*.ios.*.cs" />
    <Reference Include="System.Numerics" />
    <Reference Include="System.Numerics.Vectors" />
    <Reference Include="OpenTK-1.0" />
</ItemGroup>

Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) " は、ターゲットプラットフォームが Xamarin.iOS のときに有効になる設定です。<Compile Include="**\*.ios.cs" /><Compile Include="**\*.ios.*.cs" /> はファイル名に .ios. が含まれるファイルをコンパイルするという意味です。各プラットフォームでビルド対象となるファイル名の関係を、以下にまとめます。

プラットフォーム ビルド対象となるソースコード
.NET Standard ファイル名に .shared.、または .netstandard. が含まれるコード
Android ファイル名に .shared.、または .android. が含まれるコード
iOS ファイル名に .shared.、または .ios. が含まれるコード
UWP ファイル名に .shared.、または .uwp. が含まれるコード
tvOS ファイル名に .shared.、または .tvos. が含まれるコード
watchOS ファイル名に .shared.、または .watchos. が含まれるコード`
tvOS ファイル名に .shared.、または .tvos. が含まれるコード
Tizen ファイル名に .shared.、または .tizen. が含まれるコード

どうやってプラットフォーム固有の処理を書いているのか

どのようにプラットフォーム固有の処理を実装しているかについてですが、ポイントは以下となります。

  • 部分クラス (partial class) を利用して、プラットフォーム固有の部分を別のファイルに記述する

例として、Preferences 機能のコードを見ていきます。

preference-codes

プラットフォームで共通にビルドされるコード(Preferences.shared.cs )を以下に抜粋します。以下の二つの点に注目してください。

  • 部分クラスを利用している
  • PlatformX というメソッドが利用されているが、.shared. のファイルの中にそのメソッドの処理はかかれていない
public static partial class Preferences
{
    // .....
    public static bool ContainsKey(string key, string sharedName) =>
        PlatformContainsKey(key, sharedName);

    public static void Remove(string key, string sharedName) =>
        PlatformRemove(key, sharedName);

    public static void Clear(string sharedName) =>
        PlatformClear(sharedName);

    public static string Get(string key, string defaultValue, string sharedName) =>
        PlatformGet<string>(key, defaultValue, sharedName);
    // .....
}

次にプラットフォーム固有のソースコードを見てみます。例として、Android プラットフォームのコード(Preferences.android.cs)を抜粋します。

  • 部分クラスを利用している
  • .shared. のファイルに実装されていなかった PlatformX というメソッドが実装されている
public static partial class Preferences
{
    static readonly object locker = new object();

    static bool PlatformContainsKey(string key, string sharedName)
    {
        // ....
    }

    static void PlatformRemove(string key, string sharedName)
    {
        // ....
    }

    static void PlatformClear(string sharedName)
    {
        // ....
    }

    static T PlatformGet<T>(string key, T defaultValue, string sharedName)
    {
        // ....
    }
}

部分クラスを利用することで、ビルド時にターゲットとなるプラットフォームでビルド対象となるコードと、常にビルド対象となるコードが組み合わされてコンパイルされるように作られていることが分かります。

preference-codes

このように Xamarin.Essentials では、プラットフォーム固有の処理を分けてマルチターゲットのクラスライブラリを実装しているわけです。

おわりに

Xamarin.Essentials は使うのもよいのですが、コードを読むととても勉強になるライブラリなのではないかと思います。 理由は、プラットフォーム固有の機能を利用するときに、ネイティブでどの API を利用すればよいのかを学ぶことができるからです。

Xamarin を使っているエンジニアの中には、以前は C# .NET のアプリを作っていて、Xamarin からモバイルアプリを作るようになった人もいるのではないでしょうか。そいういう人の中で、ネイティブの知識不足に悩んでいる方がいらっしゃるかもしれません。そういう人にとって、Xamarin.Essentials のソースコードが、ネイティブの知識を効率的に学んでいくための一つの教材になるのではないかと思っています。

今回の記事は、Xamarin.Essentials の概要について書きましたが、個々の機能についても少しづつまとめていきたいと思っています。