読者です 読者をやめる 読者になる 読者になる

ぴよぴよエンジニアの日記

クラウドベンダーに勤める見習いSEの日記です。発言は私自身の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Xamarin.Forms × IBM MobileFirst Foundation

f:id:Santea:20161203154823j:plain

本エントリーは、[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016 - Qiita の3日目です.

なお、本エントリーの掲載内容は私自身の見解であり、必ずしもIBMの立場、戦略、意見を代表するものではありません.





初めに

本エントリーでは、IBM MobileFirst Foundation の Xamarin.Forms サンプルの実装を行います.

細かな解説は連載エントリーとしてまとめたいと考えておりますので今回は触りだけです.

サンプルから抜粋してプッシュ通知の実装を行います.


qiita.com

Xamarin で利用可能な mBaaS は Microsoft Azure Mobile Apps, Google Firebase, AWS Mobile Hub などが挙げられます.

各 mBaaS の比較は @amay077 さんのエントリーはご覧下さい.


あまり認知されていないかもしれませんが、IBM も Xamarin で利用可能な mBaaS を提供しています.

IBM MobileFirst Foundation(旧称:Worklight)です.

www-03.ibm.com

詳しい機能は上記をご参照ください.


本エントリーでは、MobileFirst Foundation の Xamarin.Forms サンプルの実装を行います.

www.youtube.com

元となるサンプル動画はこちらになります.

本エントリーでは、Sharedプロジェクトであるサンプルを、PCLプロジェクトで実装します.

PCLプロジェクトでは、Prism.Forms と ReactiveProperty を用います.

www.nuits.jp
github.com

必要に応じて上記をご参照ください.



実装環境

  • OS X EI Capitan 10.11.6
  • Xamarin Studio 6.1.2
  • Xamarin.Forms 2.3.3.168
  • Prism.Forms 6.2.0
  • Reactive Property 2.9.0
  • IBM MobileFirst Foundation 8.0.0.4



バックエンド: IBM MobileFirst Foundation の実装

IBM MobileFirst Platform は IBM が提供する PaSS である Bluemix のサービスの1つです.

まずは Bluemix アカウント登録を行いましょう.

Bluemix には30日のフリートライアル期間が提供されています.

Bluemix の基礎: フリートライアルを開始する

上記ページを参照し、フリートライアルの申し込みを行ってください.
(ちなみにアクティベートのメールがすぐ返って来ないかもしれませんが、少し待つと返ってくるのでご安心ください)

アクティベートを行ったら、さっそくログインしてみましょう.



プロジェクトの作成

f:id:Santea:20161203222541j:plain

Bluemix のログイン後の画面で、右上の"カタログ"を選択します.



f:id:Santea:20161203222553j:plain:w360

左側にカタログ一覧が表示されるので、その中の"モバイル"を選択します.



f:id:Santea:20161203223040j:plain

バイルアプリケーション一覧が表示されるので、"Mobile Foundation" を選択しましょう.



f:id:Santea:20161203225301j:plain

Mobile Foundationサービスを作成します. 今回は、Mobile Foundation-Sample という名前にしました.
価格プランはデフォルトの開発者のままで作成します.



f:id:Santea:20161203224101j:plain

サービス作成が成功したら、基本サーバを起動を行います.



f:id:Santea:20161203025654p:plain

サーバの起動には少し時間が掛かります(私の環境では3分程度掛かりました).



f:id:Santea:20161203224652j:plain

サーバが起動するとこのような画面が表示されていると思います.
次にコンソールの起動を行います. 目のマークをクリックするとパスワードを表示できるのでコピーしましょう.



f:id:Santea:20161203224913j:plain

ユーザ名: admin, パスワード: 先程コピーしたパスワードでログインしてください.



f:id:Santea:20161203225050j:plain:w360

ログイン後の画面で、アプリケーションの"新規"を選択してください.



f:id:Santea:20161203230209j:plain

今回は Android のアプリケーションを作成します.

パッケージ名は Android のパッケージ名と一致させる必要があります.


ここまでがプロジェクトの作成になります.



アダプターの設定

Android 用アプリケーションを作成できたら、次にセキュリティチェック用のアダプターを作成します.

アダプターは、Java または JavaScript で作成できます.

github.com

今回はサンプルに準じて、上記リポジトリJava のアダプタをそのまま使用します.

$cd SecurityCheckAdapters
$mvn clean install

リポジトリをクローンした後、上記コマンドを実行してください.

SecurityCheckAdapters/UserLogin/target/UserLogin.adapter が生成されていると思います.



f:id:Santea:20161203230542j:plain:w360

Bluemix のコンソールに戻り、アダプターの新規作成を選択します.



f:id:Santea:20161203231026j:plain

画面右上のアダプターのデプロイを選択します.

先程作成した、SecurityCheckAdapters/UserLogin/target/UserLogin.adapter を選択してください.



f:id:Santea:20161203040546p:plain

問題がなければ上記のような画面が表示されていると思います.



f:id:Santea:20161203231418j:plain:w360

次に、コンソール画面左の"Android(最新)"を選択します.



f:id:Santea:20161203231630j:plain

タブ中央の"セキュリティ"を選択します.



f:id:Santea:20161203231839j:plain

スコープ・エレメントのマッピングの新規を選択してください.



f:id:Santea:20161203232151j:plain:w480

ダイアログが表示されるので、スコープ・エレメントを"push.mobileclient"という名前で、カスタム・セキュリティー検査に先程登録した LoginAdapter を指定して追加します.

これでアダプターの設定は完了です.



プッシュ通知の設定

最後にプッシュ通知の設定を行います.

今回は Android にプッシュ通知を送るので、Firebase Cloud Messaging の設定を行います.

Firebase Console にて新規プロジェクトを作成してください.


f:id:Santea:20161203234002j:plain

作成したプロジェクトのプロジェクト ID と ウェブ API キー を MobileFirst Foundation 側にコピーします.



f:id:Santea:20161203234201j:plain:w360

コンソール画面左の"プッシュ"を選択します.



f:id:Santea:20161204000230j:plain

タブの"プッシュ設定"を選択します.




f:id:Santea:20161203234523j:plain

GCM プッシュ資格情報に、先程の Firebase Cloud Messaging のIDとキーをコピーします.


以上がバックエンドの実装になります.



クライアントサイド: Xamarin.Forms の実装

クライアントサイドである Xamarin.Forms の実装を解説します.

パッケージの導入

Prism のテンプレートを用いてソリューションを作成します.

本エントリーでは PCL を用いますので、Shared Code: Use Portable Class Library を選択してください.

今回は MobileFirstSample という名前でソリューションを作成しました.


各種 NuGet パッケージをインストールします.



MobileFirst Foundation をインストールします.

Xamarin Studio では Components からインストールができますが、Visual Studio ではできないようなので、本エントリーでは Xamarin Studio, Visual Studio 両方で可能な方法を取ります.

IBM MobileFirst SDK / Components / Xamarin

上記ページにて、IBM MobileFirst SDK をダウンロードします(2016/12/02現在の最新版は8.0.0.4です).

f:id:Santea:20161201144914p:plain

ダウンロードしたフォルダにはサンプルが同封されています.
こちらが本エントリーで用いるサンプルです.


PCL・Android それぞれのプロジェクトの References に lib 以下に含まれるライブラリを加えます.

PCLプロジェクト

  • Worklight.Core.dll

Androidプロジェクト


以上でパッケージの導入は完了です.



IPlatformInitializer の実装

プラットフォームごとの IPlatformInitializer を実装します.

public class AndroidInitializer : IPlatformInitializer
{
	public Activity Activity { get; set; }

	public void RegisterTypes(IUnityContainer container)
	{
		container.RegisterInstance<IWorklightClient>(WorklightClient.CreateInstance(Activity));
		container.RegisterInstance<IWorklightPush>(WorklightPush.Instance);
	}
}

Androidプロジェクトの MainActivity に AndroidInitializer を実装します.

IWorklightClient と WorklightPush のインスタンスとして WorklightClient.CreateInstance(Activity); と WorklightPush.Instance をそれぞれ登録します.

Android の場合、WorklightClient#CreateInstance の引数として Activity が必要なので、MainActivity を引数として指定しています.


以上で IPlatformInitializer の実装は完了です.



Model の実装

ここからはPCL プロジェクトの Model の実装になります.

SampleClient.cs

using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Worklight;
using Worklight.Push;

namespace MobileFirstSample
{
	public class SampleClient
	{
		private string _appRealm = "UserLogin";

		public IWorklightClient Client { get; private set; }
		public IWorklightPush Push { get; private set; }
		/// <summary>
		/// Gets a value indicating whether this instance is push supported.
		/// </summary>
		/// <value><c>true</c> if this instance is push supported; otherwise, <c>false</c>.</value>
		public bool IsPushSupported
		{
			get
			{
				try
				{
					return Push.IsPushSupported;
				}
				catch
				{
					return false;
				}
			}
		}

		public SampleClient(IWorklightPush push)
		{
			Push = push;
			Push.Initialize();
		}

		public async Task<WorklightResult> RegisterAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.RegisterDevice(new JObject());
				result.Success = response.Success;
				result.Message = "Registered";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}

		public async Task<WorklightResult> UnregisterAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.UnregisterDevice();
				result.Success = response.Success;
				result.Message = "Unregistered";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}

		public async Task<WorklightResult> SubscribeAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.Subscribe(new string[] { "Xamarin" });
				result.Success = response.Success;
				result.Message = "Subscribed";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}

		public async Task<WorklightResult> UnSubscribeAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.Unsubscribe(new string[] { "Xamarin" });

				result.Success = response.Success;
				result.Message = "Unsubscribed";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}

		public async Task<WorklightResult> GetSubscriptionsAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.GetSubscriptions();

				result.Success = response.Success;
				result.Message = "All Subscriptions";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}

		public async Task<WorklightResult> GetTagsAsync()
		{
			var result = new WorklightResult();

			try
			{
				MFPPushMessageResponse response = await Push.GetTags();

				result.Success = response.Success;
				result.Message = "All tags";
				result.Response = (response.ResponseJSON != null) ? response.ResponseJSON.ToString() : "";
			}
			catch (Exception exception)
			{
				result.Success = false;
				result.Message = exception.Message;
			}

			return result;
		}
	}
}

