1

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

RatherBeSailing
  • 261
  • 1
  • 11
  • 1
    why you have registered you MainActivity as service in AndroidManifest? – Bhavin Sep 08 '21 at 04:42
  • You definitely cannot have an `Activity` and a `Service` with the same name. – David Wasser Sep 08 '21 at 13:35
  • @DavidWasser the only reason that I tried adding a is because Google tells me to access Location in API 29 and up I MUST have one (code runs fine in API 28 without it). When is added to the manifest it demands a classname - but as the code shows I don't use a separate GPS Class. The only class in the whole App is the MainActivity so that is why I used "MainActivity" answering Bhavin's question. I am happy to try any and all suggestions to get the code running – RatherBeSailing Sep 09 '21 at 04:52
  • Yeah, that doesn't work. Why do you say it isn't working? What exactly are you seeing? There should be no reason why you can't get locations in an `Activity` if it is running in the foreground. – David Wasser Sep 09 '21 at 09:06
  • Also, there is no requirement to have a `Service` to access location in API >= 29. I don't know where you are getting that idea. – David Wasser Sep 09 '21 at 09:41
  • The idea comes from Google's documentation which states as I TRY to explain why the code runs just fine in API 28 and fails in API 30. – RatherBeSailing Sep 09 '21 at 23:40
  • Trying other people's code I can see none include the . SOME of these work and some don't (including several examples provided by Google). The one thing in common is when they don't work the error starts with "W/GooglePlayServicesUtil requires the Google Play Store, but it is missing" immediately followed by an GoogleApiManager: The service for com.google.android.gms.common.internal.service.zar is not available. Clearly it is because some examples run and some don't. No progress with GoogleApi as they are no longer supported – RatherBeSailing Sep 09 '21 at 23:47
  • Please don't put code or log messages in comments. They don't format and are hard to read. You can edit your question and add any relevant things there. Please explain what is happening and attach the logcat (don't filter it or you might miss something important). – David Wasser Sep 11 '21 at 20:20

0 Answers0