i Cubed Systems Engineering blog

株式会社アイキューブドシステムズの製品開発メンバーが、日頃のCLOMO開発の様子などを紹介します。

クロスプラットフォームでのコード共通化の取り組み

はじめに

みなさん、こんにちは。アイキューブドシステムズでモバイルアプリの開発を担当している tafuji-i3 です。以前、「モバイル DevOps の取り組み」の記事を書いていますので、よかったらこちらもご覧ください。

さて、今回の記事では、アイキューブドシステムズのクロスプラットフォームでのコード共通化の取り組みについて紹介したいと思います。コードの共通化を行うときに、「何を共通化したのか?」、「どのようにして共通化したのか?」、そして「どのようにして展開したのか?」について説明していきたいと思います。

なにを共通化したのか?

.NET(Xamarin) を使ったモバイルアプリ開発を学ぶときに、Xamarin でのモバイルアプリ開発の方法には、二つの方法があるということが説明されると思います。

ひとつは、ロジックのみを共通化し、UI はネイティブ(Xamarin.Android や Xamarin.iOS)で個別に実装する方法で、Xamarin Native などと呼ばれている方法です。もうひとつは、Xamarin.Forms を利用して、ロジックと UI を共通化する方法です。

Xamarin の開発スタイル

弊社のモバイルアプリケーションは、図の中の Xamarin Native の方針を採用しています。ユーザーインターフェース部分は各プラットフォームの UI の良さを利用したいからです。したがって、ロジック部分のコードの共通化を行っていくことにしました。具体的には、以下のようなものに関してコードの共通化を行っています。

  • 複数の製品に共通した業務処理のコードの共通化
    • CLOMO のサーバーの API を呼び出す処理の共通化
    • CLOMO でサポートする外部のサービス(Microsoft Exchange Online など)の APIを呼び出す処理の共通化
    • その他の製品内部で利用するユーティリティ
  • モバイルアプリでよく利用される機能の共通化
    • CLOMO 製品に依存しないモバイルアプリ開発時によく利用する機能の共通化

SECURED APPs のような同一製品では、ビジネスロジックのコードを共通化できるところまで持っていければ理想的ですが、このこと関しては今後の取り組むべき課題となっています。

どのようにして共通化したのか?

では、ロジック部分を共通で利用するにはどうしたらいいでしょうか?

モバイルアプリケーション開発では、プラットフォーム固有の機能を利用するためには、個別の API を利用する必要があります。例えば、アプリの設定情報を保存・取得するための APIは、iOS では NSUserDefaults クラスを利用しますし、 Android では SharedPreferences クラスを利用します。

このように、.NET Standard で提供されるクラスライブラリだけではアプリを作ることは困難です。