バックエンドと繋がる部分である SmapleClient クラスです.

フィールドの _appRealm = "UserLogin"; の部分は、バックエンド側のアダプター名と一致させます.

また、コンストラクの IWorklightClient と IWorklightPush は先程 IPlatformInitializer で実装したインスタンスが DI されます.

async/await のメソッド群はそれぞれ以下の機能を持ちます.

  • RegisterAsync: プッシュ通知の登録
  • UnregisterAsync: プッシュ通知登録の解除
  • SubscribeAsync: 特定のプッシュ通知グループの登録
  • UnSubscribeAsync: 特定のプッシュ通知グループの登録解除
  • GetSubscriptionsAsync: 登録済みプッシュ通知グループの取得
  • GetTagsAsync: バックエンド上に作成済みのプッシュ通知グループの取得


以上が Model の実装になります.



View の実装

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="MobileFirstSample.Views.MainPage" Title="MainPage">
	<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
		<ListView ItemsSource="{Binding CommandItemList}" SelectedItem="{Binding SelectedItem.Value, Mode=TwoWay}">
			<ListView.ItemTemplate>
				<DataTemplate>
					<ViewCell>
						<StackLayout Orientation="Horizontal" Spacing="25" Padding="20,0,0,0">
							<Image Source="{Binding Image}" WidthRequest="30" HeightRequest="30"/>
							<Label VerticalOptions="Center" Text="{Binding CommandName}"/>
						</StackLayout>
					</ViewCell>
				</DataTemplate>
			</ListView.ItemTemplate>
		</ListView>
		<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" IsVisible="{Binding IsVisibleIndicator.Value}">
			<BoxView Color="#4D000000" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
			<ActivityIndicator IsRunning="true" IsVisible="true" Color="White" WidthRequest="50" HeightRequest="50" VerticalOptions="Center" HorizontalOptions="Center"/>
		</Grid>
	</Grid>
