Kotlinで始めるAndroid Bolts


最近使い始めたFacebook（Parse）製のPromiseライブラリBoltsですが、Kotlinで書いてみたらより使いやすかったので紹介したいと思います。
JavaでもBoltsを使うと非同期処理を同期的に書くことができて便利なのですが、Kotlinではラムダ式が使えるのですっきりします。

Bolts自体の使い方はGitHubのREADMEとテストコードを見ると大体つかめてくるのではないかと思います。
あと、よくみたら C#の [Task](https://msdn.microsoft.com/ja-jp/library/system.threading.tasks.task(v=vs.110).aspx) とインターフェースを合わせているようですね。


* GitHub  
 [Bolts Android](https://github.com/BoltsFramework/Bolts-Android)
* Bolts とは  
[Parse、iOSとAndroid用の低レベルライブラリ集、Boltsを発表](http://www.infoq.com/jp/news/2014/02/parse-announces-bolts)


## BoltsとKotlin
Boltsでは複数の処理の集合を *Task* という形で分割し、つなげて書いていくことができます。
その中でJavaとKotlinで書いていて特に違いが分かりやすいのが、よく使用している次のメソッドです。

* `Task#continueWithTask(...)`
* `Task#continueWith(...)`
* `Task#onSuccessTask(...)`

それぞれのメソッドの使い方は以下のようになります。

その前に以下のサンプルコードでは [livedoor お天気Webサービス](http://weather.livedoor.com/weather_hacks/webservice) をRetrofitから使用し、
地位IDから天気情報（予報日毎の天気の先頭）を返す Task を使用しています。
（たとえば地域ID 400040(久留米）を指定すると `福岡県 久留米の天気 今日 曇り` のような文字列が返る）

``` kotlin
private fun getWeatherAsync(city: Int): Task<String> = Task.callInBackground {
    weatherService.weather(city).toString()
}
```

Boltsには Task を作成するためのヘルパーメソッドが定義されており、上記の場合は同期的な天気取得処理を非同期で行うためのTaskを作成できます。

もう一つの作成方法として、コールバックをとるような非同期処理も `Task.TaskCompletionSource` を使用する事もできるのですが、Kotlinの場合型推論が上手く行かずできていません…。
→ 追記: Java作成したでユーティリティクラスを使用することで解決。

* [Git: Tasks.java](https://gist.github.com/droibit/58d4b17b682948775ad5)

使い方はこんなかんじです。

``` kotlin
    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>)` で取得した天気情報をトーストで表示するようにしています。

``` kotlin
private fun showResult(weathers: List<String>) {
    runOnUiThread {
        Toast.makeText(this, weathers.join("\n"), Toast.LENGTH_LONG).show()
    }
}
```

### Task#continueWith + Task


まずJavaで複数の Task を連結させることができる `Task#ontinueWithTask(...)` を使用してみます。

``` java
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で同じコードを書くと次のようになります。

``` 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()` から得られます。

``` kotlin
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(...)' でまとめてエラー処理ができます。

``` kotlin
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においてあります。

* [BoltsSample](https://github.com/droibit/boltssample.kotlin)