Xamarin.Forms で Microsoft Band に接続してみたい
Visual Studio で Microsoft Band に接続ができたので、今度は iOS だけでなく、Android や Windows Phone でも試せるように(実機ないけど)Xamarin.Forms でサンプルアプリを作ってみたいと思います
Xamarin の中の人が作った Portable Class Library もあるんですが・・・
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; #if __ANDROID__ using Android.App; #endif #if __ANDROID__ || __IOS__ || WINDOWS_PHONE_APP using NativeBandClientManager = Microsoft.Band.BandClientManager; #endif namespace Microsoft.Band.Portable { public class BandClientManager { private static Lazy<BandClientManager> instance; static BandClientManager() { instance = new Lazy<BandClientManager>(() => new BandClientManager()); } public static BandClientManager Instance { get { return instance.Value; } } public async Task<IEnumerable<BandDeviceInfo>> GetPairedBandsAsync() { #if __ANDROID__ return NativeBandClientManager.Instance.GetPairedBands().Select(b => new BandDeviceInfo(b)); #elif __IOS__ return NativeBandClientManager.Instance.AttachedClients.Select(b => new BandDeviceInfo(b)); #elif WINDOWS_PHONE_APP return (await NativeBandClientManager.Instance.GetBandsAsync()).Select(b => new BandDeviceInfo(b)); #else return null; #endif } public async Task<BandClient> ConnectAsync(BandDeviceInfo info) { #if __ANDROID__ var nativeClient = NativeBandClientManager.Instance.Create(Application.Context, info.Native); var result = await nativeClient.ConnectTaskAsync(); return new BandClient(nativeClient); #elif __IOS__ await NativeBandClientManager.Instance.ConnectTaskAsync(info.Native); return new BandClient(info.Native); #elif WINDOWS_PHONE_APP var nativeClient = await NativeBandClientManager.Instance.ConnectAsync(info.Native); return new BandClient(nativeClient); #else return null; #endif } } }
こんな #if プラットフォーム分岐処理ばかりで保守性がちょっと・・・という感じだったので作ってみたのが次のコード
※ 順次改修していく予定なので、この記事の内容が現時点のソースより古い可能性があります
ソリューション構成はこんな感じ
Xamarin.Forms の共通コードからは Band SDK の各種インタフェースごしにアクセスし、各プラットフォーム側にはその実装クラスを作って、共通コードから固有ロジックを呼び出せるようにしています
Android と iOS 用の Microsoft Band SDK は Xamarin Studio で Binding したクラスライブラリをいったんビルドしたのち、できた dll を InfrastructiureAssemblies フォルダにコピーして参照して利用しています(詳しくは 前回の記事 を参照)
Xamarin.Forms のアセンブリプロファイルは、Silverlight はずしています
これは Prism.Mvvm を使っているためで、それ以上の意味はないです!
今回は Xamarin.Forms で Band に接続するために iOS ではこんなコードを書いてます
extern alias ios; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Foundation; using global::Microsoft.Band; using Native = ios::Microsoft.Band; using UIKit; namespace XamarinBandSample.iOS.Band { /// <summary> /// iOS 用 BandClientManager /// </summary> public class NativeBandClientManager : IBandClientManager { /// <summary> /// 登録 Band デバイス情報を取得する /// </summary> /// <returns>登録 Band デバイス情報</returns> public Task<IBandInfo[]> GetBandsAsync() { return Task.FromResult<IBandInfo[]>(( from c in new List<Native.BandClient>(Native.BandClientManager.Instance.AttachedClients) select new NativeBandInfo(c) as IBandInfo).ToArray()); } /// <summary> /// Band デバイスに接続する /// </summary> /// <param name="device">Band デバイス情報</param> /// <returns>Band 接続サービス</returns> public async Task<IBandClient> ConnectAsync(IBandInfo bandInfo) { var client = Native.BandClientManager.Instance.AttachedClients.FirstOrDefault(c => c.Name.Equals(bandInfo.Name)); if (client != null && !client.IsDeviceConnected) { await Native.BandClientManagerExtensions.ConnectTaskAsync(Native.BandClientManager.Instance, client); } return new NativeBandClient(client); } } }
Objective-C 用に提供された SDK を Xamarin 用に Binding しているので、微妙に利用手順を変える必要があります
例えば、Microsoft.Band.iOS.dll と Microsoft.Band.dll には同じ名前空間、クラス名のクラスが含まれており衝突するので、Microsoft.Band.iOS.dll には 'ios' というエイリアスをつけています
通常は 'global' になっているところを変更したうえで、上記のコードのように extern alias ios;
のおまじないを付ければ、Microsoft.Band.iOS.dll 由来のものと Microsoft.Band.dll 由来のものとを区別して指定できるというわけです
Android の場合だと・・・
extern alias android; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using global::Microsoft.Band; using Native = android::Microsoft.Band; namespace XamarinBandSample.Droid.Band { /// <summary> /// Android 用 Microsoft Band デバイス管理クラス /// </summary> public class NativeBandClientManager : IBandClientManager { /// <summary> /// 登録 Band デバイス情報を取得する /// </summary> /// <returns>登録 Band デバイス情報</returns> public Task<IBandInfo[]> GetBandsAsync() { return Task.FromResult<IBandInfo[]>(( from i in Native.BandClientManager.Instance.GetPairedBands() select new NativeBandInfo(i)).ToArray()); } /// <summary> /// Band デバイスに接続する /// </summary> /// <param name="device">Band デバイス情報</param> /// <returns>Band 接続サービス</returns> public async Task<IBandClient> ConnectAsync(IBandInfo bandInfo) { var info = bandInfo as NativeBandInfo; if (info == null) { throw new InvalidOperationException("Parameter 'device' is not BandDevice type."); } var client = Native.BandClientManager.Instance.Create(Application.Context, info.DeviceInfo); if (client != null && !client.IsConnected) { var result = await Native.BandClientExtensions.ConnectTaskAsync(client); } return new NativeBandClient(client); } } }
こんな感じ
当然ですが、Windows Phone はそのまま利用するだけなので、App.xaml.cs を下記のようにするだけで大丈夫でした
/// <summary> /// 既定の Application クラスに対してアプリケーション独自の動作を実装します。 /// </summary> public sealed partial class App : Application { private TransitionCollection transitions; /// <summary> /// 単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの /// 最初の行であり、main() または WinMain() と論理的に等価です。 /// </summary> public App() { this.InitializeComponent(); this.Suspending += this.OnSuspending; // Microsoft Band デバイス管理クラスを DI コンテナに登録 XamarinBandSample.App.Container.RegisterType<IBandClientManager, BandClientManager>(new ContainerControlledLifetimeManager()); XamarinBandSample.App.Container.RegisterInstance<IBandClientManager>(BandClientManager.Instance); } ~ 中略 ~ }
実際に接続する部分は結構バラバラですがインタフェースで抽象化しているので、呼び出し側の Xamarin.Forms のコード(ViewModel)はわりとスッキリ
/// <summary> /// トップ画面の ViewModel /// </summary> public class TopPageViewModel : BindableBase { /// <summary> /// Microsoft Band デバイス管理クラス /// </summary> private IBandManager manager = null; ~ 中略 ~ /// <summary> /// コンストラクタ /// </summary> /// <param name="manager">Microsoft Band デバイス管理クラス</param> [InjectionConstructor] public TopPageViewModel(IBandManager manager) { this.manager = manager; this.ConnectCommand = DelegateCommand.FromAsyncHandler(this.Connect); } /// <summary> /// 接続処理 /// </summary> /// <returns>Task</returns> private async Task Connect() { var devices = await this.manager.GetBandsAsync(); if (!devices.Any()) { await App.Current.MainPage.DisplayAlert("Warning", "No Microsoft Band found.", "OK"); return; } var device = devices.First(); this.ConnectMessage = "Connecting to Band..."; var client = await this.manager.ConnectAsync(device); if (client == null) { await App.Navigation.CurrentPage.DisplayAlert( "Error", string.Format("Failed to connect to Microsoft Band '{0}'.", device.Name), "OK"); return; } // 別の場所から利用できるように DI コンテナに登録 App.Container.RegisterInstance<IBandClient>(client, new ContainerControlledLifetimeManager()); App.Container.RegisterInstance<IBandInfo>(device, new ContainerControlledLifetimeManager()); this.ConnectMessage = string.Empty; this.BandName = device.Name; this.IsConnected = true; await App.Current.MainPage.DisplayAlert( "Connected", string.Format("Microsoft Band '{0}' connected.", device.Name), "OK"); } }
ごちゃごちゃしている部分をインタフェースが隠蔽してくれるので、画面の XAML もスッキリ
<?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:cv="clr-namespace:XamarinBandSample.Converters;assembly=XamarinBandSample" xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=XamarinBandSample" prismmvvm:ViewModelLocator.AutoWireViewModel="true" x:Class="XamarinBandSample.Views.TopPage"> <ContentPage.Resources> <ResourceDictionary> <cv:NegativeConverter x:Key="NegativeConverter"/> </ResourceDictionary> </ContentPage.Resources> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness" iOS="0,20,0,0"/> </ContentPage.Padding> <StackLayout Orientation="Vertical" Padding="10" Spacing="10"> <Label Text="Band Test" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Center"/> <StackLayout Orientation="Vertical" Spacing="5"> <Button Text="Connect Band" FontSize="Medium" HorizontalOptions="Start" VerticalOptions="Center" IsEnabled="{Binding IsConnected, Converter={StaticResource NegativeConverter}}" Command="{Binding ConnectCommand}"/> <Label Text="{Binding ConnectMessage}" Opacity="0.6" FontSize="Small"/> </StackLayout> <StackLayout Orientation="Vertical" Spacing="5"> <Label Text="Device Name:" FontSize="Medium"/> <Label Text="{Binding BandName}" FontSize="Small"/> </StackLayout> </StackLayout> </ContentPage>
実機が iPhone しかないですが実行してみました!
接続成功!!