3

Im running this code since addListenerForSingleEvent is a long Running operation:

CoroutineScope(IO).launch {  
    userRef.addListenerForSingleValueEvent(object : ValueEventListener {
        override fun onCancelled(p0: DatabaseError) {

        }

        override fun onDataChange(p0: DataSnapshot) {
            if (p0.exists()) {
                withContext(Main) {
                    toggleLoading()
                    val intent = Intent(this@LogInActivity, MainActivity::class.java)
                    startActivity(intent)
                    finish()
                }
            } else{
                withContext(Main) {
                    var addUsernameIntent = Intent(this@LogInActivity, 
                                             AddUsernameActivity::class.java)
                    startActivityForResult(addUsernameIntent, CHOOSE_USERNAME_REQUEST)
                }
            }
        }
   })
}   

I get an error where i write withContext(Main) that says :

Suspension functions can be called only within coroutine body

But I have a coroutine body right? Before i just had a Thread(runnable {..}) instead of a couroutine, but i read that i shouldn't do intents inside any other Thread than main-thread so i changed to coroutine.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
eek
  • 57
  • 2
  • 7
  • First, actually you are not using the IO coroutine at all! you are just adding a listener to `userRef` inside it which has nothing to do with IO. The implementation behind `userRef` defines how that function is called and tasks are performed. Second, you are calling `withContext` inside `onDataChange` so it not in a courtine body. – momvart Mar 21 '20 at 12:07
  • All i really want to do is to run the run the listener on a separate thread, how can i do this? @MohammadOmidvar – eek Mar 21 '20 at 12:22
  • First, check if it is not currently true (most listeners are invoked in the main thread). Second, you can use traditional ways: `Handler(Looper.getMainLooper()).post()` or `runOnUiThread` Or if you want coroutines (which are based on the mentioned methods), you can use `CoroutineScope(Dispatchers.Main).launch()` as @commander-tvis said. – momvart Mar 21 '20 at 13:06
  • Why wrap that listener in a coroutine at all? It already runs on a background thread – EpicPandaForce Mar 21 '20 at 15:13

2 Answers2

2

The Firebase client already runs any network and disk I/O operations on a separate thread. There almost never is a need to run addListenerForSingleEvent on a separate thread yourself.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
1

Function of an anonymous object may capture the variables of the scope, but it is not in enclosing coroutine body. Replace withContext(Main) with creating a new coroutine: <CoroutineScope>.launch(Main).

Commander Tvis
  • 2,244
  • 2
  • 15
  • 41
  • So i just do CoroutineScope(main).launch { } instead of withContext(Main)? Also when i try to print the current thread when i started first coroutine, it still prints out that it is the mainthread that it's running on – eek Mar 21 '20 at 11:31
  • 1
    It is not very a good solution to create a new scope for each coroutine, you can share it and if you are working with Android UI, consider using `MainScope()`. Coroutines are scheduled to threads automatically, however you may influence it by creating your own dispatchers. – Commander Tvis Mar 21 '20 at 11:39
  • So i'm not sure i understand, can i write MainScope().launch {...} instead of withContext(Main)? I still don't get why it prints Main when i print name of current thread inside the coroutine – eek Mar 21 '20 at 11:43
  • MainScope() can be stored – Commander Tvis Mar 21 '20 at 13:01
  • `MainScope` should be used is a delegate: `class MyActivity : AppCompatActivity(), CoroutineScope by MainScope()`. Check the documentation on [`CoroutineScope`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/) – Marko Topolnik Mar 23 '20 at 17:36