May 2023 updated version to get continuous location updates.
I initially set the locationCallback
and locationListener
as optional types because some people may want to use one, and other people the other, so I created the code for both of them. Either one is called at the bottom of the else condition in getLocationUpdates()
Because of this choice I also set LocationRequest
as an optional type.
If you get errors, you might need to put @RequiresApi(Build.VERSION_CODES.TIRAMISU)
above each function or outside the class as I did below for simplicity.
Add these to Manifest:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
MainActivity:
import android.content.Context
import android.content.Intent
import android.location.*
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationListener
import android.os.Build
import android.os.Looper
import androidx.annotation.RequiresApi
import com.google.android.gms.location.*
import java.util.*
import android.provider.Settings
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // *** This might not be necessary depending on which SDK version you're using. Also, this should really go above EACH FUNCTION, NOT outside the class ***
class MainActivity : AppCompatActivity() {
// region - Properties
private var fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this.applicationContext)
private var locationRequest: LocationRequest? = null
private var locationCallback: LocationCallback? = null
private var locationListener: LocationListener? = null
// endregion
// region - Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getLocationUpdates() // <--- LocationRequest set in here
}
override fun onResume() {
super.onResume()
startLocationUpdates()
}
override fun onPause() {
super.onPause()
stopLocationUpdates()
}
// endregion
// region - Permissions, LocationRequest, and set LocationCallback OR LocationListener
private fun getLocationUpdates() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this, arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION
),
1
)
} else {
setLocationRequest()
setLocationCallback() // <---CALL HERE, but comment out setLocationListener()
//setLocationListener() // <---CALL HERE, but comment out setLocationCallback()
}
}
private fun setLocationRequest() {
locationRequest = LocationRequest.Builder(1000L).apply {
this.setGranularity(Priority.PRIORITY_HIGH_ACCURACY)
//this.setMinUpdateDistanceMeters(your_choice)
//this.setMaxUpdateDelayMillis(your_choice)
//this.setMinUpdateIntervalMillis(your_choice)
}.build()
}
// endregion
// region - Create LocationCallback / LocationListener
private fun setLocationCallback() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations) {
parseLocationWithGeocoder(location)
}
}
}
}
private fun setLocationListener() {
locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
parseLocationWithGeocoder(location)
}
}
}
// endregion
// region - Start/Stop Location Updates
private fun startLocationUpdates() {
val locationRequest = locationRequest ?: return
locationCallback?.let { callback ->
fusedLocationClient.requestLocationUpdates(locationRequest, callback, Looper.getMainLooper())
}
locationListener?.let { listener ->
fusedLocationClient.requestLocationUpdates(locationRequest, listener, Looper.getMainLooper())
}
}
private fun stopLocationUpdates() {
locationCallback?.let { callback ->
fusedLocationClient.removeLocationUpdates(callback)
}
locationListener?.let { listener ->
fusedLocationClient.removeLocationUpdates(listener)
}
}
// endregion
// region - GeoCoder Location Parsing
private fun parseLocationWithGeocoder(location: Location) {
val geoCoder = Geocoder(this.applicationContext, Locale.getDefault())
geoCoder.getFromLocation(
location.latitude,
location.longitude,
1,
object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: MutableList<Address>) {
if (addresses.isNotEmpty()) {
val address: String = addresses[0].getAddressLine(0)
val city: String = addresses[0].locality
val state: String = addresses[0].adminArea
val country: String = addresses[0].countryName
val postalCode: String = addresses[0].postalCode
val knownName: String = addresses[0].featureName
println("address: $address | city: $city | state: $state | country: $country | zipCode: $postalCode | knownName: $knownName")
} else {
println("Location Unavailable")
}
}
override fun onError(errorMessage: String?) {
super.onError(errorMessage)
}
})
}
// endregion
}
In addition, you should read these answers to handle Permission.DENIED or a location permission dialog not appearing. Seems to be either device version and/or sdk issues.