 
Xamarin逆引きTips
Xamarin.Formsからプラットフォーム固有の機能を利用するには?(DependencyService利用)
UIを共通化するフレームワーク「Xamarin.Forms」で、「DependencyService」機能を使用してiOS/Androidの各プラットフォーム固有の機能を実装する方法を解説する。
Xamarin.Formsは、UIを共通にするフレームワークなので、プラットフォーム固有の機能は、それぞれ(iOS/Android)実装する必要がある。しかし、「DependencyService」と呼ばれる機能を使用することで、Xamarin.Formsから共通な呼び出しを行うことができる。今回は、このDependencyServiceの使い方を解説する。
1. Xamarin.Formsプロジェクトを作成する
プラットフォーム固有の機能を使用する例として、iOSとAndroidで、GPSの緯度経度を表示するアプリを作る。
Xamarin Studioのメニューバーの[ファイル]-[新規]-[ソリューション]から、[C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、ソリューション名を「GpsSample」として[OK]ボタンを押す。
 GpsSampleプロジェクト内のApp.csファイルを以下のように修正して、ボタンとラベルを配置する。
| ……省略…… public class App {   public static Page GetMainPage()   {        var buttonStartGps = new Button     {       Text = "Start GPS"     };     var labelLatLon = new Label     {     };     return new ContentPage     {       Content = new StackLayout       {         Orientation = StackOrientation.Vertical,         VerticalOptions = LayoutOptions.CenterAndExpand,         HorizontalOptions = LayoutOptions.CenterAndExpand,         Children =         {           buttonStartGps,           labelLatLon         }       }     };   } } | 
2. GPSを利用するための、共通なインターフェースを定義する
 GpsSampleプロジェクト内にIGeoLocator.csファイルを作成し、GPSを利用するための共通なIGeoLocatorインターフェースとその関連クラスを以下のように定義する。
| ……省略…… // イベントのパラメーター public class LocationEventArgs : EventArgs {   public double Latitude { get; set; }   public double Longitude { get; set; } } // 位置を受信した際のイベントハンドラー public delegate void LocationEventHandler(object sender, LocationEventArgs args); // GPS を利用するための、共通なインターフェース public interface IGeolocator {   void StartGps();   event LocationEventHandler LocationReceived; } | 
 Xamarin.Forms内ではこのIGeoLocatorインターフェースを介してプラットフォーム固有の機能を利用する。
3. iOSで、IGeolocatorインターフェースを実装する
 GpsSample.iOSプロジェクト内に、GeoLocator_iOS.csファイルを作成し、以下のように実装する。
| 1 2 3 | using System; using MonoTouch.CoreLocation; using Xamarin.Forms; using GpsSample.iOS; [assembly: Dependency (typeof (GeoLocator_iOS))] namespace GpsSample.iOS {   public class GeoLocator_iOS : IGeolocator   {     public event LocationEventHandler LocationReceived;     private readonly CLLocationManager _locationMan = new CLLocationManager();     public void StartGps()     {       _locationMan.LocationsUpdated += (sender, e) =>       {         if (this.LocationReceived != null) {           var l = e.Locations[e.Locations.Length - 1];           this.LocationReceived(this, new LocationEventArgs           {             Latitude = l.Coordinate.Latitude,             Longitude = l.Coordinate.Longitude           });         }       };       _locationMan.StartUpdatingLocation();     }   } } | 
 まず、1が重要な要素だ。この定義により、このGeoLocator_iOSクラスがIGeoLocatorインターフェースのiOS用の実装クラスだと宣言している。
 2の箇所で、実際にGeoLocator_iOSクラスは、IGeoLocatorインターフェースを実装している。
 iOSではCLLocationManagerクラスを利用してGPSの位置を取得する。取得した位置をLocationReceivedイベントで通知するのが3だ。
【コラム】GeoLocator_iOSのクラス名
 このTipsではIGeoLocatorインターフェースの実装クラス名をGeoLocator_iOSとしているが、特に命名規約はない。IGeoLocatorを実装しており、Dependency(typeof ())属性で指定されていればどのようなクラス名でも動作する。
4. Xamarin.Formsで、IGeoLocatorオブジェクトを利用する
 GpsSampleプロジェクトに戻り、App.csファイルを以下のように追記する。
| 1 | public class App {   public static Page GetMainPage()   {        var buttonStartGps = new Button     {       Text = "Start GPS"     };     var labelLatLon = new Label     {     };     // 追加ここから ---     buttonStartGps.Clicked += (sender, e) =>      {       var geoLocator = DependencyService.Get<IGeolocator>();       geoLocator.LocationReceived += (_, args) =>        {         labelLatLon.Text = String.Format("{0:0.00}/{1:0.00}",            args.Latitude, args.Longitude);       };       geoLocator.StartGps();     };     // --- 追加ここまで     return new ContentPage     {       Content = new StackLayout       {         Orientation = StackOrientation.Vertical,         VerticalOptions = LayoutOptions.CenterAndExpand,         HorizontalOptions = LayoutOptions.CenterAndExpand,         Children =         {           buttonStartGps,           labelLatLon         }       }     };   } } | 
 いよいよDependencyServiceの登場となる。ボタンが押された時に、DependencyServiceから、IGeoLocatorのインスタンスを取得する(1)。DependencyServiceはこのとき、実行中のプラットフォームに応じてIGeoLocatorの実装クラス(iOSならばGeoLocator_iOS)をインスタンス化し返却する。あとは、この得られたインスタンスを使い、受信した緯度経度値をラベルに表示させる。
5. iOSで実行してみる
ここまでのプログラムをiOSで実行すると、次の画面のようになる。

6. Androidで、IGeolocatorインターフェースを実装する
 iOSでの実行が確認できたら、次はAndroid側も実装する。GpsSample.Androidプロジェクトに、GeoLocator_Android.csファイルを作成し、以下のように実装する。
| 1 2 4 3 | using System; using Xamarin.Forms; using GpsSample.Android; using Android.Content; using Android.Locations; using Android.OS; [assembly: Dependency (typeof (GeoLocator_Android))] namespace GpsSample.Android {   public class GeoLocator_Android : IGeolocator   {     public event LocationEventHandler LocationReceived;     public void StartGps()     {       var context = Forms.Context;       var locationMan = context.GetSystemService(Context.LocationService)          as LocationManager;       locationMan.RequestLocationUpdates(LocationManager.GpsProvider, 0, 0,          new MyLocationListener(l =>         {           if (this.LocationReceived != null) {             this.LocationReceived(this, new LocationEventArgs              {               Latitude = l.Latitude,               Longitude = l.Longitude             });           }         }));     }     class MyLocationListener : Java.Lang.Object, ILocationListener     {       private readonly Action<Location> _onLocationChanged;       public MyLocationListener(Action<Location> onLocationChanged)       {         _onLocationChanged = onLocationChanged;       }       public void OnLocationChanged(Location location)       {         _onLocationChanged(location);       }       public void OnProviderDisabled(string provider) { }       public void OnProviderEnabled(string provider) { }       public void OnStatusChanged(string provider,          Availability status, Bundle extras)  { }     }   } } | 
 1、2はiOSと同じ要領だ。AndroidではStartGpsメソッドの実装は、LocationManagerクラスを利用して行う。
 位置の受信はILocationListenerインターフェースのオブジェクトで行うが、C#言語では、Java言語のように匿名クラスが使えないので、別のクラスMyLocationListenerを定義し(3)、そのコンストラクターの引数にActionデリゲートを設定できるようにして、そのデリゲート経由でOnLocationChangedメソッドへの位置受信結果をLocationReceivedイベントハンドラー側へ通知して、緯度経度値をラベルに表示させる。
 なお、このActionデリゲートのメソッドは、使用するときにラムダ式で記述できる(4)。この手法はXamarin.Androidではよく利用するので覚えておくとよいだろう。
7. Androidで、GPSを使うための権限を設定する
 Androidでは、GPSを使用するのに権限を設定する必要がある。次の画面を参考に、AndroidManifest.xmlを開いて、[AccessFineLocation]にチェックを入れる。
8. Androidで実行する
ここまでのプログラムをAndroidで実行すると、次の画面のようになる。

まとめ
 DependencyServiceを利用すると、プラットフォーム固有の処理をXamarin.Formsから、共通のインターフェースを介して利用できる。これはMvvmCrossなどのフレームワークでも提供される機能であるが、Xamarinの標準機能として搭載されているのはありがたい。
 DependencyServiceは、実行中のプラットフォームに対応する実装クラスが見つからないときにはnullを返す。例えばGeoLocator_Androidクラスを実装していないときにAndroidで実行すると、geoLocator変数(=DependencyServiceを使って取得したIGeolocatorオブジェクト)はnullとなり動作しないので考慮が必要だ。
Xamarin Developersの「Accessing Native Features via the DependencyService(英語)」にも情報があるので、参考にしてほしい。
※以下では、本稿の前後を合わせて5回分(第6回~第10回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
 
6. Xamarin.iOSでStoryboardとXamarin.Formsを併用するには?
Storyboardで作成したiOSアプリの一部の画面に、Xamarin.Formsを利用する方法を解説する。
 
7. Xamarin.AndroidでActivityとXamarin.Formsを併用するには?
iOSの場合と同じように、Androidアプリの一部の画面に、Xamarin.Formsを利用する方法を解説する。また、iOSとの挙動の違いやフラグメントとの併用についても言及する。
 
8. 【現在、表示中】≫ Xamarin.Formsからプラットフォーム固有の機能を利用するには?(DependencyService利用)
UIを共通化するフレームワーク「Xamarin.Forms」で、「DependencyService」機能を使用してiOS/Androidの各プラットフォーム固有の機能を実装する方法を解説する。
 
9. Xamarin.Formsでダイアログボックス(とBusyインジケーター)を表示するには?
ダイアログ(=iOSのUIAlertView/AndroidのAlertDialog)や、処理実行中を示すBusyインジケーターを、Xamarin.Formsで表示する方法を解説する。これらは共通のAPIを使って実装できる。
 
10. Xamarin.Formsでカスタムダイアログを表示するには?(MessagingCenter利用)
Xamarin.Formsで共通のAPIが提供されているダイアログ(=iOSのUIAlertView/AndroidのAlertDialog)以外のプラットフォーム個別のダイアログを表示する方法を解説。その呼び出しにはMessagingCenterが利用できる。





 
  
 
