RxM4A により Reactive Extensions が使えるようになった ので、以前に Android+reactive4Java でやったコレ を Xamarin.Android でやってみます。
//LocationManager.Extenstion.cs
namespace Amay077.Android.Locations
{
static class LocationManagerExtenstion
{
// 渡した Action<Location> を OnLocationChanged で実行されるようにしただけ
class LocationListener : Java.Lang.Object, ILocationListener
{
private readonly Action<Location> _locationChangedHandler;
public LocationListener(Action<Location> locationChangedHandler)
{
_locationChangedHandler = locationChangedHandler;
}
#region ILocationListener implementation
public void OnLocationChanged(Location location)
{
_locationChangedHandler(location);
}
public void OnProviderDisabled(string provider) { }
public void OnProviderEnabled(string provider) { }
public void OnStatusChanged(string provider, Availability status, Bundle extras) { }
#endregion
}
public static IConnectableObservable<Location> RequestLocationAsObservable(
this LocationManager locMan,
string provider)
{
return Observable.Create<Location>(o =>
{
try {
var isStop = false; // RemoveUpdates してもすぐ止まるか分からんので一応フラグ持っとく
var listener = new LocationListener(l =>
{
if (isStop) return;
o.OnNext(l);
});
// 位置取得開始
locMan.RequestLocationUpdates(provider, 0, 0, listener);
return () => // Dispose() した時に停止
{
if (isStop) return;
isStop = true;
locMan.RemoveUpdates(listener);
o.OnCompleted();
};
} catch (Exception ex) {
o.OnError(ex);
return () => { /* empty */ };
}
}).Publish(); // Hot な Observable に
}
}
}
LocationManager
の拡張メソッドにしたいので、クラス名を慣例に習って LocationManager.Extenstion.cs
に、メソッド RequestLocationAsObservable
の第一引数に this
を付けてます。
LocationListener
Inner クラスは、C# では匿名クラスが使えないので、コンストラクタで指定した Action
が OnLocationChanged
で呼ばれるようにしただけです。
RequestLocationAsObservable
メソッドがメイン。やってることは reactive4Java と同じです。Hot な Observable にしたので、最後に .Publish()
してるので、返値が IConnectableObservable
になってます。
さて使う方。
//Howtouse.cs
using Amay077.Android.Locations; // 拡張メソッドを使えるように
<省略>
// LocationManager を得る
var locationMan = (LocationManager)context.GetSystemService(Context.LocationService);
// RequestLocationAsObservable があたかも LocationManager のメンバのよ(ry
var observable = locationMan.RequestLocationAsObservable(LocationManager.GpsProvider);
observable.Take(3) // 3回取得
.Timeout(new TimeSpan(10000)) // 10秒待って取得できなかったらタイムアウト
.Subscribe(
l => { Android.Util.Log.Debug(TAG,
String.Format("received: {0}/{1}",
l.Latitude, l.Longitude)); }, // 位置が取得される度に呼ばれる
e => Android.Util.Log.Debug(TAG, "error:" + e.Message), // エラー(タイムアウト含む)の時呼ばれる
() => Android.Util.Log.Debug(TAG, "finished.")); // 全部終わったら呼ばれる
observable.Connect(); // 接続開始
Reactive Extensions と C# の拡張メソッドなどのおかげで、Java よりもずいぶんとすっきり書けました。