2
votes

I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread. When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.

In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.

When I debug my application, it goes into my Presenter but crashes before going into my Repository.

Presenter

    override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
        scope.launch(Dispatchers.IO) {
            try {
                userResponseRepository.insertUserResponse(data)

                withContext(Dispatchers.Main) {
                    redirectToClientMainPage(activity)
                }
            } catch (error: Exception) {
                parentJob.cancel()
                Log.e("ERROR PRESENTER", "${error.message}")
            }
        }
    }

Repository

    override suspend fun insertUserResponse(userResponse: UserResponse) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                val existingUser: UserResponse? =
                    userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)

                existingUser?.let {
                    userResponseDAO.updateUser(userResponse)
                } ?: userResponseDAO.insertUser(userResponse)
            } catch (error: Exception) {
                Log.e("ERROR REPOSITORY", "${error.message}")
            }
        }

    }

I have no error shown in my logcat.

EDIT:

Scope initialization

    private var parentJob: Job = Job()

    override val coroutineContext: CoroutineContext
        get() = uiContext + parentJob

    private val scope = CoroutineScope(coroutineContext)

val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)

Stack trace

enter image description here

2
Change GlobalScope.launch with withContext in Repository. And how the scope is initialized?Yeldar.N
I tried your solution but it still crashes. I've updated my topic with the initialization of scope.Lena

2 Answers

2
votes

I finally found the answer!

Thanks to @sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.

So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.

I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application. That's it.

1
votes

Without stacktrace it's hard to help you.

Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:

As a general pattern, start coroutines in the ViewModel (Read: Presenter)

I don't see the need to launch one more coroutine in your case in Repository.

As for switching coroutine's context for Room:

Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.

I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.

Both Room and Retrofit make suspending functions main-safe. It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.

So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.

One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?