</ContentPage>

ListView と、バックエンドと通信中を表すためのインジケータを実装しています.


View の実装は以上です.



ViewModel の実装

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using Reactive.Bindings;
using System.Diagnostics;
using System.Threading.Tasks;
using Prism.Services;
using Worklight;
using Worklight.Push;
using Newtonsoft.Json.Linq;

namespace MobileFirstSample.ViewModels
{
	public class MainPageViewModel : BindableBase
	{
		public ReactiveProperty<bool> IsVisibleIndicator { get; set; }
		public ReactiveProperty<CommandItem> SelectedItem { get; set; }
		public List<CommandItem> CommandItemList { get; set; }

		private readonly INavigationService _navigationService;
		private readonly IPageDialogService _pageDidalogService;
		private readonly SampleClient _sampleClient;

		public MainPageViewModel(INavigationService navigationService,
								 IPageDialogService pageDailogService,
								 IWorklightClient worklightClient,
								 IWorklightPush worklightPush)
		{
			_navigationService = navigationService;
			_pageDidalogService = pageDailogService;

			_sampleClient = new SampleClient(worklightClient, worklightPush);
			_sampleClient.Push.NotificationReceived += (sender, e) =>
			{
				PushEventArgs eventArgs = (PushEventArgs) e;
				_pageDidalogService.DisplayAlertAsync("Got notification!",
				                                     eventArgs.Alert,
				                                     "OK");
			};

			IsVisibleIndicator = new ReactiveProperty<bool> { Value = false };
			SelectedItem = new ReactiveProperty<CommandItem>();
			SelectedItem.PropertyChanged += (sender, e) =>
			{
				if (SelectedItem.Value != null)
				{
					SelectedItem.Value.Command();
				}
				SelectedItem.Value = null;
			};

			CommandItemList = new List<CommandItem>
			{
				new CommandItem { CommandName = "Register Device", Image = "subscribe.png", Command = OnRegister },
				new CommandItem { CommandName = "Unregister Device", Image = "unsubscribe.png", Command = OnUnregister },
				new CommandItem { CommandName = "Subscribe to Push", Image = "subscribe.png", Command = OnSubscribe },
				new CommandItem { CommandName = "Unsubscribe from Push", Image = "unsubscribe.png", Command = OnUnSubscribe },
				new CommandItem { CommandName = "Get All Subscriptions", Image = "issubscribed.png", Command = OnGetSubscriptions },
				new CommandItem { CommandName = "Get All Tags", Image = "issubscribed.png", Command = OnGetTags }
			};
		}

