13
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

はじめに

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

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
13
Help us understand the problem. What are the problem?