Why my ProgressBar is showing up after inline coroutine function, instead of before?

129 Views Asked by At

When I'm sending HTTP requests to server from my tablet by using Fuel, I want to display a ProgressBar at screen before the concerned function begins to run. But for an unknow reason, it's happened once the function has completed its job.

The ProgressBar is defined programmatically by this function :

protected fun initProgressBar(constraintLayoutID: Int): ProgressBar {
        val originalConstraintLayout = findViewById<ConstraintLayout>(constraintLayoutID)
        progressBar = ProgressBar(this).apply {
            id = getInteger(R.integer.INT_PROGRESS_BAR_ID)
            elevation = 999F
            isIndeterminate = true
            layoutParams = ViewGroup.LayoutParams(300, 300)
            visibility = View.GONE

            originalConstraintLayout.addView(this)
        }

        ConstraintSet().run {
            clone(originalConstraintLayout)

            for(position in listOf(ConstraintSet.TOP, ConstraintSet.START, ConstraintSet.END, ConstraintSet.BOTTOM))
                connect(progressBar!!.id, position, constraintLayoutID, position,0)
            applyTo(originalConstraintLayout)
        }
        return progressBar!!
    }

I've tried to show/hide the ProgressBar lonely and it's working perfectly. Here is the inline function where it should be showed before sending the main request :

inline fun <reified T : DefaultResponse> sendRequest(
        routeName: String = "",
        routeArgs: String = ""
    ): T = runBlocking { // Help : https://fuel.gitbook.io/documentation/support/fuel-coroutines
        owner.runOnUiThread {
            owner.showProgressBar() // Should display at this point, not after
        }

        val adaptedResponse = ResponseFactory.getInstance(owner).getResponseObjectFor(T::class.java.name, routeName)
        val (request, response, result) = Fuel.get(
            "%s%s?%s%s".format(
                networkManager.getURLConnection(),
                (adaptedResponse as DefaultResponse).getRouteName(),
                routeArgs,
                getIndustrialTrypticArgs()
            )
        ).awaitObjectResponseResult(adaptedResponse)

        val exception =
            FailedRequestException(owner.getString(R.string.STR_EXCEPTION_RM_FAILED_REQUEST)).apply {
                setHTTPMessage(response.responseMessage)
                setStatusCode(response.statusCode)
                setUsedRequest(request.url)
            }

        return@runBlocking result.fold<T>(
            { responseObject ->
                if (response.statusCode != 200)
                    throw exception
                return@fold responseObject as T
            },
            { error ->
                throw exception.setOriginalError(error.exception)
            }
        )
    }

Also, I've added a call to runOnUiThread for the display, with an encapsulation in a different coroutine thread with launch at some point, but it didn't work (still appearing after the execution's end of the inline function).

It's because it's an inline function ? Or there's a concern about concurrent threads ?

UPDATE N°1

Following Tenfour04's advices, I changed the sendRequest into a suspend function :

suspend inline fun <reified T : DefaultResponse> sendRequest(
        routeName: String = "",
        routeArgs: String = "",
        isBlocking: Boolean = true
    ): T? {
        val adaptedResponse = ResponseFactory.getInstance(owner).getResponseObjectFor(T::class.java.name, routeName) // 1
        val fuelRequest = Fuel.get( // 2
            "%s%s?%s%s".format(
                networkManager.getURLConnection(),
                (adaptedResponse as DefaultResponse).getRouteName(),
                routeArgs,
                getIndustrialTrypticArgs()
            )
        )
        var fuelResponse: T? = null // 3

        CoroutineScope(Dispatchers.Main).launch {
            owner.showProgressBar() // 4
        }
        try {
            fuelResponse =
                if(isBlocking) {
                    val answer = fuelRequest.awaitObjectResponseResult(adaptedResponse) // 5

                    treatsResponse(answer) as T // 6
                }
                else {
                    fuelRequest.responseObject(adaptedResponse) { httpRequest, httpResponse, httpResult ->
                        try {
                            treatsResponse(httpRequest, httpResponse, httpResult) as T
                        }
                        catch (e: FailedRequestException) {
                            println(e.getOriginalError())
                            println(e.getUsedRequest())
                        }
                        finally {
                            owner.hideProgressBar()
                        }
                    }
                    null
                }
        }
        catch (e: FailedRequestException) {
            println(e.getOriginalError())
            println(e.getUsedRequest())
        }
        finally {
            owner.hideProgressBar()
        }
        return fuelResponse
    }

The call of sendRequest is encapsulated into a CoroutineScope(Dispatchers.Main).launch. It's an improvement since the ProgressBar begins to appear within the function and not after its call, but I still want that it display before the fuelRequest.awaitObjectResponseResult(adaptedResponse) line (// 5).

awaitObjectResponseResult is also a suspend function.

UPDATE N°2

While a line-per-line debug session, I remarked the following on sendRequest : when owner.showProgressBar() is encapsulated into CoroutineScope(Dispatchers.Main).launch (// 4), it's clearly executed after // 5th instruction.

So, the actual order of execution since the beginning is always 1 -> 2 -> 3 -> 5 -> 4 -> 6.

When I add Dispatchers.Main as the second argument of awaitObjectResponseResult, the order become 1 -> 2 -> 3 -> 5 -> 6 -> 4. It looks like this function always get the priority for some reason...

2

There are 2 best solutions below

4
Tenfour04 On

runOnUiThread is asynchronous. Since you want the behavior to be synchronous, meaning in the order the code appears, you should use withContext(Dispatchers.Main) instead.

withContext(Dispatchers.Main) {
    owner.showProgressBar()
}

withContext is a suspend function that can change which thread the inner code is run on synchronously.

0
Omiganox On

Finally, it appears that Fuel has an open issue with native Java's async IO API. Whatever I've tried so far just can't work on the main thread since Fuel's method awaitObjectResponseResult blocks it completely until it have been called. Apparently, this issue will be fixed in the next major update.