Search code examples
androidkotlinandroid-asynctask

How should I implement my AsyncTask class?


I am making a weather app, where I use AsyncTask for getting response from API and after that setting up the UI. Here how looks like my code now after simplyfing:

class MainActivity : AppCompatActivity() {


    /*
    SOME INSIGNIFICANT CODE HERE
    */

    private fun setUI(currentWeather: Root){
        tv_city.text = "${currentWeather.name}, ${currentWeather.sys.country}"
        /*
        ...
        */
    }

    inner class WeatherByNameTask: AsyncTask<String, Unit, Unit>(){

        override fun doInBackground(vararg p0: String?) {
            val city: String? = p0[0]
            val call = weatherApi.getCurrentWeatherByCityName(city!!, API_KEY, "metric")
            call.enqueue(object: Callback<Root>{
                override fun onResponse(call: Call<Root>, response: Response<Root>) {
                    if (!response.isSuccessful){
                        Toast.makeText(this@MainActivity, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
                    } else {
                        val currentWeather = response.body()
                        setUI(currentWeather!!)
                    }
                }

                override fun onFailure(call: Call<Root>, t: Throwable) {
                    Toast.makeText(this@MainActivity, "Code: ${t.message}", Toast.LENGTH_LONG).show()
                }
            })
        }
    }

    inner class WeatherByCoordTask: AsyncTask<Location, Unit, Unit>(){

        override fun doInBackground(vararg p0: Location?) {
            val lat: String = p0[0]?.latitude.toString()
            val lon: String = p0[0]?.longitude.toString()
            val call = weatherApi.getCurrentWeatherByCoordinates(lat, lon, API_KEY, "metric")
            call.enqueue(object: Callback<Root>{
                @SuppressLint("SetTextI18n")
                override fun onResponse(call: Call<Root>, response: Response<Root>) {
                    if (!response.isSuccessful){
                        Toast.makeText(this@MainActivity, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
                    } else {
                        val currentWeather = response.body()
                        setUI(currentWeather!!)
                    }
                }

                override fun onFailure(call: Call<Root>, t: Throwable) {
                    Toast.makeText(this@MainActivity, "Code: ${t.message}", Toast.LENGTH_LONG).show()
                }
            })
        }
    }
}

It works, but I'm getting a warning:

This AsyncTask class should be static or leaks might occur

I want to make it in correct way. I tried to implement it outside the MainActivity class, passing the Context as a parameter, but what's about setUI function? I guess making it public is bad idea.


Solution

  • This AsyncTask class should be static or leaks might occur

    In the MainActivity, there are 2 AsyncTask class with inner modifier, this means the inner class will keep a strong reference to the outer class. The warning tells you while the AsyncTask is doing its job in the background, if the user leaves the current activity (press Back key or calling finish() method), then the activity instance will be leaked because the AsyncTask still keeps a strong reference to it.

    Solution

    Using WeakReference to let the AsyncTask keeps a weak reference to the MainActivity.

    class WeatherByNameTask (activity: MainActivity): AsyncTask<String, Unit, Unit>(){
        private val activityRef = WeakReference<MainActivity>(activity)
    
        override fun doInBackground(vararg p0: String?) {
            val city: String? = p0[0]
            val call = weatherApi.getCurrentWeatherByCityName(city!!, API_KEY, "metric")
            call.enqueue(object: Callback<Root>{
                override fun onResponse(call: Call<Root>, response: Response<Root>) {
                    if (!response.isSuccessful){
                        activityRef.get()?.let {
                            Toast.makeText(it, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
                        }
                    } else {
                        val currentWeather = response.body()
                        activityRef.get()?.setUI(currentWeather!!)
                    }
                }
    
                override fun onFailure(call: Call<Root>, t: Throwable) {
                    activityRef.get().let {
                        Toast.makeText(it, "Code: ${t.message}", Toast.LENGTH_LONG).show()
                    }
                }
            })
        }
    }
    
    class WeatherByCoordTask (activity: MainActivity): AsyncTask<Location, Unit, Unit>(){
        private val activityRef = WeakReference<MainActivity>(activity)
    
        override fun doInBackground(vararg p0: Location?) {
            val lat: String = p0[0]?.latitude.toString()
            val lon: String = p0[0]?.longitude.toString()
            val call = weatherApi.getCurrentWeatherByCoordinates(lat, lon, API_KEY, "metric")
            call.enqueue(object: Callback<Root>{
                @SuppressLint("SetTextI18n")
                override fun onResponse(call: Call<Root>, response: Response<Root>) {
                    if (!response.isSuccessful){
                        activityRef.get()?.let {
                            Toast.makeText(it, "Code: ${response.code()}", Toast.LENGTH_LONG).show()
                        }
                    } else {
                        val currentWeather = response.body()
                        activityRef.get()?.setUI(currentWeather!!)
                    }
                }
    
                override fun onFailure(call: Call<Root>, t: Throwable) {
                    activityRef.get().let {
                        Toast.makeText(it, "Code: ${t.message}", Toast.LENGTH_LONG).show()
                    }
                }
            })
        }
    }
    

    Use from the activity

    val weatherByNameTask = WeatherByNameTask(this)
    val weatherByCoordTask = WeatherByCoordTask(this)