Is there a way to run Okhttps request synchronously?

Viewed 229

So i am kind of experimenting with an app and i am making an okhttp request. I found out that this request task is made on its own thread, which leads to an asynchronous behaviour.

Down below you see my function which gets the data from the API, and the onCreateView function, where i call fetchData().

@Throws(IOException::class)
    fun fetchData() {
        val client = OkHttpClient()
        val request: Request = Request.Builder()
            .url("https://www.themealdb.com/api/json/v1/1/search.php?f=a")
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                call.cancel()
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                val responseString = U.formatJson(response.body?.string())
                val meals = Gson().fromJson(responseString, Meals::class.java)
                
                Log.d(TAG, "First")
                
            }
        })
    }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        fetchData()

        Log.d(TAG, "second")

        return inflater.inflate(R.layout.fragment_home, container, false)
    }

So i put these logs here to see, which Log gets called first, and since fetchData() is asynchronous, it is obvious that Log.d(TAG, "second"), which is in onCreateView(), gets called first. So if i try to use the data that is getting requested down below, i get null because the request has not yet finished. And that is my problem. I need to use the requested data after calling the function.

Is there any solution where i can do fetchData() somehow synchronous, but without blocking the main Thread?

3 Answers

You can't run a network request on the main thread. It has to be asynchronous. Just kind of guessing at what you're doing, I'd add a callback to your fetch function and use it like this.

fun fetchData(callback: (Meals?) -> Unit) {
    val client = OkHttpClient()
    val request: Request = Request.Builder()
        .url("https://www.themealdb.com/api/json/v1/1/search.php?f=a")
        .build()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            call.cancel()
            callback(null)
        }

        @Throws(IOException::class)
        override fun onResponse(call: Call, response: Response) {
            val responseString = U.formatJson(response.body?.string())
            val meals = Gson().fromJson(responseString, Meals::class.java)
            callback(meals)
        }
    })
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    fetchData { meals ->
        if (meals == null) 
            showErrorMessage()
        else
            updateSomeListAdapterWithMeals(meals)
    }

    return inflater.inflate(R.layout.fragment_home, container, false)
}

Since the callback will fire later, it doesn't matter that you're calling fetchData before inflating your view.

Edit:

Thanks for your answers, i now use the fetched data in the callback function since there is no other useful way. Thanks for your answers

try the following code for synchromized request:

@Throws(IOException::class)
    fun fetchData() {
        val client = OkHttpClient()
        val request: Request = Request.Builder()
            .url("https://www.themealdb.com/api/json/v1/1/search.php?f=a")
            .build()

       Response response = client.newCall(request).execute();

       if(response.isSuccessful()){
            val responseString = U.formatJson(response.body?.string())
            val meals = Gson().fromJson(responseString, Meals::class.java)
                
            Log.d(TAG, "First")             
        }else {
            Log.d(TAG, "Request failed")
        }
    }
Related