2

My app displays a navigation chart, and if a current GPS position is available, it shows that too. I tested this on a ship moving along a known route. Sometimes the correct position is shown. Sometimes I get an old GPS location, even when asking for a fresh read. At times it seems to get stuck on an old location for several reads. When that happened, I ran Google Maps, it seems to clear the error and I start getting a fresh location again.

// try to look at GPS....
        status1 = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
        if (status1 != PackageManager.PERMISSION_GRANTED)
        {   requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},10);
        }
        status2 = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
        Object cancellationToken = null;
        if (status2 == PackageManager.PERMISSION_GRANTED)
            fusedLocationClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY, (CancellationToken) cancellationToken)
                    .addOnSuccessListener(this, location -> // using lambda expression...

                    {   if (location != null)
                    {   GPSLatitude = location.getLatitude();
                        GPSLongitude = location.getLongitude();
                        gotGPS = true;
                        //if we have a location, and it is on chart, draw the blue dot....

Google maps is better at reading the GPS than I am. Should I consider another approach.

user1644002
  • 3,211
  • 3
  • 19
  • 21

4 Answers4

9

Welcome to the club of people who have suffered at the hands of google's FusedLocationProvider, I suggest you instead use LocationManager with GPS provider. which can be used to request location in your Activity as

fun getLocation() {
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
        ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
         // do proper permissions handling
         return
    }
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
    val gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
    if (gpsEnabled) {
        // Register for location updates
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0f, locationListener)
            
        // Or get last known location
        val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
    }
}

private val locationListener = LocationListener {
        // Do something with the location
}

sometimes FusedLocationProvider can provide inaccurate location

In some situations I have found the FusedLocationProvider to be rather inaccurate, in a recent experience which was no different than yours, I couldn't get a GPS lock using FusedLocationProvider in my app and when I opened google maps the GPS lock pin appeared instantly. so I did some research and sure enough I found the fault with FusedLocationProvider and I ended up using LocationManger, as to why it behaves this way some of the points that I came across are

  1. its Fused, meaning it uses multiple sources(WiFi, network, GPS etc), it will choose the one that it deems fit, or a mix of all in deciding the location
  2. FusedLocationProvider's top selling point is that its optimized for battery usage, so as long as it can provide you with a location(generally accurate, not always), it won't hit the hardware if it determines the call would put unnecessary load on the battery
  3. It also seems to be doing location caching which can be a reason for outdated location fix, in the comments of this so question, a user shares their experience of this case

Some of the links to issues caused by FusedLocationProvider

How FusedLocationProvider affected location accuracy of a data collection app which was earlier using LocationManger

FusedLocationProvider causing issues because of caching

Why LocationManager provides more accurate location

How FusedLocationProvider performs badly in some situations

How FusedLocationProvider provides inaccurate results, see the second highest voted answer

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46
  • 1
    thank you WOZ, I appreciate the effort and the history you are aware of. It is some very good reading, it explains some of the oddball values I've received (one location several miles off).Immediately, tomorrow anyway, I will back up my code then start over with LocationManager. – user1644002 Aug 17 '21 at 23:29
1

The problem is that you haven't used the recommended approach for an app that tracks the location of assets. And you've tried to update the location manually by the getCurrentLocation method. As matter of fact, the getCurrentLocation method is used when you won't need location updates constantly. For example, you only need just a single fresh location of a user, for registration, nearby search, etc.

When you need to track an asset constantly, you should listen for location updates by the requestLocationUpdates method. According to official recommendation

Appropriate use of location information can be beneficial to users of your app. For example, if your app helps the user find their way while walking or driving, or if your app tracks the location of assets, it needs to get the location of the device at regular intervals.

Here I've implemented a full example

package com.example.locationtracker;

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.LinearLayoutManager;

import com.example.locationtracker.databinding.ActivityMainBinding;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.material.snackbar.Snackbar;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {


    private boolean isTracking = false;
    private FusedLocationProviderClient fusedLocationClient;
    private LocationCallback locationCallback;
    private LocationRequest locationRequest;
    private LocationRecycleViewAdapter locationRecycleViewAdapter;
    private final ArrayList<String> locations = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        com.example.locationtracker.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        binding.location.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
        locationRecycleViewAdapter = new LocationRecycleViewAdapter(getApplicationContext(), locations);
        binding.location.setAdapter(locationRecycleViewAdapter);
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        locationRequest = LocationRequest.create();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setInterval(12000);
        locationRequest.setFastestInterval(2000);
        locationRequest.setWaitForAccurateLocation(true);
        locationRequest.setSmallestDisplacement(0);

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(@NonNull LocationResult locationResult) {
                for (Location location : locationResult.getLocations()) {
                    System.out.println("New Location Received");
                    String loc = String.format("lat: %s, lon: %s", location.getLatitude(), location.getLongitude());
                    locations.add(loc);
                    locationRecycleViewAdapter.notifyDataSetChanged();
                    /*locationRecycleViewAdapter = new LocationRecycleViewAdapter(getApplicationContext(), locations);
                    binding.location.setAdapter(locationRecycleViewAdapter);*/
                }
            }
        };
        binding.fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!isTracking) {
                    Snackbar.make(view, "Starting Location Tracking", Snackbar.LENGTH_LONG)
                            .show();
                    startLocationUpdates();

                } else {
                    Snackbar.make(view, "Stopping Location Tracking", Snackbar.LENGTH_LONG)
                            .show();
                    stopLocationUpdates();
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isTracking) {
            startLocationUpdates();
        }
    }

    private static final int PERMISSION_REQUEST_CODE = 200;

    private void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_CODE);
            return;
        }
        fusedLocationClient.requestLocationUpdates(locationRequest,
                locationCallback,
                Looper.getMainLooper());
        isTracking = true;
    }

    private void stopLocationUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback);
        isTracking = false;
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.locationtracker">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LocationTracker">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.LocationTracker.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Output