そこで、各アプリケーションのロジック部分から、各プラットフォームの固有の処理をアプリケーションのロジックから分離してコードの共通化を行っていくことにしました。

  • アプリケーション部分(Shared C# App Logic の部分)から呼び出されるコード(図の Shared C# codes / interface)は、.NET Standard で共通の interface や クラスを実装する
  • プラットフォーム固有の機能を利用する部分(C# iOS などの部分)は、「Shared C# codes / interface」経由で呼び出すようにする

これを実装するために、マルチターゲットに対応したクラスライブラリを作成することにしました。マルチターゲットのクラスライブラリを作成するときには、プロジェクトファイル(.csprj ファイル)に設定を追加する必要があります。

Xamarin でクラスライブラリをマルチターゲットに対応させるときに、どのような設定情報を追加したらよいか、あまり情報がなかったのですが、Microsoft から Xamarin.Essentials が 提供されたので、GitHub のソースを参考にして、設定を追加することができました。

Xamarin.Essentials の設定を見ても、設定されている項目が多いため、見るべきポイントを絞って紹介しておきます。これから、Xamarin でマルチターゲットのクラスライブラリを作成しようと思っている人の参考になれば幸いです。

  • マルチターゲットの設定部分 プロジェクトを複数のターゲットに対応させる設定部分では、対応するターゲットフレームワークを <TargetFrameworks> 要素の中で定義します。
<Project Sdk="MSBuild.Sdk.Extras/3.0.22">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.0;netstandard2.0;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid10.0;tizen40;Xamarin.Mac20;</TargetFrameworks>
    <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);uap10.0.16299;</TargetFrameworks>
</Project>
  • すべてのプラットフォームで利用されるコードに関する設定 すべてのプラットフォームで共通に利用されるソースコードをビルドするための設定は、以下のように設定されています。 すべてのターゲットフレームワークに対して、ファイルの拡張子が .shared.cs.shared.[任意の文字].cs となっているソースコードをビルドするという設定です。
  <ItemGroup>
    <Compile Include="**\*.shared.cs" />
    <Compile Include="**\*.shared.*.cs" />
  </ItemGroup>
  • 各プラットフォームに対応したコードに関する設定 ターゲットプラットフォームに対応したクラスをビルドするための設定は、以下のように設定されています。 ポイントは、ターゲットフレームワークの名前が、Xamarin.iOS で始まるときに、ファイルの拡張子が、.ios.cs.ios.[任意の文字].cs のソースコードをビルドするという設定の部分です。
  <ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
    <Compile Include="**\*.ios.cs" />
    <Compile Include="**\*.ios.*.cs" />
    <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
    <Reference Include="System.Numerics" />
    <Reference Include="OpenTK-1.0" />
  </ItemGroup>

プロジェクトファイルの設定を、自分だけで行っていたら非常に大変だったと思いますが、Xamarin.Essentials の設定を参考にできたので、非常に助かりました。

参考: .NET 6 の場合のコード共有はどうなるのか?

この記事を書いているうちに、.NET MAUI がリリースされました

Visual Studio 2022 Version 17.3.0 Preview 1.1 で .NET MAUI クラスライブラリのプロジェクトを作成すると、プラットフォーム固有の実装を行うための設定がすでに追加されているプロジェクトが作成されていることが分かります。

この記事で紹介しているようにプロジェクトファイルを自分で編集して設定を記述する必要がなくなり、マルチターゲットのクラスライブラリを楽に作成することができるようになっています。

どのようにして展開したのか?

共通コードをクロスプラットフォームで実装することはできるようになりました。次の課題は、共通コードをどのように各アプリケーション開発者に展開するかということでした。

共通コードを展開する一般的な方法としては、以下の二つの選択肢があると思います。

  • 選択肢 1:共通クラスライブラリとして NuGet で展開する
  • 選択肢 2:Git のサブモジュールでソースコードを共有する

アプリケーションの Xamarin 化を行う以前は、Git のサブモジュールを利用していましたが、Xamarin 化を行うにあたって、「選択肢 1」の共通クラスライブラリでアセンブリを展開する方法を採用しました。

その理由は、「選択肢 2」で長期間運用を行ってきた結果、各アプリケーションが参照しているサブモジュールのコミットにずれが生じるなどの問題が起きていたためです。こうなってしまうと、コードを共通化している意味がなくなってしまいます。チームのメンバーの一部からは、サブモジュールでソースコードを共有してデバッグを行いたいという要望がありましたが、共通コードの品質を担保することを約束することで、「選択肢 1」を採用することを了承してもらいました。

共通クラスライブラリの NuGet による展開

共通クラスライブラリを開発者に展開するには、どのようなアプローチをとったのかについて記載します。

  • 課題 1:社内で利用するライブラリであるため、パブリックなパッケージソース(NuGet)には公開できない
  • 課題 2:共通クラスライブラリを展開する前の品質確保

「課題 1」に関しては、プライベートな NuGet のパッケージソースを利用することで解決することができました。プライベートな NuGet のパッケージソースは、このドキュメントで紹介されているものの中で、Azure DevOps の Azure Artifacts を利用することにしました。

以下の図は、弊社の Azure Artifacts に登録されている共通ライブラリのリストです。

AzureArtifact

ここに登録された共通クラスライブラリは、チームのエンジニアの Visual Studio から参照することができるようになっています。

Visual Studio NuGet

「課題 2」に関しては、モバイル DevOps の取り組み の記事で紹介したように、クラスライブラリのビルド・単体テスト・NuGet へのプッシュを自動で行い、共通クラスライブラリの品質確保、パッケージの公開の作業を効率化しています。

まとめ

少し長くなりましたので、簡単にまとめておきます。

  • ロジック部分のコードの共通化を行っている
    • ユーザーインターフェース部分は、各プラットフォームの良さを活用するために個別に開発している
  • マルチターゲットのクラスライブラリでコードを共通化している
    • マルチターゲットのクラスライブラリを作成している
    • プロジェクトの設定は、Xamarin.Essentials を参考にしている
    • .NET MAUI がリリースされたので、今後は .NET MAUI のクラスライブラリプロジェクトを利用したほうが効率がよい
  • クラスライブラリは、プライベートな NuGet のパッケージソースで展開している
    • Azure Artifacts を利用している
    • クラスライブラリを NuGet パッケージとして公開する前には、Azure DevOps で自動単体テストを行っている

おわりに

今回の記事では、クロスプラットフォームでのコード共通化の取り組みを紹介いたしました。 私たちのチームでは、組織の規模や環境の変化の中で、少しづつカイゼンを積み重ねていっている様子を感じとっていただけると幸いです。

アイキューブドシステムズでは、一緒に働いてくれる仲間を募集しています。 このブログを通して興味を持たれたみなさまの応募をお待ちしております!