		private async void OnRegister()
		{
			ShowWorking();
			var result = await _sampleClient.RegisterAsync();
			HandleResult(result, "Register Device Result");
		}

		private async void OnUnregister()
		{
			ShowWorking();
			var result = await _sampleClient.UnregisterAsync();
			HandleResult(result, "Unregister Device Result");
		}

		private async void OnSubscribe()
		{
			ShowWorking();
			var result = await _sampleClient.SubscribeAsync();
			HandleResult(result, "Subscribe Result");
		}

		private async void OnUnSubscribe()
		{
			ShowWorking();
			var result = await _sampleClient.UnSubscribeAsync();
			HandleResult(result, "Unsubcribe Result");
		}

		private async void OnGetSubscriptions()
		{
			ShowWorking();
			var result = await _sampleClient.GetSubscriptionsAsync();
			HandleResult(result, "Get Subscriptions Result");
		}

		private async void OnGetTags()
		{
			ShowWorking();
			var result = await _sampleClient.GetTagsAsync();
			HandleResult(result, "Get Tags Result");
		}

		private void OnIsPushEnabled()
		{
			_pageDidalogService.DisplayAlertAsync("Push Status",
												  $"Push {(_sampleClient.IsPushSupported ? "is" : "isn't")} supported",
												  "OK");
		}

