最近使い始めたFacebook(Parse)製のPromiseライブラリBoltsですが、Kotlinで書いてみたらより使いやすかったので紹介したいと思います。
JavaでもBoltsを使うと非同期処理を同期的に書くことができて便利なのですが、Kotlinではラムダ式が使えるのですっきりします。
Bolts自体の使い方はGitHubのREADMEとテストコードを見ると大体つかめてくるのではないかと思います。
あと、よくみたら C#の Task とインターフェースを合わせているようですね。
- GitHub
Bolts Android - Bolts とは
Parse、iOSとAndroid用の低レベルライブラリ集、Boltsを発表
BoltsとKotlin
Boltsでは複数の処理の集合を Task という形で分割し、つなげて書いていくことができます。
その中でJavaとKotlinで書いていて特に違いが分かりやすいのが、よく使用している次のメソッドです。
Task#continueWithTask(...)
Task#continueWith(...)
Task#onSuccessTask(...)
それぞれのメソッドの使い方は以下のようになります。
その前に以下のサンプルコードでは livedoor お天気Webサービス をRetrofitから使用し、
地位IDから天気情報(予報日毎の天気の先頭)を返す Task を使用しています。
(たとえば地域ID 400040(久留米)を指定すると 福岡県 久留米の天気 今日 曇り
のような文字列が返る)
private fun getWeatherAsync(city: Int): Task<String> = Task.callInBackground {
weatherService.weather(city).toString()
}
Boltsには Task を作成するためのヘルパーメソッドが定義されており、上記の場合は同期的な天気取得処理を非同期で行うためのTaskを作成できます。
もう一つの作成方法として、コールバックをとるような非同期処理も Task.TaskCompletionSource
を使用する事もできるのですが、Kotlinの場合型推論が上手く行かずできていません…。
→ 追記: Java作成したでユーティリティクラスを使用することで解決。
使い方はこんなかんじです。
private fun getWeatherAsyncWithCallback(city: Int) = Tasks.createTask<String> {
mWeatherService.weatherWithCallback(city, object: Callback<Weather> {
override fun success(weather: Weather, response: Response) = it.setResult(weather.toString())
override fun failure(error: RetrofitError) = it.setError(error)
})
}
そして showResult(List<String>)
で取得した天気情報をトーストで表示するようにしています。
private fun showResult(weathers: List<String>) {
runOnUiThread {
Toast.makeText(this, weathers.join("\n"), Toast.LENGTH_LONG).show()
}
}
Task#continueWith + Task
まずJavaで複数の Task を連結させることができる Task#ontinueWithTask(...)
を使用してみます。
public void onContinueWithTask() {
final List<String> weathers = new ArrayList<>();
getWeatherAsync(400040).continueWithTask(new Continuation<String, Task<String>>() {
@Override public Task<String> then(Task<String> task) throws Exception {
weathers.add(task.getResult());
return getWeatherAsync(130010);
}
}).continueWith(new Continuation<String, Void>() {
@Override public Void then(Task<String> task) throws Exception {
weathers.add(task.getResult());
showResult(weathers);
return null;
}
});
}
Task#continueWithTask(...)
メソッドには次に実行するTaskを返すContinuation
インターフェースを実装し、渡しています。
これによりTaskを複数つなげていくことができます。また、その中で前処理の結果も受け取ることができ、 Task#getResult()
で取り出すことができます。
末尾の Task#continueWith(...)
は一連の処理を終えるときに使用します。メソッド名にTaskがついていません。
Android Studioを使用していると無名クラスが折りたためるのでKotlinのコードに近い外観になりますが
実際のコードはジェネリックを多用しているので結構見づらいと思います。そして、Kotlinで同じコードを書くと次のようになります。
fun onContinueWithTask(v: View) {
val weathers = ArrayList<String>()
getWeatherAsync(400040).continueWithTask {
weathers.add(it.getResult())
getWeatherAsync(130010)
} continueWith {
weathers.add(it.getResult())
showResult(weathers)
}
}
非常にすっきりかけました!
天気取得の結果のトーストはこんな感じです。
福岡県 久留米の天気 今日 曇り
東京都 東京の天気 今日 晴れのち曇り
Task#onSuccessTask
BoltsはTask内で例外が発生しても簡単に対応処理をかけます。
Task#continueWithTask(...)
で前処理の結果を受け取れると書きましたが、エラー情報も取得でき、 Task#getError()
から得られます。
fun onContinueWithTask(v: View) {
val weathers = ArrayList<String>()
getWeatherAsync(400040).continueWithTask {
weathers.add(it.getResult())
throw IllegalStateException("Error!!!")
getWeatherAsync(130010)
} continueWithTask {
if (it.getError() != null) {
// TODO: エラー処理
}
weathers.add(it.getResult())
getWeatherAsync(130010)
} continueWith {
if (it.getError() != null) {
// TODO: エラー処理
return
}
weathers.add(it.getResult())
showResult(weathers)
}
}
Task内で途中でエラーが出た場合、その都度エラー処理をするのではなく以後のTaskをスキップしてほしい時に Task#onSuccessTask(...)
が便利です。
こちらを使うと前のTask内で例外が自動的に伝播されるので最後に 'Task#continueWith(...)' でまとめてエラー処理ができます。
fun onSuccessTaskWithError(v: View) {
var weathers = ArrayList<String>()
getWeatherAsync(400040) continueWithTask {
weathers.add(it.getResult())
throw IllegalStateException()
getWeatherAsync(400040)
} onSuccessTask {
weathers.add(it.getResult()) // スキップされる
getWeatherAsync(400040)
} onSuccessTask {
weathers.add(it.getResult()) // スキップされる
getWeatherAsync(400040)
} continueWith {
if (it.getError() != null) {
Log.d(BuildConfig.BUILD_TYPE, "Task Error: ", it.getError()) // getError() -> IllegalStateException
} else {
weathers.add(it.getResult())
}
showResult(weathers)
}
}
ここでは使っていませんが Task#continueWith
のサクセス版とでもいえる Task#onSuccess
もあります。
他にも 全てのTaskが完了されたら次が呼ばれるTaskを作るための Task#whenAllResult(...)
など
便利なものが用意されていますので用途に合わせて使えるとよさそうですね。
また、Task をキャンセルすることもできるようにもなっています。
以上のコードはサンプルプロジェクトとしてGitHubにおいてあります。