Project API requirements: API 23+.
Location details are available and they work for API 26+, but for API 23 don't.
I have just read the article Get user's location in android API 23
However, tried different solutions and still have no result. What could be the reason?
class TabFragment1 : Fragment() {
var lat: Double = 0.0
var long: Double = 0.0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.let {}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreate(savedInstanceState)
val v: View = inflater.inflate(R.layout.fragment_tab1, container, false)
val spinner1 = v.spinnerCategory as Spinner
val buttonSearch = v.buttonSearch as Button
// Search button
buttonSearch.setOnClickListener {
// GET LOCATION
val g = this.activity?.let {
it1 -> geo(it1)
}
if (g != null) {
activity?.let {
if (g[0] == 0.0 && g[1] == 0.0) {
println(" g result ${g[0]}")
alert(getString(R.string.error_no_location))
// Request location updates
if (ActivityCompat.checkSelfPermission(it, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
var permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
permissions = permissions.plus(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
ActivityCompat.requestPermissions(context as Activity, permissions, 0)
}
} else {
// segue
val intent = Intent(it, PostTableViewActivity::class.java)
intent.putExtra("long", g[1].toString())
intent.putExtra("lat", g[0].toString())
startActivity(intent)
}
}
} else {
alert("TurnOn Geolocation!")
}
}
return v
}
geo.kt:
fun geo(context: Context): DoubleArray? {
val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val providers: List<String> = locationManager.getProviders(true)
var location: Location? = null
for (i in providers.size - 1 downTo 0) {
if (ActivityCompat.checkSelfPermission(context.applicationContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(context.applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
println(" NO PERMS?? ")
var permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
permissions = permissions.plus(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
ActivityCompat.requestPermissions(context as Activity, permissions,0)
return null
}
location = locationManager.getLastKnownLocation(providers[i])
if (location != null)
break
}
val gps = DoubleArray(2)
if (location != null) {
println(" GEO FUNC has lat, long - ?? ${location.latitude}, ${location.longitude}")
gps[0] = location.latitude
gps[1] = location.longitude
}
return gps
}
API 23 console result:
I/System.out: g result 0.0
API 26 console result:
I/System.out: GEO FUNC has lat, long - ?? 37.421998333333335, -122.08400000000002
Manifest:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
Only this works for API == 23
1. build.gradle
implementation 'com.google.android.gms:play-services-location:17.1.0'
2. permission
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3. context -> gps[latitude, longitude]
private fun requestCurrentLocation(context: Context): DoubleArray? {
val fusedLocationClient: FusedLocationProviderClient by lazy {
LocationServices.getFusedLocationProviderClient(context)
}
var cancellationTokenSource = CancellationTokenSource()
val gps = DoubleArray(2)
// Check Fine permission
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// Main code
val currentLocationTask: Task<Location> = fusedLocationClient.getCurrentLocation(
PRIORITY_HIGH_ACCURACY,
cancellationTokenSource.token
)
currentLocationTask.addOnCompleteListener { task: Task<Location> ->
val result = if (task.isSuccessful) {
val result: Location = task.result
gps[0] = result.latitude
gps[1] = result.longitude
"Location (success): ${result.latitude}, ${result.longitude}"
} else {
val exception = task.exception
"Location (failure): $exception"
}
println("getCurrentLocation() result: $result")
}
} else {
// Request fine location permission (full code below).
}
return gps
}
Related
I am trying to implement a way to get the location once and then return it in Kotlin. This is implemented in an Android widget to fetch a location that is used to fetch more data that is then displayed in a widget.
Currently my implementation is as following:
suspend fun getLocation(
context: Context,
fusedLocationClient: FusedLocationProviderClient
): LocationInterface? {
if (ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return null
}
val def = CompletableDeferred<LocationInterface?>()
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null) {
def.complete(null)
}
var name = ""
try {
runBlocking {
val geocoder = Geocoder(context, Locale.getDefault())
val addresses =
geocoder.getFromLocation(location!!.latitude, location!!.longitude, 1)
}
} catch (e: IOException) {
def.complete(null)
}
if (name.isNotEmpty()) {
def.complete(
LocationInterface(
name,
location!!.latitude,
location.longitude,
)
)
} else {
def.complete(null)
}
}.addOnFailureListener {
def.complete(null)
}
return def.await()
}
It works in my emulator, but I get consistently get errors from real devices with java.lang.NullPointerException referring to getLocation$3$1.invokeSuspend
Does anyone know what I may be doing wrong or have a better solution to simply fetch the location and reverse geocode it and return it from the function that makes it?
Thanks in advance!
GPS service is not enabled on Android versions 12 and above.
Everything worked correctly on earlier versions
I have read this documentation,
but I'm not sure if I have a problem with android:foregroundServiceType since I basically don't use it in the application.
So here is some of my code AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
And code of my vm:
private fun enableGps(activity: FragmentActivity) {
val manager = activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER) && hasGPSDevice(manager)) {
locationManager.enableGps(activity)
}
}
And here is the implementation of the method itself:
override fun enableGps(activity: FragmentActivity) {
if (googleApiClient == null) {
googleApiClient = GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(object : GoogleApiClient.ConnectionCallbacks {
override fun onConnected(bundle: Bundle?) {}
override fun onConnectionSuspended(i: Int) {
googleApiClient?.connect()
}
})
.addOnConnectionFailedListener { connectionResult ->
Timber.e("${connectionResult.errorCode}")
}.build()
googleApiClient?.connect()
}
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 10000
locationRequest.fastestInterval = 5000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
builder.setAlwaysShow(true)
googleApiClient?.let {
val result: PendingResult<LocationSettingsResult> =
LocationServices.SettingsApi.checkLocationSettings(it, builder.build())
result.setResultCallback { result ->
val status: Status = result.status
when (status.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
// Show the dialog by calling startResolutionForResult(),
status.startResolutionForResult(
activity,
REQUEST_LOCATION
)
} catch (e: IntentSender.SendIntentException) {
Timber.e(e)
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
activity.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
}
}
}
}
UPD
Here is full code of my DeviceLocationManager, maybe I 'm requesting my permissions somehow wrong here:
https://gist.github.com/mnewlive/0c0a0c1f7ccb26fe58fd6b0fa5dd1dda
I rewrote the following method because it was deprecated:
override fun isLocationProviderActive(): Boolean {
val providerInfo = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.LOCATION_PROVIDERS_ALLOWED
)
return providerInfo?.isNotEmpty() == true
}
to
override fun isLocationStateEnabled(): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)
}
Improved version!
The following code shows how to ACCESS_FINE_LOCATION. While longer than my original code it is preferred because it stops GPS updates when the App is Paused or Stopped.
Due to issues with the Android Studio emulators it also includes a simple watchdog timer reporting the GPS Updates have stopped if no updates are received for approximately 10 seconds
const val PERMISSIONS_REQUEST_ACCESS_LOCATION = 99 // any positive value
class MainActivity : AppCompatActivity() {
private lateinit var tvCurrentTime : TextView
private lateinit var tvSpeed : TextView
private lateinit var tvBearing : TextView
private var myTimer: Timer = Timer()
private var sHourMinuteSecond : String = "00:00:00"
private lateinit var fusedLocationClient : FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private var gpsWatchDog : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvCurrentTime = findViewById<View>(R.id.show_time_textview) as TextView
tvSpeed = findViewById<View>(R.id.speed_textview) as TextView
tvBearing = findViewById<View>(R.id.bearing_textview) as TextView
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity) as FusedLocationProviderClient
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
processNewLocation(locationResult.lastLocation) // only process the lastLocation (may be others)
}
}
}
private fun checkPermissionsForGPSUpdates() {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
// Android wants us to request both COARSE and FINE permissions
writeToLog("checkSelfPermission Permission Not Granted so requesting!")
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSIONS_REQUEST_ACCESS_LOCATION)
// During requestPermission OnPause() is called
// When user grants permission OnResume() is called which once again calls checkPermissionsForGPSUpdates() when GPS updates should commence
return
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// only run if we have FINE location
writeToLog("ACCESS_FINE_LOCATION Permission Granted start GPS updates !")
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
try {
if (!fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()).isSuccessful) {
writeToLog("requestLocationUpdates() was unsuccessful") // Stackoverflow indicates this may actually be a google error
}
} catch (unlikely: SecurityException) {
writeToLog("Lost location permission. Could not request updates. $unlikely")
}
} else {
writeToLog("Permissions Not Granted so exit")
Toast.makeText(this#MainActivity, "App will not run without GPS" , Toast.LENGTH_LONG).show()
finish()
}
}
private fun updateTimeString() {
// Called by the timer DO NOT put code affecting the UI in here!!!
val systemTime = GregorianCalendar()
sHourMinuteSecond = SimpleDateFormat("HH:mm:ss").format(systemTime.timeInMillis)
// Use the runOnUiThread method to update the display
this.runOnUiThread(updateDisplay)
}
private val updateDisplay = Runnable {
// This method runs in the same thread as the User Interface
// due to continuous crashing of the emulator has been written to be short
tvCurrentTime.text = sHourMinuteSecond
if (++gpsWatchDog > 10) {
tvBearing.text = "No GPS"
}
}
private fun processNewLocation(latestLocation : Location) {
// reset the watchdog since we have received a GPS update
gpsWatchDog = 0
// do something with the latest location
// for now just show the GPS time
val sSpeed = SimpleDateFormat("HH:mm:ss").format(latestLocation.time)
tvSpeed.text = sSpeed
val sBearing = latestLocation.bearing.roundToInt().toString() // no decimal places
tvBearing.text = sBearing
}
override fun onResume() {
super.onResume()
writeToLog("Called onResume()")
// start the GPS updates (after checking user permission)
checkPermissionsForGPSUpdates()
val currentSystemTime = System.currentTimeMillis()
// start a timer to update the time
myTimer = Timer()
val delayToNextWholeSecond = ((currentSystemTime / 1000) + 1) * 1000 - currentSystemTime // Synchronise to whole seconds
myTimer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
updateTimeString()
}
}, delayToNextWholeSecond, 1000)
}
override fun onPause() {
super.onPause()
writeToLog("onPause()")
myTimer.cancel() // stop the existing timer
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
override fun onStop() {
super.onStop()
writeToLog("onStop()")
myTimer.cancel()
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private fun writeToLog(message : String) {
Log.d("SmallGPSDemo", message)
}
}
The original very short example is shown below only to show Android's new RequestPermission() to obtain permissions (and start updates)
class MainActivity : AppCompatActivity() {
private var debugTextView : TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
debugTextView = findViewById<View>(R.id.debug_textview) as TextView
// this checks permission and starts the location updates
requestPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
private fun processNewLocation(latestLocation : Location) {
// do something with the location
debugTextView!!.text = SimpleDateFormat("hh:mm:ss.SSS").format(System.currentTimeMillis())
}
#SuppressLint("MissingPermission")
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// Permissions granted so start GPS updates
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
processNewLocation(location) // Settings should only give one location but ...
}
}
}
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity)
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
} else {
Log.d("DEBUG", "permission denied ")
Toast.makeText(this#MainActivity, "This activity will not run unless you allow GPS", Toast.LENGTH_LONG).show()
finish()
}
}
}
Reminder add location permissions to the AndroidManifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.reallysmallgpscode">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.ReallySmallGPSCode">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Hopefully others find the code useful
I'm learning Kotlin, and I'm trying to make a simple app that displays the users long/lat. Everything seems like it should be working, but the location data keeps throwing the 'null else{}' case. Below is what my code looks like.
class MainActivity : AppCompatActivity() {
val RequestPermissionCode = 1
var mLocation: Location? = null
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
getLastLocation()
}
fun getLastLocation(){
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermission()
}
else{
fusedLocationClient.lastLocation
.addOnSuccessListener {location: Location? ->
mLocation = location
if(location != null){
latitude.text = location.latitude.toString()
longitude.text = location.longitude.toString()
}
else{
latitude.text = "LOCATION_DENIED"
longitude.text = "LOCATION_DENIED"
}
}
}
}
private fun requestPermission(){
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), RequestPermissionCode)
this.recreate()
}
}
Any help would be greatly appreciated! I'm about to start pulling my hair out, and can't find the answers on Google ):
Emulators I have noticed have issues with Location data, plus lastLocation is not making a location request, so there is good chance there is no location data. What you need is requestLocationUpdate method, so that it can retrieve active location of the user. Also you will have better luck on a physical device for last Location.
Hi i have permission check on my forYou fragment when the app starts start and you give the permission and it makes a api call and get the weather for you current location the problem is I must switch the fragment for getting the call and switch again for showing it in the reclyerview the problem is when I give the permission for the location it doesn't do the api after that, I must refresh the fragment
fusedLocationClient.lastLocation.addOnSuccessListener(requireActivity()) { location ->
if (location != null) {
currentLocation = location
val currentLatLng = LatLng(location.latitude, location.longitude)
val currentAddress = getAddress(location.latitude, location.longitude)
Log.d("TAG", "klagenfurt : ${currentAddress}")
retrofitOneCallResponse(location.latitude, location.longitude, currentAddress)
dataService.getFavorites(this)
}
}
if (ActivityCompat.checkSelfPermission(
requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
MapViewFragment.LOCATION_PERMISSION_REQUEST_CODE
)
return
}
enter image description here
HERE it says and you write the code without ActivityCompat on the requestpermisson line it doesn't work etiher
Create one method like this.
private fun getLocation(){
fusedLocationClient.lastLocation.addOnSuccessListener(requireActivity()) { location ->
if (location != null) {
currentLocation = location
val currentLatLng = LatLng(location.latitude, location.longitude)
val currentAddress = getAddress(location.latitude, location.longitude)
Log.d("TAG", "klagenfurt : ${currentAddress}")
retrofitOneCallResponse(location.latitude, location.longitude, currentAddress)
dataService.getFavorites(this)
}else{
getLocation()
}
}
}
then from here
if (ActivityCompat.checkSelfPermission(
requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
requestPermissions(
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
MapViewFragment.LOCATION_PERMISSION_REQUEST_CODE
)
}else{
getLocation()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Unit {
if(requestCode==MapViewFragment.LOCATION_PERMISSION_REQUEST_CODE){
if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
getLocation();
}
}
}
Also, override the onRequestPermissionsResult in the fragment. and check if permission granted the call getLocation().