LoginSignup
25
16

More than 3 years have passed since last update.

KotlinのCoroutines 1.3でシンプルにコールバック待ちをする

Last updated at Posted at 2018-12-12

はじめに

こんにちは。今年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 さんです。お楽しみに!

25
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
16