4

I've encountered an odd issue here(new to Kotlin and coroutines). I am modifying a class variable in the function getMovies. In the launch block I get a JSON which I then run through GSON and get a mutable list of movie info entries. You can see 2 longToasts from Anko which displays a Toast, but the first one displays 20 and the other one 0. Why is that? Android Studio doesn't throw any errors and I am referencing the same moviesList variable. I tried numerous blogs and instructions on the Internet, but couldn't find anything useful. Any help would be super appreciated!

class MainActivity : Activity() {

private var moviesList: MutableList<Movie> = mutableListOf()

fun getMovies() {
        launch(UI){
            val result = async(CommonPool){
                getResponseJSON()
            }.await()
            moviesList = Gson().fromJson(result, MovieDBResponse::class.java).results
            longToast(moviesList.size.toString())
        }
        longToast(moviesList.size.toString())
    }
}
4
  • 1
    Are you sure that really the first toast shows 20? Maybe try to write to log instead to ensure the order. Commented Dec 4, 2018 at 23:19
  • 2
    Are you still using experimental coroutines API? It's time to upgrade to Kotlin 1.3. Also, don't use async-await, it's an anti-pattern. Use withContext(Dispatchers.IO) instead. Commented Dec 5, 2018 at 8:45
  • @MichaelButscher Yes, the first one definitely shows 20. I tried logs, but for some reason they don't show. Commented Dec 6, 2018 at 7:28
  • @MarkoTopolnik Hmmm, will try that. Any useful links about that? Thanks! Commented Dec 6, 2018 at 7:28

1 Answer 1

3

The code you have is written against the experimental coroutines API, so let me first upgrade it to Kotlin 1.3 and fix some obvious mistakes:

class MainActivity : Activity(), CoroutineScope {
    override val coroutineContext = Dispatchers.Main + SupervisorJob()

    private val moviesList: MutableList<Movie> = mutableListOf()

    fun getMovies() {
        launch {
            val result = withContext(Dispatchers.IO) { getResponseJSON() }
            moviesList += Gson().fromJson(result, MovieDBResponse::class.java).results
            longToast("Size after fetching: ${moviesList.size}")
        }
        longToast("Immediate size: ${moviesList.size}")
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutineContext[Job]!!.cancel()
    }
}

Now, looking at your code, this is what I expect it to do:

  1. launch a concurrent coroutine that will eventually fetch some JSON data from the network
  2. Request to display the "Immediate size" toast
  3. JSON results arrive
  4. Populate the movie list
  5. Request to display the "Size after fetching" toast

The exact details of when you see a toast on the screen can vary and depends on Android's policies and the speed of your fetch.

Sign up to request clarification or add additional context in comments.

4 Comments

The code looks great! Will try it out when I get home and let you know. And thanks for the step by step explanation, much appreciated. But I thought that the coroutines allow to run a long-running task on a separate(like background) thread and then pauses UI while it's running, so it would work in a kind of sequential order? So launch block first and then longToast?
Pausing the UI thread is precisely the thing that coroutines let you avoid in an elegant and non-disruptive manner. launch, in particular, is like Thread.start() and the code inside it runs concurrently to the code that called it. BTW "concurrently" does not imply "on another thread". Many coroutines may run concurrently on the single UI thread, through cooperative multithreading.
I tried your code and it works! I see that I can get data properly and that class variables are definitely accessible from the launch block. Though I see that I definitely need to update the UI from the launch block after the operation is done(after withContext block). So it seems logical because we cannot really know when the data will be retrieved. Thanks for the help! Will mark as accepted answer.
That's what withContext is for, transferring the blocking call to another thread so it doesn't freeze the GUI. While your coroutine is inside withContext (IO), other code runs on the GUI thread. That's cooperative multithreading.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.