Kotlin & Anko coroutines: return outside async

1.7k views Asked by At

In our project we need to obtain some data (which will be stored) before doing operations. In case the data was obtained more than 15 minutes ago we have to refresh it.

We're using Kotlin + Anko coroutines in order to do that. The idea (assuming the data was obtained at some point before) is that:

The method gets called and checks when we got the data. If it was less than 15 min ago, returns it. If not, obtain it asynchronously (it's a network operation), store it and return it.

Since we can't do anything before getting the refreshed data, the refresh must be synchronous (althrough the network operation itself is async).

We've this code:

fun Context.retrieveInfo(api: Api?): User? {

  try {

    // For sake of simplification the real conditional check is removed
    if (time > 15) {

        val context = this
        val asyncUser = async(UI) {
            val obtainData: Deferred<Data?> = bg {
                api?.obtainData(sphelper.getDefaultUser(context))
            }

            val obtainedData = storeAndRetrieve(obtainData.await(), context)
            return@async obtainedData
        }

 // ???????

    } else {
        val input = ObjectInputStream(this.openFileInput("data.dat"))
        return input.readObject() as Data
    }
  } catch (e: Exception) {
    return null
  }
}

How can we make the function wait for the result outside the async(UI) block? That return is needed, but we have no clue about what should we put there. We've tried with the getCompleted() method of the Deferred object ( return asyncUser.getCompleted()) but it ends up crashing because it returns null.

Thank you!

1

There are 1 answers

2
fal On BEST ANSWER

I see a couple of ways to do this. One is to use the kotlinx.coroutines.experimental method runBlocking:

val user = runBlocking(CommonPool) {
    val asyncUser = async(UI) {
        val obtainData: Deferred<String> = bg {
            ...
        }

        val obtainedData = storeAndRetrieve(obtainData.await(), context)
        return@async obtainedData
    }
    return@runBlocking asyncUser.await()
}

Depending on use case, you may even be able to simplify this to:

val asyncUser = runBlocking {
    async(CommonPool) {
        val obtainedData = storeAndRetrieve(api?.obtainData(sphelper.getDefaultUser(context)), context)
        return@async obtainedData
    }.await()
}

Another way is to use a basic Java CountDownLatch:

var asyncUser: String? = null
val c = CountDownLatch(1)
async(UI) {
    val obtainData: Deferred<String> = bg {
        ...
    }

    val obtainedData = obtainData.await()
    asyncUser = obtainedData
    c.countDown()
}
c.await()