		private async void ShowWorking(int timeout = 15)
		{
			IsVisibleIndicator.Value = true;

			await Task.Delay(TimeSpan.FromSeconds(timeout));

			if (IsVisibleIndicator.Value == true)
			{
				IsVisibleIndicator.Value = false;
				await _pageDidalogService.DisplayAlertAsync("Timeout", "Call timed out", "OK");
			}
		}

		private void HandleResult(WorklightResult result, string title)
		{
			IsVisibleIndicator.Value = false;

			Debug.WriteLine(title);
			Debug.WriteLine($"Success: {result.Success}");
			Debug.WriteLine($"Message: {result.Message}");
			Debug.WriteLine($"Response: {result.Response}");

			_pageDidalogService.DisplayAlertAsync(title, result.Message, "OK");
		}
	}
}

ViewModel の実装です.

ListView#SelectedItem にバインドすることで、タップされたセルのイベントを呼び出します.

それぞれのセルに指定した Command では、SampleClient の asyncメソッドを呼び出しています.


ViewModel の実装は以上です.



バックエンドのプロパティファイル

最後にバックエンド側のプロパティをクライアント側に記述します.

f:id:Santea:20161203041244p:plain

f:id:Santea:20161203133423p:plain

Androidプロジェクトの assets に "mfpclient.properties" を作成してください.

f:id:Santea:20161203042103p:plain

Host の部分には Bluemix のコンソール URL の上記の部分をコピーしてください.


以上でクライアントサイドの実装は完了です.



実際にプッシュ通知を送ってみる

前準備として、プッシュ通知のタグを登録しておきましょう.

f:id:Santea:20161203235350j:plain:w360

MobileFirst Foundation コンソールの、プッシュを選択します.




f:id:Santea:20161203134011p:plain

タブ中央の"タグ"の中で、新規を選択します.



f:id:Santea:20161203134145p:plain

ダイアログが現れるので、タグ名に"Xamarin"を指定し、作成しましょう.

これで事前準備は完了です.


初めに、すべてのデバイス向けのプッシュ通知を行いましょう.

youtu.be

まず、クライアント側でプッシュ通知への登録を行います.

MobileFirst Foundation のデバイス一覧に Nexus 5X が追加されます.

送信先をすべてに設定しプッシュ通知を送信すると、デバイスがプッシュ通知を受信します.


次に、任意のタグに登録したデバイスに対しプッシュ通知を行います.

youtu.be

まず、クライアント側でタグへの登録を行います.
SampleClient.cs でタグ名に"Xamarin"を指定しているので、"Xamarinタグ"への登録が行われます.

送信先を"タグ別デバイス"に設定し、タグ名に"Xamarin"を指定してプッシュ通知を送信すると、デバイスがタグ別プッシュ通知を受信します.



終わりに

今回は、IBM MobileFirst Foundation と Xamarin.Forms を使って、プッシュ通知を実装しました.

手順が多く、複雑に感じられた方もいらっしゃると思います.

今後はより手順を細分化した、わかりやすい形で連載エントリーにしたいと考えておりますので、どうぞよろしくお願いします.


MobileFirst Foundation には、今回実装したプッシュ通知の他にも、セキュアなリソースアクセスや、JSONデータストレージなど、様々なサービスが提供されてます.

Xamarin で mBaaS というと Azure をまず最初に思い浮かべると思いますが、MobileFirst Foundation があることも頭の片隅に置いていただければ嬉しいです.


以上です.


参考