enter image description here

Important Notes

Make sure that you have both FINE and COARSE location permissions

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

COARSE Location and PRIORITY_HIGH_ACCURACY are VERY IMPORTANT if you want to retrieve the asset's location in the fastest interval. the below settings are very important as well.

locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(12000);
locationRequest.setFastestInterval(2000);
locationRequest.setWaitForAccurateLocation(true);
locationRequest.setSmallestDisplacement(0); 
A Farmanbar
  • 4,381
  • 5
  • 24
  • 42
  • I maybe misstated my requirement. I only want a new location when the map is first drawn, or after a manual refresh by the user, never more than every 10 or 20 seconds, usually never. A one time thing. So, When I ask for the GPS, that is when I want it valid and I will not be hammering on it soon. That is why asked the question like that. However, I am certain I can find useful stuff in your reply and I thank you. – user1644002 Aug 13 '21 at 23:41
  • @user1644002 your welcome. I recommend you even If location refresh intervals are large, use this approach and only change `locationRequest.setInterval` to the desired value. – A Farmanbar Aug 14 '21 at 00:04
0

I suggest try to get updated current with interval

        if (ActivityCompat.checkSelfPermission(mActivity, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 0, new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
             log.w("Get locations:: "+ location.getLatitude());
                newLocation(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) {

            }
        });
0

Here is the java code I have working now.

locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);


// try to look at GPS....
        status1 = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
        if (status1 != PackageManager.PERMISSION_GRANTED)
        {   requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},10);
        }
        status2 = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
        if (status2 == PackageManager.PERMISSION_GRANTED)
        {   gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            if (gpsEnabled) // Register for location updates, each minute (60,000 milliseconds)
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 60000, 0.0f, locationListenerGPS);
LocationListener locationListenerGPS=new LocationListener() {
        @Override
        public void onLocationChanged(android.location.Location location) {
            GPSLatitude = location.getLatitude();
            GPSLongitude = location.getLongitude();
            gotGPS = true;

            reDraw();

            //String msg="New Latitude: "+latitude + "New Longitude: "+longitude;
            //Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }

        @Override
        public void onProviderEnabled(String provider) {

        }

        @Override
        public void onProviderDisabled(String provider) {

        }
    };
///

based on Kotlin code from WOZ, it works well 
user1644002
  • 3,211
  • 3
  • 19
  • 21