はじめに
こんにちは。今年ACCESSに入社したk.nagauchiです。
GT-Rのアイコンについてよく聞かれますが、昔から好きで一時期乗っていた(動画)ので、
今も自分のIdentityとして使ってます。
現在KotlinとSwiftを使うスマホアプリ開発業務(と時間が許せばFlutter)に力を入れています。
ACCESS Advent Calendar 2018 の13日目の記事を担当いたします。
今回の内容
KotlinのCoroutines(コルーチン)で、シンプルにコールバック待ちをしたい時の実装テンプレです。
Javaからの移民向けです。
シンプルと言いつつJavaライクな記法で書いているのはそんな理由もあります。
実装例
通信処理MyConnectionService.connect()が時間のかかる非同期コールバック持ちメソッドになっているが、
それを使う自分のsyncConnect()はreturnで結果を返す同期メソッドにしたい場合。
メソッド
suspend fun syncConnect() : Boolean {
return suspendCoroutine { continuation ->
val callback = object: MyConnectionCallback {
override fun onConnected() {
continuation.resume(true) // ここでsyncConnect()をreturnする
}
override fun onError(error: Error) {
Log.e(LOG_TAG, this.javaClass.simpleName + ":onError:" + error.message);
continuation.resume(false) // ここでsyncConnect()をreturnする
}
}
MyConnectionService.connect(callback)
return@suspendCoroutine // resumeが呼ばれるまで待つ
}
}
呼び出し部分
class MyActivity : AppCompatActivity(), CoroutineScope {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(LOG_TAG, "syncConnect() start.")
launch(Dispatchers.Default) {
syncConnect()
}.join()
Log.d(LOG_TAG, "syncConnect() finish.")
}
}
もしくは
suspend fun doSyncConnect() : Boolean {
Log.d(LOG_TAG, "syncConnect() start.")
val result = GlobalScope.async(Dispatchers.Default) {
syncConnect()
}.await()
Log.d(LOG_TAG, "syncConnect() finish.")
return result
}
実装例2
callbackではなくstartActivityを挟むケースでも、Activityにcallbackを渡しておいて、
Activity側でcallback.xxxxx()をコールすれば同じことができると思います。
メソッド
private suspend fun grantPermission() : Boolean {
return suspendCoroutine { continuation ->
val callback = object: MyPermissionCallback {
override fun onGranted() {
continuation.resume(true) // ここでgrantPermission()をreturnする
}
override fun onDenied(reason: String) {
Log.e(LOG_TAG, this.javaClass.simpleName + ":onDenied:" + reason);
continuation.resume(false) // ここでgrantPermission()をreturnする
}
}
MyPermissionActivity.callback = callback // Activity側でcallbackはcompanion objectにしておく
activity.startActivity(Intent(activity, MyPermissionActicity::class.java))
return@suspendCoroutine // resumeが呼ばれるまで待つ
}
}
呼び出し部分
class MyActivity : AppCompatActivity(), CoroutineScope {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(LOG_TAG, "grantPermission() start.")
launch(Dispatchers.Default) {
grantPermission()
}.join()
Log.d(LOG_TAG, "grantPermission() finish.")
}
}
もしくは
suspend fun doGrantPermission() : Boolean {
Log.d(LOG_TAG, "grantPermission() start.")
val result = GlobalScope.async(Dispatchers.Default) {
grantPermission()
}.await()
Log.d(LOG_TAG, "grantPermission() finish.")
return result
}
説明
suspendCoroutineを付けてreturnすると、メソッドの処理が中断されます。
中断中は実行スレッドをブロックしません。ここがCoroutinesの一番の強みです。
resumeを呼ぶと再開し、その引数をsyncConnectやgrantPermissionの結果として呼び元に返します。
ただし、CoroutineScopeを介さず呼ぶ場合、syncConnectやgrantPermissionの呼び元にもsuspendを付けなければならないのが難点なのと、
コルーチン入門者がとりあえずうまくいったという程度の記事なので、問題がある場合は教えてください。
それでは、明日は @aRyoKuroda さんです。お楽しみに!