これは Android Advent Calendar 2014 8日目 の記事です。
例えば◯秒間操作がなかったらパスキーロック画面を表示する、とかそういうの。普通に作るとタイマーを使って面倒な感じになっちゃいますが、RxJava と RxAndroid を使うととても簡単にできます。
例えば、画面に EditBox
と Button
があって、「文字列の入力」と「ボタンが押されたか」を監視、◯秒間操作がなかったら××する、という処理をしたい時、RxJava+RxAndroid では以下のように書けます。
//MyActivity.java
public class MyActivity extends Activity {
private static final String TAG = "MyActivity";
private Subscription _subscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
final EditText editName = (EditText)findViewById(R.id.editName);
final View buttonOk = findViewById(R.id.buttonOk);
// OnTextChangeEvent や OnClickEvent をただの Void シグナルに変換
final Func1<Object, Void> signalizer = new Func1<Object, Void>() {
@Override
public Void call(Object onClickEvent) {
return null;
}
};
// 文字入力イベントのストリームと…
_subscription = ViewObservable.text(editName).map(signalizer)
// ボタン押されたのストリームを合体
.mergeWith(ViewObservable.clicks(buttonOk).map(signalizer))
// 3秒間なんもなかったらエラーにする
.timeout(3, TimeUnit.SECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void dummy) {
// 何かアクションがあったらこっち
Log.d(TAG, "文字が入力されたか、ボタンが押されたよ");
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
// 3秒間何もなかったらこっち
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MyActivity.this,
"3秒間何も操作がありませんでした", Toast.LENGTH_SHORT)
.show();
}
});
}
});
}
@Override
protected void onDestroy() {
// イベント系は無限ストリームだから開放してやらないとリークするはず
_subscription.unsubscribe();
super.onDestroy();
}
}
ViewObservable.text(editName)
がテキストが入力される度にシグナルを発するストリーム、ViewObservable.clicks(buttonOk)
がボタンが押される度にシグナルを発するストリームです。これらを mergeWith
で合体させます。
あとは timeout
につなげるだけ。3秒以内にシグナルがあったら onNext→new Action<Void>()
のとこ、3秒以上何も操作がなかったらタイムアウトして onError→new Action<Throwable>()
のとこに飛びます。あとはご自由に、ここでは Toast
を表示してるだけです。
注意点は、イベントから生成されたストリームは無限、つまり onComplete
は来ない。こういう Observable
は自力での購読解除(unsubscribe
)が必須です。
これを動かすとこんな感じになります
さて Xamarin です。Xamarin では本家の Reactive Extensions が使用できます。RxAndroid と同じことをやると下のように書けます、スマート。
//MainActivity.cs
[Activity(Label = "RxJavaSample", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
var editName = FindViewById<EditText>(Resource.Id.editName);
var buttonOk = FindViewById<Button>(Resource.Id.buttonOk);
Observable.FromEventPattern<TextChangedEventArgs>(editName, "TextChanged").Select(_=>true)
.Merge(Observable.FromEventPattern(buttonOk, "Click").Select(_=>true))
.Timeout(TimeSpan.FromSeconds(3))
.Subscribe(_ => {} ,
e => RunOnUiThread(() => Toast.MakeText(this,
"3秒間何も操作がありませんでした", ToastLength.Short).Show()));
}
}
Reactive Extensions を使うと、UIイベントをストリームに変換でき、合成・加工・フィルタなどして様々な応用ができます。しかしこれは Rx のパワーのまだ半分。もう半分は、WebAPI とか DB とか、Model 由来のレスポンスもストリーム化できること。どちらも Observable にしたら、あとはそれをつなぐだけでアプリ完成! さあみんなで Rx にロックインされましょう!