get the current location fast and once in android
Here, you can use this...
Example usage:
public void foo(Context context) {
// when you need location
// if inside activity context = this;
SingleShotLocationProvider.requestSingleUpdate(context,
new SingleShotLocationProvider.LocationCallback() {
@Override public void onNewLocationAvailable(GPSCoordinates location) {
Log.d("Location", "my location is " + location.toString());
}
});
}
You might want to verify the lat/long are actual values and not 0 or something. If I remember correctly this shouldn't throw an NPE but you might want to verify that.
public class SingleShotLocationProvider {
public static interface LocationCallback {
public void onNewLocationAvailable(GPSCoordinates location);
}
// calls back to calling thread, note this is for low grain: if you want higher precision, swap the
// contents of the else and if. Also be sure to check gps permission/settings are allowed.
// call usually takes <10ms
public static void requestSingleUpdate(final Context context, final LocationCallback callback) {
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (isNetworkEnabled) {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
locationManager.requestSingleUpdate(criteria, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
callback.onNewLocationAvailable(new GPSCoordinates(location.getLatitude(), location.getLongitude()));
}
@Override public void onStatusChanged(String provider, int status, Bundle extras) { }
@Override public void onProviderEnabled(String provider) { }
@Override public void onProviderDisabled(String provider) { }
}, null);
} else {
boolean isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (isGPSEnabled) {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
locationManager.requestSingleUpdate(criteria, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
callback.onNewLocationAvailable(new GPSCoordinates(location.getLatitude(), location.getLongitude()));
}
@Override public void onStatusChanged(String provider, int status, Bundle extras) { }
@Override public void onProviderEnabled(String provider) { }
@Override public void onProviderDisabled(String provider) { }
}, null);
}
}
}
// consider returning Location instead of this dummy wrapper class
public static class GPSCoordinates {
public float longitude = -1;
public float latitude = -1;
public GPSCoordinates(float theLatitude, float theLongitude) {
longitude = theLongitude;
latitude = theLatitude;
}
public GPSCoordinates(double theLatitude, double theLongitude) {
longitude = (float) theLongitude;
latitude = (float) theLatitude;
}
}
}
AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
MainActivity.java:
public class MainActivity extends AppCompatActivity implements LocationListener {
private LocationManager locationManager;
private Location onlyOneLocation;
private final int REQUEST_FINE_LOCATION = 1234;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
}
@Override public void onLocationChanged(Location location) {
onlyOneLocation = location;
locationManager.removeUpdates(this);
}
@Override public void onStatusChanged(String provider, int status, Bundle extras) { }
@Override public void onProviderEnabled(String provider) { }
@Override public void onProviderDisabled(String provider) { }
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_FINE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("gps", "Location permission granted");
try {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates("gps", 0, 0, this);
}
catch (SecurityException ex) {
Log.d("gps", "Location permission did not work!");
}
}
break;
}
}
For anyone interested in retrieving a single location update, in the best, idiomatic way, using the latest APIs, and the magic of Kotlin, here you go:
Gradle dependency:
dependencies {
...
implementation "com.google.android.gms:play-services-location:18.0.0"
...
}
Manifest permissions:
<manifest>
...
<!-- required only for LocationRequest.PRIORITY_HIGH_ACCURACY -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- required for all other priorities -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
...
</manifest>
Somewhere in your Extensions
file:
// To use PRIORITY_HIGH_ACCURACY, you must have ACCESS_FINE_LOCATION permission.
// Any other priority will require just ACCESS_COARSE_LOCATION,
// but will not guarantee a location update
@SuppressLint("MissingPermission")
suspend fun FusedLocationProviderClient.awaitCurrentLocation(priority: Int): Location? {
return suspendCancellableCoroutine {
// to use for request cancellation upon coroutine cancellation
val cts = CancellationTokenSource()
getCurrentLocation(priority, cts.token)
.addOnSuccessListener {location ->
// remember location is nullable, this happens sometimes
// when the request expires before an update is acquired
it.resume(location)
}.addOnFailureListener {e ->
it.resumeWithException(e)
}
it.invokeOnCancellation {
cts.cancel()
}
}
}
In your fragment:
// need to register this anywhere before onCreateView, idealy as a field
private val permissionRequester = registerForActivityResult(
// you can use RequestPermission() contract if you only need 1 permission
ActivityResultContracts.RequestMultiplePermissions()
) { map ->
// If you requested 1 permission, change `map` to `isGranted`
// Keys are permissions Strings, values are isGranted Booleans
// An easy way to check if "any" permission was granted is map.containsValue(true)
// You can use your own logic for multiple permissions,
// but they have to follow the same checks here:
val response = map.entries.first()
val permission = response.key
val isGranted = response.value
when {
isGranted -> onPermissionGranted()
ActivityCompat.shouldShowRequestPermissionRationale(requireContext(), permission) -> {
// permission denied but not permanently, tell user why you need it.
// Idealy provide a button to request it again and another to dismiss
AlertDialog.Builder(requireContext())
.setTitle(R.string.perm_request_rationale_title)
.setMessage(R.string.perm_request_rationale)
.setPositiveButton(R.string.request_perm_again) { _, _ ->
requirePermission()
}
.setNegativeButton(R.string.dismiss, null)
.create()
.show()
}
else -> {
// permission permanently denied
// 1) tell user the app won't work as expected, or
// 2) take him to your app's info screen to manually change permissions, or
// 3) silently and gracefully degrade user experience
// I'll leave the implementation to you
}
}
}
onPermissionGranted function:
private fun onPermissionGranted() {
val lm = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
if(LocationManagerCompat.isLocationEnabled(lm)) {
// you can do this your own way, eg. from a viewModel
// but here is where you wanna start the coroutine.
// Choose your priority based on the permission you required
val priority = LocationRequest.PRIORITY_HIGH_ACCURACY
lifecycleScope.launch {
val location = LocationServices
.getFusedLocationProviderClient(requireContext())
.awaitCurrentLocation(priority)
// do whatever with this location, notice that it's nullable
}
} else {
// prompt user to enable location or launch location settings check
}
}
Now all you have to do is add this to MyLocation button click listener:
private fun requirePermission() {
val permissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
// optional: Manifest.permission.ACCESS_COARSE_LOCATION
)
permissionRequester.launch(permissions)
}
Note that this has the beauty of checking if the permission was already given implicitly, and not show a dialog/request if that was the case. Ergo, always start your flow by launching the requester and only do your checks in its callback.
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-feature android:name="android.hardware.location.gps" />
Requesting User Permissions
build.gradle (Module: app)
dependencies {
...
implementation 'com.google.android.gms:play-services-location:15.0.0'
...
}
If you receive an error, check that your top-level build.gradle contains a reference to the google() repo or to maven { url "https://maven.google.com" }
Set Up Google Play Services
LocationService.kt
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.net.Uri
import android.os.Looper
import android.provider.Settings
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import org.jetbrains.anko.alert
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.okButton
object LocationService {
@SuppressLint("StaticFieldLeak")
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var locationRequest: LocationRequest
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
doAsync {
location = locationResult.lastLocation
onSuccess(location)
}
}
}
private lateinit var onSuccess: (location : Location) -> Unit
private lateinit var onError: () -> Unit
lateinit var location: Location
fun init(activity: Activity) {
fusedLocationProviderClient = FusedLocationProviderClient(activity)
locationRequest = LocationRequest().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(1000).setFastestInterval(1000).setNumUpdates(1)
}
private fun checkLocationStatusAndGetLocation(activity: Activity) {
doAsync {
when {
ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED -> LocationServices.getSettingsClient(activity).checkLocationSettings(LocationSettingsRequest.Builder().addLocationRequest(locationRequest).setAlwaysShow(true).build()).addOnCompleteListener { task ->
doAsync {
try {
task.getResult(ApiException::class.java)
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
} catch (exception: ApiException) {
when (exception.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
try {
(exception as ResolvableApiException).startResolutionForResult(activity, 7025)
} catch (ex: Exception) {
promptShowLocation(activity)
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
promptShowLocation(activity)
}
}
}
}
}
ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION) -> activity.runOnUiThread {
activity.alert("To continue, allow the device to use location, witch uses Google's Location Service") {
okButton {
val ite = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", activity.packageName, null))
ite.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.startActivity(ite)
onError()
}
negativeButton("Cancelar", { onError() })
onCancelled { onError() }
}.show()
}
else -> ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 7024)
}
}
}
private fun promptShowLocation(activity: Activity) {
activity.runOnUiThread {
activity.alert("To continue, allow the device to use location, witch uses Google's Location Service") {
okButton {
activity.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
onError()
}
negativeButton("Cancelar", { onError() })
onCancelled { onError() }
}.show()
}
}
fun onRequestPermissionsResult(activity: Activity, requestCode: Int, grantResults: IntArray) {
if (requestCode == 7024) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
checkLocationStatusAndGetLocation(activity)
} else {
onError()
}
}
}
fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int) {
if (requestCode == 7025) {
if (resultCode == Activity.RESULT_OK) {
checkLocationStatusAndGetLocation(activity)
} else {
onError()
}
}
}
fun getLocation(activity: Activity, onSuccess: () -> Unit, onError: () -> Unit) {
this.onSuccess = onSuccess
this.onError = onError
checkLocationStatusAndGetLocation(activity)
}
}
your activity
override fun onCreate(savedInstanceState: Bundle?) {
...
LocationService.init(this)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
LocationService.onRequestPermissionsResult(this, requestCode, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
LocationService.onActivityResult(this, requestCode, resultCode)
}
private fun yourFunction() {
LocationService.getLocation(this, { location ->
//TODO: use the location
}, {
//TODO: display error message
})
}