Search code examples
androidcurrentlocationfusedlocationproviderclient

Unable to get current (real time) location using FusedLocationProviderClient


After 2 days of struggle, I have been forced to ask this question. I have been searching StackOverflow but no solution seems to work for me. I don't know why.

I am trying to get the current device location once:

(a) the permissions have been granted

(b) user has enabled location service by tapping 'Okay' in the dialogue box that appears in the fragment (SelectLocationFragment.kt)

The issue: Once the permissions are granted, location is enabled by pressing 'okay' in the dialogue - If I use hard coded values like here:

//currentLatLng = LatLng(51.000, -0.0886)

instead of :

currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)

It works fine. But this is not ideal of course. I'd like real time device location using onLocationCallBack's onLocationResult using:

                    fusedLocationClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper())

The problem is when I try to use:

currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)

a) onLocationCallBack does not seem to be called. And therefore no call to onLocationResult happens, and therefore, mCurrentLocation remains uninitialised. And hence I get the error :

kotlin.UninitializedPropertyAccessException: lateinit property mCurrentLocation has not been initialized

This my onMapReady()

@SuppressLint("MissingPermission")
override fun onMapReady(gMap: GoogleMap) {
    isPoiSelected = false

    map = gMap

    **checkPermissionsAndDeviceLocationSettings()**

    setMapStyle(map)
    setPoiClick(map)
    setMapLongClick(map)
}

This is checkPermissionAndDeviceLocationSettings():

@SuppressLint("MissingPermission")
private fun checkPermissionsAndDeviceLocationSettings() {
    if (isPermissionGranted()) {
        checkDeviceLocationSettings()
    } else {
        requestPermissions(
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            REQUEST_PERMISSION_LOCATION
        )
    }
}

This is checkDeviceLocationSettings:

private fun checkDeviceLocationSettings(resolve: Boolean = true) {

    val locationRequest = LocationRequest.create().apply {
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        interval = 10000L
    }

    val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)

    val settingsClient = LocationServices.getSettingsClient(requireActivity())

    val locationSettingsResponseTask =
        settingsClient.checkLocationSettings(builder.build())

    locationSettingsResponseTask.addOnFailureListener { exception ->
        if (exception is ResolvableApiException && resolve) {
            try {
                startIntentSenderForResult(
                    exception.resolution.intentSender,
                    REQUEST_TURN_DEVICE_LOCATION_ON, null, 0, 0, 0, null
                )

            } catch (sendEx: IntentSender.SendIntentException) {
                Log.d(
                    SaveReminderFragment.TAG,
                    "Error getting location settings resolution: " + sendEx.message
                )
            }
        } else {
            Snackbar.make(
                activity!!.findViewById<CoordinatorLayout>(R.id.myCoordinatorLayout),
                R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
            ).setAction(android.R.string.ok) {
                checkDeviceLocationSettings()
            }.show()
        }
    }

    locationSettingsResponseTask.addOnSuccessListener {

        Log.e(TAG, "SUCCESSFUL!")
        Toast.makeText(requireContext(), "TOAST", Toast.LENGTH_SHORT).show()

        enableLocation(locationRequest)
        showSnackBar(getString(R.string.select_location))
    }
}

This is enableLocation:

@SuppressLint("MissingPermission")
private fun enableLocation(locationRequest: LocationRequest) {

    Log.e(TAG, "Inside Enable Location Start")

    locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            Log.e(TAG, "Inside on Location Result")
            val locationList = locationResult.locations
            Log.e(TAG, "${locationResult.locations}")
            if(locationList.isNotEmpty()){
                val location = locationList.last()
                Log.i("MapsActivity", "Location: " + location.latitude + " " + location.longitude)
                mCurrentLocation = location
            }

            fusedLocationClient.removeLocationUpdates(locationCallback)
        }
    }

    map.isMyLocationEnabled = true

    val locationResult: Task<Location> = fusedLocationClient.lastLocation

    locationResult.addOnCompleteListener(OnCompleteListener<Location?> { task ->
        if (task.isSuccessful) {
            Log.e(TAG, locationResult.result?.latitude.toString())
            // Set the map's camera position to the current location of the device.
            if (task.result != null) {
                mCurrentLocation = task.result!!
                val latLng = LatLng(
                    mCurrentLocation.latitude,
                    mCurrentLocation.longitude
                )
                val update = CameraUpdateFactory.newLatLngZoom(
                    latLng,
                    18f
                )
                Toast.makeText(requireContext(), "CAMERA MOVING", Toast.LENGTH_SHORT).show()
                map.animateCamera(update)
            } else {
                Log.e(TAG, " Task result is null")
                //Need to do something here to get the real time location
                fusedLocationClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper())

                currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)

                //currentLatLng = LatLng(51.000, -0.0886)

                val update = CameraUpdateFactory.newLatLngZoom(currentLatLng, 18f)
                map.animateCamera(update)

            }
        } else {

            Log.e(TAG, "Unsuccessful Task result")
            Toast.makeText(requireContext(), "ENABLE LOCATION ELSE", Toast.LENGTH_LONG).show()

        }
    })
}

Is there any God sent man out there to help me resolve this issue. I am beginning to fade away.

If you guys need to see the entire file:

Thank you in advance.


Solution

  • FusedLocationProvider can return a null Location, especially the first time it fetches a Location. So, as you have found, simply returning when the result is null will force requestLocationUpdates() to fire again and actually fetch a valid Location the second time.

    I think you can also force FusedLocationProvider to have a Location the first time if you just open the Google Maps app and wait for it to obtain a GPS lock. Of course, you wouldn't want your users to have to perform this process in order for your app to work, but it's still good to know for debugging.

    The cause of this seems to be that FusedLocationProvider tries to return the most recently fetched Location for the device. If this Location is too old, has never been fetched, or the user toggled on/off the Location option on their phone, it just returns null.