androidandroid-studiokotlinandroid-fusedlocation

Location request takes a lot time to run after refresh


I am working currently on weather app from a tutorial. The app works fine until the moment when I try to refresh everything and request once again a location and call API using the new location. Whenever I do that it either takes a lot of time to get a new location or it never happens. I am a little bit lost here and would love to find out how to make it work.

Step by step:

  1. In override fun onOptionsItemSelected I call requestLocationData() -> this part goes fine and the called function runs.
  2. In requestLocationData() I should request location using fused location provider client and move to override fun onLocationResult but this either takes a lot of time or never happens.

MainActivity:


import android.Manifest
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.location.Location
import android.location.LocationManager
import android.net.Uri
import android.os.Bundle
import android.os.Looper
import android.provider.Settings
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.location.*
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import eu.apps.weatherapp.databinding.ActivityMainBinding
import eu.apps.weatherapp.models.WeatherResponse
import eu.apps.weatherapp.network.WeatherService
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
import java.text.SimpleDateFormat
import java.util.*

// OpenWeather Link : https://openweathermap.org/api
/**
 * The useful link or some more explanation for this app you can checkout this link :
 * https://medium.com/@sasude9/basic-android-weather-app-6a7c0855caf4
 */
class MainActivity : AppCompatActivity() {

