Search code examples
androidkotlinfragmentandroid-mapviewgoogle-api-client

Retrieve the current Location in fragment with MapView in kotlin?


i want to retrieve the current position and update the position of the user if he moves. I have a mapView within i create the map. Now my question is how can i retieve the user current position and if he moves the position is updated? I use googleApliclient and use Mapview because of the ApiLevel. I can retrieve permissions but sadly i dont know how to retrieve the user location and update this. Also code refinements are welcome.

class MapFragment : Fragment(), OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener {
    private var mapView: MapView? = null
    private var gMap: GoogleMap? = null
    private val LOCATION_PERMISSION_REQUEST_CODE = 1234


    private lateinit var mapViewModel: MapViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        mapViewModel =
            ViewModelProvider(this).get(MapViewModel::class.java)
        val root = inflater.inflate(R.layout.fragment_map, container, false)
        mapView = root.findViewById(R.id.mapView) as MapView
        if (mapView != null) {
            mapView!!.onCreate(null);
            mapView!!.onResume();
            mapView!!.getMapAsync(this);
            checkLocationPermission();
        }
        checkLocationPermission();
        return root
    }


    override fun onResume() {
        super.onResume()
        mapView?.onResume()
    }

    override fun onPause() {
        super.onPause()
        mapView?.onPause()
    }

    override fun onStart() {
        super.onStart()
        mapView?.onStart()
    }

    override fun onStop() {
        super.onStop()
        mapView?.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        mapView?.onDestroy()
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mapView?.onLowMemory()
    }


    override fun onMapReady(googleMap: GoogleMap) {
        MapsInitializer.initialize(context)
        gMap = googleMap
        val coffeys = LatLng(52.075890, 4.309170)
        gMap!!.addMarker(MarkerOptions().position(coffeys).title("coffeys"))
        gMap!!.moveCamera(CameraUpdateFactory.newLatLngZoom(coffeys, 12f))

    }

    fun checkLocationPermission(): Boolean {
        return if (ContextCompat.checkSelfPermission(
                requireActivity(),
                Manifest.permission.ACCESS_FINE_LOCATION
            )
            != PackageManager.PERMISSION_GRANTED
        ) {


            // Asking user if explanation is needed
            if (ActivityCompat.shouldShowRequestPermissionRationale(
                    requireActivity(),
                    Manifest.permission.ACCESS_FINE_LOCATION
                )
            ) {
                //Prompt the user once explanation has been shown
                requestPermissions(
                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                    LOCATION_PERMISSION_REQUEST_CODE
                )
            } else {
                // No explanation needed, we can request the permission.
                requestPermissions(
                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                    LOCATION_PERMISSION_REQUEST_CODE
                )
            }
            false
        } else {
            true
        }
    }

    override fun onConnected(p0: Bundle?) {

    }

    override fun onConnectionSuspended(p0: Int) {
        TODO("Not yet implemented")
    }

    override fun onConnectionFailed(p0: ConnectionResult) {
        TODO("Not yet implemented")
    }


}

Solution

  • Use the fused location provider to retrieve the device's last known location.

    private lateinit var fusedLocationClient: FusedLocationProviderClient
    
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
    
        // ...
    
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    }
    

    To request the last known location and handle the response:

    fusedLocationClient.lastLocation
            .addOnSuccessListener { location : Location? ->
                // Got last known location. In some rare situations this can be null.
            }
    

    This returns a Task that you can use to get a Location object with the latitude and longitude coordinates of a geographic location.

    For more details: https://developer.android.com/training/location/request-updates

    Update: Complete implementation

    @SuppressLint("MissingPermission")
        private fun getDeviceLocation() {
            /*
             * Get the best and most recent location of the device, which may be null in rare
             * cases when a location is not available.
             */
            try {
                if (locationPermissionGranted) {
                    val locationResult = fusedLocationProviderClient.lastLocation
                    locationResult.addOnCompleteListener(requireActivity()) { task ->
    
                        if (task.isSuccessful) {
    
                            // Set the map's camera position to the current location of the device.
                            lastKnownLocation = task.result
                            if (lastKnownLocation != null) {
                                Timber.d("last  known location $lastKnownLocation")
    
                                map.moveCamera(
                                    CameraUpdateFactory.newLatLngZoom(
                                        LatLng(
                                            lastKnownLocation!!.latitude,
                                            lastKnownLocation!!.longitude
                                        ), DEFAULT_ZOOM.toFloat()
                                    )
                                )
                            } else {
                                Timber.e( "Exception: %s", task.exception)
                                map.moveCamera(
                                    CameraUpdateFactory
                                        .newLatLngZoom(defaultLocation, DEFAULT_ZOOM.toFloat())
                                )
                                map.uiSettings?.isMyLocationButtonEnabled = false
                            }
                        }
                    }
                }
            } catch (e: SecurityException) {
                Timber.e("Exception: %s", e.message)
            }
        }
    

    Update 2: Location updates

    This code is from the official documentation, implemented for an activity. Should work well with fragments as well. Note: I have not tested this.

    Call requestLocationUpdates(), passing it your instance of the locationRequest object, and a locationCallback. Define a startLocationUpdates() method as shown in the following code sample:

    override fun onResume() {
        super.onResume()
        if (requestingLocationUpdates) startLocationUpdates()
    }
    
    private fun startLocationUpdates() {
        fusedLocationClient.requestLocationUpdates(locationRequest,
                locationCallback,
                Looper.getMainLooper())
    }
    

    The fused location provider invokes the LocationCallback.onLocationResult() callback method. The incoming argument contains a list Location object containing the location's latitude and longitude. The following snippet shows how to implement the LocationCallback interface and define the method, then get the timestamp of the location update and display the latitude, longitude and timestamp on your app's user interface:

    private lateinit var locationCallback: LocationCallback
    
    // ...
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
    
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                locationResult ?: return
                for (location in locationResult.locations){
                    // Update UI with location data
                    // ...
                }
            }
        }
    }
    

    To stop location updates, call removeLocationUpdates(), passing it a locationCallback, as shown in the following code sample:

    override fun onPause() {
        super.onPause()
        stopLocationUpdates()
    }
    
    private fun stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }
    

    A change to the device's configuration, such as a change in screen orientation or language, can cause the current activity to be destroyed. Your app must therefore store any information it needs to recreate the activity. One way to do this is via an instance state stored in a Bundle object.

    The following code sample shows how to use the activity's onSaveInstanceState() callback to save the instance state:

    override fun onSaveInstanceState(outState: Bundle?) {
        outState?.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, requestingLocationUpdates)
        super.onSaveInstanceState(outState)
    }
    

    Define an updateValuesFromBundle() method to restore the saved values from the previous instance of the activity, if they're available. Call the method from the activity's onCreate() method, as shown in the following code sample:

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        updateValuesFromBundle(savedInstanceState)
    }
    
    private fun updateValuesFromBundle(savedInstanceState: Bundle?) {
        savedInstanceState ?: return
    
        // Update the value of requestingLocationUpdates from the Bundle.
        if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
            requestingLocationUpdates = savedInstanceState.getBoolean(
                    REQUESTING_LOCATION_UPDATES_KEY)
        }
    
        // ...
    
        // Update UI to match restored state
        updateUI()
    }
    

    For a more persistent storage, you can store the user's preferences in your app's SharedPreferences. Set the shared preference in your activity's onPause() method, and retrieve the preference in onResume()