I have finished the product I was developing, but currently, we track the users ( passenger ) location as well as the drivers too slowly.
This is the code I use to track and update the map with the passangers/ drivers icon as it moves :
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
import mobi.audax.tupi.motorista.bin.task.GeoDecodeTask
import mobi.audax.tupi.passageiro.util.Prefs
class IntermitentLocationThread(val context: Context, val onLocationUpdate: (location: Location?) -> Unit) : LocationListener {
private var UPDATE_INTERVAL = (1000 * 10).toLong() // 10 segundos de intervalo
private val MAX_WAIT_TIME = UPDATE_INTERVAL * 2 // 20 segundos
private var bestLocation: Location? = null
fun requestLocation() {
this.locationService()
}
private fun locationService() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.fastestInterval = 1000
locationRequest.interval = UPDATE_INTERVAL
locationRequest.maxWaitTime = MAX_WAIT_TIME
locationRequest.smallestDisplacement = 15f
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationProviderClient.lastLocation.addOnSuccessListener { location -> onLocationChanged(location) }
fusedLocationProviderClient.requestLocationUpdates(locationRequest, object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
onLocationChanged(locationResult.lastLocation)
}
}, Looper.myLooper()!!)
}
}
override fun onLocationChanged(location: Location) {
try {
Log.v("IntermitentLocationThread", "onLocationChanged")
if (location != null) {
val commons = LocationCommons()
// if (!commons.isMock(context, location) && commons.isBetterLocation(location, bestLocation)) {
Log.v("IntermitentLocationThread", "isBetter true")
val prefs = Prefs(context)
prefs.latitude = location.latitude.toFloat()
prefs.longitude = location.longitude.toFloat()
prefs.precisao = location.accuracy
prefs.velocidade = location.speed * 3.6f
prefs.bearing = location.bearing
if (location.extras.containsKey("satellites")) {
prefs.satellites = location.extras.getInt("satellites")
}
GeoDecodeTask(context, location).decoder { }
bestLocation = location
onLocationUpdate(bestLocation)
} else {
Log.v("IntermitentLocationThread", "isBetter false")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
and heres how i implement it in my Activity:
private void handleLocationUpdates() {
if (isLocationEnabled()) {
loadMapScene();
IntermitentLocationThread thread = new IntermitentLocationThread(this, location -> {
Log.e(TAG, "handleLocationUpdates: "+"pegado localização" );
clearPassageiroMapMarker();
addPassageiroMarker(new GeoCoordinates(location.getLatitude(),
location.getLongitude()), R.drawable.ic_passageiro);
passageiro.setLat(location.getLatitude());
passageiro.setLongitude(location.getLongitude());
SharedPreferences.Editor editor = this.getSharedPreferences(Constantss.PREFERENCES, MODE_PRIVATE).edit();
mapView.getCamera().lookAt(new GeoCoordinates(passageiro.getLat(), passageiro.getLongitude()));
return null;
});
thread.requestLocation();
} else {
Toast.makeText(this, "Por favor" + "ative sua localização...", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
}
at the moment, I get passengers/drivers' locations in a very weird and odd consistency, it's not as " fluid " as uber does it. right now, my marker jumps from one point to another (because I clear the marker list and set another one in place, still working on having only one marker in a heremaps map) in a range of 5 to 15 seconds and everybody else on the internet seems to use this google engine to track one's location.
How is it possible to track users' location in a faster / smoother way?
i was able to find a better/fast tracking by using Google's Location Manager...
yes.. LocationManager.
The code i was using before that wasnt really accurate at all, with Googles Location Manager i was able to set a specific timer and keep it accurate.
More on : https://developer.android.com/reference/android/location/LocationManager
Related
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.
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.
To request the last known location of the user's device, we can use the fused location provider to retrieve the device's last known location using getLastLocation(), but using getCurrentLocation() gets a refresher, and more accurate location.
so, how to use the fusedLocationClient.getCurrentLocation() in Kotlin as there is no example illustrated in the documentation?
According to the documentation, the getCurrentLocation() takes two parameters.
The 1st parameter it takes is the priority (e.g. PRIORITY_HIGH_ACCURACY) to request the most accurate locations available, or any other priority that can be found here.
The 2nd parameter it takes is a cancellation token that can be used to cancel the current location request.
From the Google play services reference, a CancellationToken can only be created by creating a new instance of CancellationTokenSource.
so here is the code you need to use when using getCurrentLocation()
class YourActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = location.latitude
val lon = location.longitude
}
}
}
}
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(listener: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener {
if (it == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = it.latitude
val lon = it.longitude
}
}
I know the questio is about Kotlin but I want to add for those searching for Java :
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, cancellationTokenSource.getToken())
.addOnSuccessListener(MyActivity.this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
//do your thing
}
Log.w(TAG, "No current location could be found");
}
});
After I updated my phone to Android 12, my app (TargetSDKVersion 29) stopped to get location updates. So I have to update the app to API 31.
For this I want to write some instrumented tests for the location updates. I started with the simple test for requestLocationUpdates(). But in this simple test I don't get any location update.
Just in case I also adjusted the timestamps to 5s intervals to simulate some time between the updates - but no luck.
package de.leo.android.buddytracker_lib
import android.content.Context
import android.location.*
import android.os.Looper
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
private const val MOCK_PROVIDER = "MockLocationProvider"
private const val LOG_TAG ="**** Test ****"
class LocationHandlerAndroidTests : LocationListener {
private lateinit var context: Context
private lateinit var locationManager: LocationManager
private var locationUpdateCount = 0
private var currentLocation: Location? = null
override fun onLocationChanged(location: Location) {
locationUpdateCount++
currentLocation = location
}
#Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().context
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.addTestProvider(
MOCK_PROVIDER,
false,
false,
false,
false,
false,
false,
false,
Criteria.POWER_LOW,
Criteria.ACCURACY_FINE
)
locationManager.setTestProviderEnabled(MOCK_PROVIDER, true)
assertTrue(locationManager.isLocationEnabled)
locationUpdateCount = 0
currentLocation = null
Thread.sleep(5000L)
}
#After
fun tearDown() {
locationManager.removeTestProvider(MOCK_PROVIDER)
}
#Test
fun requestLocationUpdatesTest() {
Log.d(LOG_TAG, "Requesting Location Updates")
locationManager.requestLocationUpdates(MOCK_PROVIDER, 4000, 10.0f, this, Looper.getMainLooper())
val location1 = Location(MOCK_PROVIDER)
location1.latitude = 10.0
location1.longitude = 20.0
location1.accuracy = 2.0f
location1.time = 1000
location1.elapsedRealtimeNanos = 10000000000
Log.d(LOG_TAG, "set location 1")
locationManager.setTestProviderLocation(MOCK_PROVIDER, location1)
Thread.sleep(5000L)
val location2 = Location(MOCK_PROVIDER)
location2.latitude = 11.0
location2.longitude = 21.0
location2.accuracy = 2.0f
location2.time = 5000
location2.elapsedRealtimeNanos = 15000000000
Log.d(LOG_TAG, "set location 2")
locationManager.setTestProviderLocation(MOCK_PROVIDER, location2)
Thread.sleep(5000L)
// Check if your listener reacted the right way
assertEquals("Got location updates", 2, locationUpdateCount)
assertEquals(11.0, currentLocation?.latitude)
assertEquals(21.0, currentLocation?.longitude)
}
}
Any tips what I made wrong here?
i am trying to get user's location using only GPS provider. i don't want my app to get the last known location or request for location updates after a particular period of time or when location is changed.is there any way that can give me current location on button press ??? ... i am a beginner, i have tried LocationManager API from android studio and it returns network provided location fine but dosent work when using GPS . here is my code :
fun getlocation() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), locationPermissionCode)
}
else
{
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
// val listener = locationListener()
val gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
//if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
if (gps || network) {
if (network) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10000, 5F, this)
}
if (gps) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 5F, this)
}
}
else {
val AlertDialog = AlertDialog.Builder(this)
AlertDialog.setTitle("GPS is Disabled")
AlertDialog.setMessage("Do you want enable GPS ?")
AlertDialog.setPositiveButton("Yes") { dialougeInterface: DialogInterface, i: Int ->
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
dialougeInterface.dismiss()
}
AlertDialog.setNegativeButton("No") { dialouge: DialogInterface, i: Int ->
dialouge.dismiss()
}
AlertDialog.show()
}
}
}
i have added location listener in the parent class " class AddCustomer : AppCompatActivity() , LocationListener "
in override function :
override fun onLocationChanged(location: Location) {
locationx.text = location.latitude.toString() +" , "+ location.longitude.toString()
}
should i use "getcurrentlocation()" method from "LocationManager" ?
I don't know if you are getting your last location or not. Here's the answer when you are not getting anything at all from the gps provider.
Making this block like this will only make you get your location via gps.
if (gps) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 5F, this)
}
The above can return when your device's GPS chipset feels like it's getting signal from the GPS, so when inside a building, it will likely be not getting any responds.
Go outside and try it out.
In your usecase, pressing a button to get an accurate location, in case the user can't receive any signal, you should provide a callback for timeout.
private val mHandler = Handler(Looper.getMainLooper())
private val mTimeoutRunnable = Runnable {
//stopLoading
//Toast the user to try it somewhere else
}
fun askGsp() {
mHandler.postDelayed(mTimeoutRunnable, 5 * 1000) //time out in 5 seconds
//ask for gps update like the code above
//...
override fun onLocationChanged(location: Location) {
mHandler.removeCallbacks(mTimeoutRunnable) //remove timeout action
locationx.text = location.latitude.toString() +" , "+
location.longitude.toString()
}
}
My app works fine if using foreground service to get user location, however in background I just need location for each 15 minutes, also Google requires new Policy for getting background location so, foreground service is over my expectation.
I am trying to get location from background using WorkManager, it can run normally every (around) 15 minutes. My location is requested, however it always returns the previous address, even 1, 2... hours are passed.
Here is my code:
class LocationWorker(private val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override suspend fun doWork() = withContext(Dispatchers.IO) {
val location = getLocation()
if (location == null) {
if (runAttemptCount < MAX_ATTEMPT) { // max_attempt = 3
Result.retry()
} else {
Result.failure()
}
} else {
Log.d(TAG, "doWork success $location")
Result.success()
}
}
private suspend fun getLocation(): Location? = withTimeoutOrNull(TIMEOUT) {
suspendCancellableCoroutine<Location?> { continuation ->
val intent = PendingIntent.getBroadcast(context, REQUEST_CODE, Intent(ACTION), 0)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, data: Intent?) {
if (data?.action != ACTION) return
val lastLocation = LocationResult.extractResult(data)?.lastLocation
Log.e(TAG, "Get lastLocation success $lastLocation")
fusedLocationClient.removeLocationUpdates(intent)
context?.unregisterReceiver(this)
continuation.resume(lastLocation)
}
}
context.registerReceiver(receiver, IntentFilter(ACTION))
val request = LocationRequest().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY }
fusedLocationClient.requestLocationUpdates(request, intent)
continuation.invokeOnCancellation {
fusedLocationClient.removeLocationUpdates(intent)
context.unregisterReceiver(receiver)
}
}
companion object {
val TAG = LocationWorker::class.java.simpleName
const val LOCATION_WORKER_TAG = "LOCATION_WORKER_TAG"
const val MAX_ATTEMPT = 3
private const val ACTION = "my.background.location"
private const val TIMEOUT = 60_000L
private const val REQUEST_CODE = 888
}
}
Pre-condition:
Tested device: emulator android 27 (O_MR1)
Route Play normally
GPS is enabled
Allow location permission (allow all time)
Why the lastknown location not updated ?
I also tried this demo https://github.com/pratikbutani/LocationTracker-WorkManager/ . However, the problem is same, lastknown location is not updated.
The previous location gets cached.
Clear the cache before you store the new location in lastknown location