    // A fused location client variable which is further used to get the user's current location
    private lateinit var mFusedLocationClient: FusedLocationProviderClient
    // Binding variable
    private lateinit var binding: ActivityMainBinding
    // A dialog variable
    private var mProgressDialog: Dialog? = null
    //longitude and latitude
    private var mLongitude: Double = 0.0
    private var mLatitude: Double = 0.0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        // Initialize the Fused location variable
        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)


        if (!isLocationEnabled()) {
            Toast.makeText(
                this,
                "Your location provider is turned off. Please turn it on.",
                Toast.LENGTH_SHORT
            ).show()

            // This will redirect you to settings from where you need to turn on the location provider.
            val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
            startActivity(intent)
        } else {
            // Asking the location permission on runtime.)
            Dexter.withActivity(this)
                .withPermissions(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                )
                .withListener(object : MultiplePermissionsListener {
                    override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
                        if (report!!.areAllPermissionsGranted()) {
                            // Calling the location request function
                            requestLocationData()
                        }

                        if (report.isAnyPermissionPermanentlyDenied) {
                            Toast.makeText(
                                this@MainActivity,
                                "You have denied location permission. Please allow it is mandatory.",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPermissionRationaleShouldBeShown(
                        permissions: MutableList<PermissionRequest>?,
                        token: PermissionToken?
                    ) {
                        showRationalDialogForPermissions()
                    }
                }).onSameThread()
                .check()
        }
    }

    /**
     * A function which is used to verify that the location or GPS is enabled or not.
     */
    private fun isLocationEnabled(): Boolean {
        // This provides access to the system location services.
        val locationManager: LocationManager =
            getSystemService(Context.LOCATION_SERVICE) as LocationManager
        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(
            LocationManager.NETWORK_PROVIDER
        )
    }

    /**
     * A function used to show the alert dialog when the permissions are denied and need to allow it from settings app info.
     */
    private fun showRationalDialogForPermissions() {
        AlertDialog.Builder(this)
            .setMessage("It Looks like you have turned off permissions required for this feature. It can be enabled under Application Settings")
            .setPositiveButton(
                "GO TO SETTINGS"
            ) { _, _ ->
                try {
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    val uri = Uri.fromParts("package", packageName, null)
                    intent.data = uri
                    startActivity(intent)
                } catch (e: ActivityNotFoundException) {
                    e.printStackTrace()
                }
            }
            .setNegativeButton("Cancel") { dialog,
                                           _ ->
                dialog.dismiss()
            }.show()
    }

    /**
     * A function to request the current location. Using the fused location provider client.
     */
    @SuppressLint("MissingPermission")
    private fun requestLocationData() {
        Log.e("requestLocationData:", "Start")

        val mLocationRequest = LocationRequest()
        mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY

        mFusedLocationClient.requestLocationUpdates(
            mLocationRequest, mLocationCallback,
            Looper.myLooper()
        )
    }

    /**
     * A location callback object of fused location provider client where we will get the current location details.
     */
    private val mLocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            Log.e("LocationResult:", "Start")

            val mLastLocation: Location = locationResult.lastLocation
            mLatitude = mLastLocation.latitude
            Log.i("Current Latitude", "$mLatitude")

            mLongitude = mLastLocation.longitude
            Log.i("Current Longitude", "$mLongitude")

            //Getting weather details for lat and long
            getLocationWeatherDetails()
        }
    }

    /**
     * Function responsible for getting weather using API call and current location
     */
    private fun getLocationWeatherDetails(){
        Log.e("LocationWeatherDetails:", "Start")

        if(Constants.isNetworkAvailable(this)){
            //Building retrofit object
            val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl(Constants.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            //Creating service
            val service: WeatherService = retrofit
                .create<WeatherService>(WeatherService::class.java)
            //Prepare call
            val listCall: Call<WeatherResponse> = service.getWeather(
                mLatitude, mLongitude, Constants.APP_ID, Constants.METRIC_UNIT)

            //Show custom progress dialog
            showCustomProgressDialog()

            //Performing API call
            listCall.enqueue(object : Callback<WeatherResponse>{
                override fun onResponse(
                    call: Call<WeatherResponse>,
                    response: Response<WeatherResponse>
                ) {
                    if (response.isSuccessful){
                        //Hide custom progress dialog
                        hideCustomProgressDialog()
                        val weatherList: WeatherResponse = response.body()!!
                        Log.i("Response Result", "$weatherList")
                        //Setting up UI
                        setupUI(weatherList)
                    }else{
                        //Hide custom progress dialog
                        hideCustomProgressDialog()

                        //Logging return code.
                        val rc = response.code()
                        when(rc){
                            401 -> {
                                Log.e("Error 401", "API Key Issue")
                            }
                            400 -> {
                                Log.e("Error 400", "Bad Connection")
                            }
                            404 -> {
                                Log.e("Error 404", "Not Found")
                            }
                            429 -> {
                                Log.e("Error 429", "Over 60 API calls per minute")
                            }else -> {
                                Log.e("Error", "Generic Error: $rc")
                            }
                        }
                    }
                }

                override fun onFailure(call: Call<WeatherResponse>, t: Throwable) {
                    Log.e("Error", t.message.toString())
                }

            })

        }else {
            Toast.makeText(this@MainActivity,
                "No internet connection",
                Toast.LENGTH_SHORT).show()
        }
    }

    private fun showCustomProgressDialog() {
        mProgressDialog = Dialog(this)

        /* Set the screen content from a layout.
           The layout will be inflated, adding a top-level views to the screen*/
        mProgressDialog!!.setContentView(R.layout.dialog_custom_progress)

        // Show dialog on the screen
        mProgressDialog!!.show()
    }

    private fun hideCustomProgressDialog() {
        if (mProgressDialog != null){
            mProgressDialog!!.dismiss()
        }
    }

    /**
     * Function responsible for populating UI with data from API
     */
    private fun setupUI(weatherList: WeatherResponse) {
        //Since Weather is a list we go through all of its elements
        for(i in weatherList.weather.indices){

            binding.tvMain.text = weatherList.weather[i].main
            binding.tvMainDescription.text = weatherList.weather[i].description

            //deciding on the icon to use based on ID.
            when (weatherList.weather[i].id){
                in 200..232 -> binding.ivMain.setImageResource(R.drawable.rain)
                in 300..321 -> binding.ivMain.setImageResource(R.drawable.rain)
                in 500..531 -> binding.ivMain.setImageResource(R.drawable.rain)
                in 600..622 -> binding.ivMain.setImageResource(R.drawable.snowflake)
                781 -> binding.ivMain.setImageResource(R.drawable.storm)
                800 -> binding.ivMain.setImageResource(R.drawable.sunny)
                in 801..804 -> binding.ivMain.setImageResource(R.drawable.cloud)
            }
        }

        binding.tvTemp.text = weatherList.main.temp.toString() + getUnit(application.resources.configuration.locale.toString())
        binding.tvHumidity.text = weatherList.main.humidity.toString() + "%"
        binding.tvMin.text = weatherList.main.temp_min.toString() +
                getUnit(application.resources.configuration.locale.toString()) + " min"
        binding.tvMax.text = weatherList.main.temp_max.toString() +
                getUnit(application.resources.configuration.locale.toString()) + " max"
        binding.tvSpeed.text = weatherList.wind.speed.toString()
        binding.tvName.text = weatherList.name
        binding.tvCountry.text = weatherList.sys.country

        //getting current sunset and sunrise
        val sunrise = unixTime(weatherList.sys.sunrise)
        val sunset = unixTime(weatherList.sys.sunset)
        binding.tvSunriseTime.text = sunrise
        binding.tvSunsetTime.text = sunset
    }

    /**
     * Function responsible for returning user temperature unit based on the configuration
     */
    private fun getUnit(value: String): String {
        var unit = "°C"
        if (value == "US" || value == "LR" || value == "MM"){
            unit = "°F"
        }

        return unit
    }

    /**
     * Function responsible for getting current time
     * It changes Long value into Date then set the simple date format and return hours and minutes
     */
    private fun unixTime(timex: Long): String?{
        val date = Date(timex * 1000L)
        val sdf = SimpleDateFormat("HH:mm")
        sdf.timeZone = TimeZone.getDefault()

        return sdf.format(date)
    }

    /**
     * Function responsible for displaying
     */
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return super.onCreateOptionsMenu(menu)
    }

    /**
     * Function responsible for refreshing when menu button is clicked.
     */
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when(item.itemId) {
            R.id.action_refresh -> {
                Log.e("Refresh:", "Clicked")
                requestLocationData()
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

My logs from start -> refresh -> (wait) -> refresh -> now:

2022-03-30 11:48:31.370 10371-10371/eu.apps.weatherapp E/requestLocationData:: Start
2022-03-30 11:48:32.068 10371-10371/eu.apps.weatherapp E/LocationResult:: Start
2022-03-30 11:48:32.069 10371-10371/eu.apps.weatherapp E/LocationWeatherDetails:: Start
2022-03-30 11:51:09.086 10371-10371/eu.apps.weatherapp E/Refresh:: Clicked
2022-03-30 11:51:09.086 10371-10371/eu.apps.weatherapp E/requestLocationData:: Start
2022-03-30 11:54:23.514 10371-10371/eu.apps.weatherapp E/LocationResult:: Start
2022-03-30 11:54:23.515 10371-10371/eu.apps.weatherapp E/LocationWeatherDetails:: Start
2022-03-30 12:13:16.605 10371-10371/eu.apps.weatherapp E/Refresh:: Clicked
2022-03-30 12:13:16.606 10371-10371/eu.apps.weatherapp E/requestLocationData:: Start```

Solution

  • every time when you call requestLocationData() the whole service creates a new instance, which means the whole process starts from zero. it's not a good practice. first of all, you should use the ViewModel approach or create a global retrofit instance.