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
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)
}
}
CodePudding user response:
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) { ... }
}
CodePudding user response:
You are already using coroutines. The problem is that lifecycleScope
is tied to main thread. You want to replace it with GlobalScope
or coroutineScope
(latter is better in terms of complex project, but I assume you are writing pet-project now, so GlobalScope.launch
will be fine)
CodePudding user response:
you should replace
lifecycleScope.launch{
with
lifecycleScope.launch(Dispatchers.IO){