Retrofitをなどを使って非同期の通信を行うときに通信中はくるくるダイアログなどのProgressダイアログを出したいケースはよくあると思います。
ベタなやりかた
もっともベタなケースとして、通信を行うまえにダイアログを出し、onComplete()とonError()でダイアログを閉じるというやり方があります。
void onClick() {
// ProgressDialogを生成して表示
final ProgressDialog progressDialog = createProgressDialog();
progressDialog.show();
loadData().subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
// complete時にProgressDialogを閉じる
progressDialog.dismiss();
}
@Override
public void onError(Throwable e) {
// errorがあった時もProgressDialogを閉じる
progressDialog.dismiss();
}
@Override
public void onNext(String s) {
}
});
}
この場合、onCompleted()
とonError()
の両方でダイアログを閉じるコードを書く必要があります。また、もしloadData()
が完了する前にunsubscribe()
された場合はダイアログが閉じられずに残ってしまいます。従って途中でunsubscribe()
される可能性がある場合は以下のようにunsubscribe()するときにダイアログも明示的に閉じてあげる必要があります。
private Subscription loadDataSubscription;
/// progressDialogの参照はプロパティとして保持する必要がある
private ProgressDialog progressDialog;
void onClick() {
progressDialog = createProgressDialog();
progressDialog.show();
loadDataSubscription = loadData().subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
progressDialog.dismiss();
progressDialog = null;
}
@Override
public void onError(Throwable e) {
progressDialog.dismiss();
progressDialog = null;
}
@Override
public void onNext(String s) {
}
});
}
private void cancel(){
if(loadDataSubscription != null && !loadDataSubscription.isUnsubscribed()){
loadDataSubscription.unsubscribe();
progressDialog.dismiss();
progressDialog = null;
}
}
これはめんどい、とてもめんどい
doOnSubscribe()
/ doOnUnsubscribe()
を使う
Observable
にはdoOnSubscribe()
/doOnUnsubscribe()
というメソッドがあり、それぞれsubscribe時、unsubscribe時の処理を記述できます。
void onClick() {
final ProgressDialog progressDialog = createProgressDialog();
loadData()
.doOnSubscribe(new Action0() {
@Override
public void call() {
progressDialog.show();
}
})
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
progressDialog.dismiss();
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
}
});
}
これだと、ダイアログを閉じる処理をdoOnSubscribe()
に集約できるのでそこら中に記述する必要がありませんし、処理中にunsubscribeされても安心です。
ただ、このやりかただとダイアログを表示する箇所でいちいちこの記述をする必要がありますし、ダイアログへの参照を外部に持たせる必要があります。
もうちょっとスマートに書きたい!
using()
を使う
こういったケースで使われるユーティリティとしてusing()
という関数があります。
using()
とは
Observableと同じライフタイムであるリソースを生成/破棄することができるユーティリティ関数です。Observableが生成されるタイミングであるリソースを生成し、Observableが終了するタイミングでそのリソースを破棄します。
関数の定義
public static final <T,Resource> Observable<T> using(
Func0<Resource> resourceFactory,
Func1<? super Resource,? extends Observable<? extends T>> observableFactory,
Action1<? super Resource> disposeAction)
引数
resourceFactory
Observableと連動するリソースを生成するファクトリ関数を
obesrvableFactory
Observableを生成するファクトリ関数
desposeAction
リソースを破棄する関数
using()
をつかってProgressダイアログを表示する
このusing()
関数をつかってダイアログの表示を実装すると以下のようになります。
void onClick() {
Observable.using(
new Func0<ProgressDialog>() {
@Override
public ProgressDialog call() {
ProgressDialog progressDialog = createProgressDialog();
progressDialog.show();
return progressDialog;
}
},
new Func1<ProgressDialog, Observable<? extends String>>() {
@Override
public Observable<? extends String> call(ProgressDialog progressDialog) {
// 実際に行いたいデータ取得
return loadData();
}
},
new Action1<ProgressDialog>() {
@Override
public void call(ProgressDialog progressDialog) {
progressDialog.dismiss();
}
})
.subscribe();
}
これで生成したProgressDialogはObservableで自動的に管理されることになり、どこかに参照を保持する必要がなくなりました。
でもまあ、あんまりdoOnSubscribe()``doOnUnsubscribe()
を使った場合と代わり映えしないです。どうせならこの処理を再利用できるように変えてしまいましょう。
Progressダイアログの表示処理を再利用できるように
using()
からはVoidかなんかを返してその後flatMap()
実際の通信処理を行うことによって、Progressダイアログの表示部分を再利用できるように切り離すことができます。
void onClick() {
usingProgressDialog()
.flatMap(new Func1<Void, Observable<String>>() {
@Override
public Observable<String> call(Void aVoid) {
// flatMapでloadData()を実行
return loadData();
}
})
.subscribe();
}
// ProgressDialog表示するObservableを生成する
private Observable<Void> usingProgressDialog() {
return Observable.using(
new Func0<ProgressDialog>() {
@Override
public ProgressDialog call() {
ProgressDialog progressDialog = createProgressDialog();
progressDialog.show();
return progressDialog;
}
},
new Func1<ProgressDialog, Observable<? extends Void>>() {
@Override
public Observable<? extends Void> call(ProgressDialog progressDialog) {
return Observable.just(null);
}
},
new Action1<ProgressDialog>() {
@Override
public void call(ProgressDialog progressDialog) {
progressDialog.dismiss();
}
});
}
これで、非常にシンプルに、Observableの処理にプログレスダイアログの表示を付加することができました。
こちらにサンプルコードを置いています。