Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Async requests in Kotlin Android

I often get an error android.os.NetworkOnMainThreadException, when I try get info from some api. I know that this problem is related to the main android thread, but I don’t understand how to solve it – coroutines, async okhttp, or both?
P.S I have a bad eng, sorry.

My code:

MainAtivity.kt

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

class MainActivity: AppCompatActivity(), Alert {
    private lateinit var binding: ActivityMainBinding
    lateinit var api: ApiWeather
    var okHttpClient: OkHttpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        api = ApiWeather(okHttpClient)

        binding.buttonGetWeather.setOnClickListener {
            val cityInput = binding.textInputCity.text.toString()
            if (cityInput.isEmpty()) {
                errorAlert(this, "...").show()
            } else {
                val city = "${cityInput.lowercase()}"
                val limit = "1"
                val appId = "key"
                val urlGeocoding = "http://api.openweathermap.org/geo/1.0/direct?" +
                        "q=$city&limit=$limit&appid=$appId"

                var status = false
                val coordinates: MutableMap<String, Double> = mutableMapOf()
                val job1: Job = lifecycleScope.launch {
                        val geo = api.getGeo(urlGeocoding)
                        if (geo != null) {
                            coordinates["lat"] = geo.lat
                            coordinates["lon"] = geo.lon
                            status = true
                        } else {
                            status = false
                        }
                }
                val job2: Job = lifecycleScope.launch {
                    job1.join()
                    when(status) {
                        false -> {
                            binding.textviewTempValue.text = ""
                            errorAlert(this@MainActivity, "...").show()
                        }
                        true -> {
                            val urlWeather = "https://api.openweathermap.org/data/2.5/weather?" +
                                    "lat=${coordinates["lat"]}&lon=${coordinates["lon"]}&units=metric&appid=${appId}"
                            val weather = api.getTemp(urlWeather)
                            binding.textviewTempValue.text = weather.main.temp.toString()
                        }
                    }
                }
            }
        }
    }
}

Api.kt

class ApiWeather(cl: OkHttpClient) {
    private val client: OkHttpClient

    init {
        client = cl
    }

    suspend fun getGeo(url: String): GeocodingModel? {
        val request: Request = Request.Builder()
            .url(url)
            .build()
        val responseStr = client.newCall(request).await().body?.string().toString()
        val json = Json {
            ignoreUnknownKeys = true
        }
        return try {
            json.decodeFromString<List<GeocodingModel>>(responseStr)[0]
        } catch (e: Exception) {
            return null
        }
    }

    suspend fun getTemp(url: String): DetailWeatherModel {
        val request: Request = Request.Builder()
            .url(url)
            .build()
        val responseStr = client.newCall(request).await().body?.string().toString()
        val json = Json {
            ignoreUnknownKeys = true
        }
        return json.decodeFromString<DetailWeatherModel>(responseStr)
    }
}

>Solution :

The problem is that api.getGeo(urlGeocoding) runs in the current thread. lifecycleScope.launch {} by default has Dispatchers.Main context, so calling api function will run on the Main Thread. To make it run in background thread you need to switch context by using withContext(Dispatchers.IO). It will look like the following:

lifecycleScope.launch {
      val geo = withContext(Dispatchers.IO) { api.getGeo(urlGeocoding) }
      if (geo != null) {
           coordinates["lat"] = geo.lat
           coordinates["lon"] = geo.lon
           status = true
      } else {
           status = false
      }

      when(status) { ... }           
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading