10

I have a coroutine I'd like to fire up at android startup during the splash page. I'd like to wait for the data to come back before I start the next activity. What is the best way to do this? Currently our android is using experimental coroutines 0.26.0...can't change this just yet.

UPDATED: We are now using the latest coroutines and no longer experimental

onResume() {
    loadData()
}

fun loadData() = GlobalScope.launch {
    val job = GlobalScope.async {
        startLibraryCall()
    }
    // TODO await on success
    job.await()
    startActivity(startnewIntent)
}

fun startLibraryCall() {
    val thirdPartyLib() = ThirdPartyLibrary()
    thirdPartyLib.setOnDataListener() { 
        ///psuedocode for success/ fail listeners
        onSuccess -> ///TODO return data
        onFail -> /// TODO return other data
    }
}
JPM
  • 8,360
  • 13
  • 73
  • 124

2 Answers2

18

The first point is that I would change your loadData function into a suspending function instead of using launch. It's better to have the option to define at call site how you want to proceed with the execution. For example when implementing a test you may want to call your coroutine inside a runBlocking. You should also implement structured concurrency properly instead of relying on GlobalScope.

On the other side of the problem I would implement an extension function on the ThirdPartyLibrary that turns its async calls into a suspending function. This way you will ensure that the calling coroutine actually waits for the Library call to have some value in it.

Since we made loadData a suspending function we can now ensure that it will only start the new activity when the ThirdPartyLibrary call finishes.

import kotlinx.coroutines.*
import kotlin.coroutines.*

class InitialActivity : AppCompatActivity(), CoroutineScope {
    private lateinit var masterJob: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + masterJob

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        masterJob = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        masterJob.cancel()
    }

    override fun onResume() {
        this.launch {
            val data = ThirdPartyLibrary().suspendLoadData()
            // TODO: act on data!
            startActivity(startNewIntent)
        }
    }
}

suspend fun ThirdPartyLibrary.suspendLoadData(): Data = suspendCoroutine { cont ->
    setOnDataListener(
            onSuccess = { cont.resume(it) },
            onFail = { cont.resumeWithException(it) }
    )
    startLoadingData()
}
Marko Topolnik
  • 179,046
  • 25
  • 276
  • 399
Eric Martori
  • 2,111
  • 12
  • 23
  • Okay that looks great. but the library is a third party I have no access to the call. All I can do is listen to a listener – JPM Nov 05 '18 at 21:59
  • This answer requires no changes to the library, it's built on exactly the ingredients you specified. What it does is show you how to use the listeners to construct a `suspend fun`. Once you have that, you don't need any `async-await` logic. – Marko Topolnik Nov 06 '18 at 08:58
  • The coroutines stuff is still so new and has been changing constantly that I am still not sure of best practices. But thanks this loots pretty good I'll test it out and see if I can get it working. – JPM Nov 06 '18 at 16:08
  • What is `suspendCoroutine`? – IgorGanapolsky Mar 02 '20 at 19:10
0

You can use LiveData

liveData.value = job.await()

And then add in onCreate() for example

liveData.observe(currentActivity, observer)

In observer just wait until value not null and then start your new activity

Observer { result ->
            result?.let { 
                startActivity(newActivityIntent)
            } 
}