views:

935

answers:

3

I am writing an app that requires the user's current location (lastknownlocation won't be very helpful) and displays a list of all the closest "items" to them taken from the database.

I have got the finding of the closest items working well but only using a hardcoded latitude and longitude location for the time being but now it is time to implement finding the actual location.

Can anyone provide an example of how to have the app find the current fine location in the background and have the app wait. I only need the location once not updates as the person moves. I have implemented a location listener however I understand getting a GPS fix can be slow. I have seen other examples using last known location or implementing the location listener which continues to run and update however as my Activity needs the location coordinates before it can display anything the app just crashes. I need to show a Progress Dialog while its searching.

How can I have the locationlistener run once in the background and then remove location updates once it finds the location. I need the rest of the application to wait until it has a GPS location or timeout after say 20-30seconds. I would like a ProgressDialog up so the user knows that something is going on, but it just has to be the spinning loading animation not a percentage or anything. If possible I would like the user to be able to cancel the dialog if they are sick of waiting which then they can search by typing suburb etc instead.

I have been trying to do it with threads but it is getting way more complicated than I feel it should be and still not working anyway. On iPhone this is much more simple?

Can anyone provide a nice way of doing this, I have been ripping my hair out for a week on this and it is really putting me behind schedule for the rest of the app completion date.

In summary I want to:
* Find current coordinates
* Show Progress Dialog while getting location
* Have app be able to wait for successful location or give up after a certain time?
* If Successful: Dismiss Progress Dialog and use coordinates to run my other methods for finding items closeby.
* If Failed to get location: Dismiss Progress Dialog and show error message, and encourage user to use menu to go to Search Activity.
* Use these coordinates if the user chooses map view from the menu, to show current location on the map and pins dropped at all the nearby items by using their coordinates from the database.

Thanks guys, I hope I have explained myself well enough. Any questions just ask, I will be checking back regularly as I am keen to get this part completed. Cheers

EDIT
Code as requested

locationList activity file

protected void onStart()
{
    super.onStart();

    // ....

    new LocationControl().execute(this);
}


private class LocationControl extends AsyncTask<Context, Void, Void>
{
    private final ProgressDialog dialog = new ProgressDialog(branchAtmList.this);

    protected void onPreExecute()
    {
        this.dialog.setMessage("Determining your location...");
        this.dialog.show();
    }

    protected Void doInBackground(Context... params)
    {
        LocationHelper location = new LocationHelper();

        location.getLocation(params[0], locationResult);

        return null;
    }

    protected void onPostExecute(final Void unused)
    {
        if(this.dialog.isShowing())
        {
            this.dialog.dismiss();
        }

        useLocation(); //does the stuff that requires current location
    }

}

public LocationResult locationResult = new LocationResult()
{
    @Override
    public void gotLocation(final Location location)
    {
        currentLocation = location;
    }
};  

LocationHelper class

    package org.stgeorge.bank.utils;

import java.util.Timer;
/*
imports
*/

public class LocationHelper
{
    LocationManager locationManager;
    private LocationResult locationResult;
    boolean gpsEnabled = false;
    boolean networkEnabled = false;

    public boolean getLocation(Context context, LocationResult result)
    {       
        locationResult = result;

        if(locationManager == null)
        {
            locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
        }
            //exceptions thrown if provider not enabled
            try
            {
                gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            }
            catch (Exception ex) {}
            try
            {
                networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
            }
            catch (Exception ex) {}

            //dont start listeners if no provider is enabled
            if(!gpsEnabled && !networkEnabled)
            {
                return false;
            }

            if(gpsEnabled)
            {
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);
            }
            if(networkEnabled)
            {
                locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
            }


            GetLastLocation();
            return true;
    }

    LocationListener locationListenerGps = new LocationListener() {
        public void onLocationChanged(Location location)
        {
            locationResult.gotLocation(location);
            locationManager.removeUpdates(this);
            locationManager.removeUpdates(locationListenerNetwork);

        }
        public void onProviderDisabled(String provider) {}
        public void onProviderEnabled(String provider) {}
        public void onStatusChanged(String provider, int status, Bundle extra) {}
    };

    LocationListener locationListenerNetwork = new LocationListener() {
        public void onLocationChanged(Location location)
        {
            locationResult.gotLocation(location);
            locationManager.removeUpdates(this);
            locationManager.removeUpdates(locationListenerGps);

        }
        public void onProviderDisabled(String provider) {}
        public void onProviderEnabled(String provider) {}
        public void onStatusChanged(String provider, int status, Bundle extra) {}

    };

    private void GetLastLocation()
    {
            locationManager.removeUpdates(locationListenerGps);
            locationManager.removeUpdates(locationListenerNetwork);

            Location gpsLocation = null;
            Location networkLocation = null;

            if(gpsEnabled)
            {
                gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            }
            if(networkEnabled)
            {
                networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
            }

            //if there are both values use the latest one
            if(gpsLocation != null && networkLocation != null)
            {
                if(gpsLocation.getTime() > networkLocation.getTime())
                {
                    locationResult.gotLocation(gpsLocation);
                }
                else
                {
                    locationResult.gotLocation(networkLocation);
                }

                return;
            }

            if(gpsLocation != null)
            {
                locationResult.gotLocation(gpsLocation);
                return;
            }

            if(networkLocation != null)
            {
                locationResult.gotLocation(networkLocation);
                return;
            }

            locationResult.gotLocation(null);
    }

    public static abstract class LocationResult
    {
        public abstract void gotLocation(Location location);
    }
}

This really seems like a big effort just to get the current location once? I do not require updates or anything? Is there not a simpler way of having the application wait for a result? I have been stuck on this for so long.

+1  A: 

Use an AsyncTask and use both network_provider as well as gps_provider, which means two listeners. gps takes longer to get a fix, maybe sometimes a minute and only works outdoors, while network gets a location pretty fast.

A good code example is here: http://stackoverflow.com/questions/3145089/what-is-the-simplest-and-most-robust-way-to-get-the-users-current-location-in-an/3145655#3145655

For AsyncTask, look http://developer.android.com/reference/android/os/AsyncTask.html

There are also many code example here on SO for it, for example here: http://stackoverflow.com/questions/3430987/android-show-a-dialog-until-a-thread-is-done-and-then-continue-with-the-program/3431028#3431028

EDIT: code concept:

add class variable

boolean hasLocation = false;

call in onCreate (not in AsyncTask):

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

then in locationListener.onLocationChanged, change the value once a location is found:

boolean hasLocation = false;

AsyncTask: leave as it is, but don't call

LocationHelper location = new LocationHelper();
location.getLocation(params[0], locationResult);

there, instead do

Long t = Calendar.getInstance().getTimeInMillies(); 
while (!hasLocation && Calendar.getInstance().getTimeInMillies()-t<30000)) {
    Thread.Sleep(1000); 
};    

probably with a delay in between would be sufficient.

Mathias Lin
Thanks this looks helpful. I thought GPS API stuff was asynchronous anyway? Or do I still need to implement this in order to have the app wait for a result? Based on those examples:So would I call myLocation.getLocation(this, locationResult) from within doInBackground(final String... args)?I just don't really understand when onPostExecute(final Void unused) would be called as the end result of the MyLocation stuff is gotLocation(final Location location)? also what happens if the timer runs out instead?
Daniel Bowden
yes the gps stuff is async, but showing a progress isn't. therefore you need to wrap it in an AsyncTask, since the progress dialog depends on your gps status/results.onPostExecute and onPreExecute run on the UI main thread, therefore you update your UI in there, i.e. once you got a gps fix for example, and also dismiss the progress dialog in there.
Mathias Lin
Ok thanks I will give this a go.How does onPostExecute know when I have a gps fix or when it is finished executing? ie how does it know when either gotLocation() is called or the timer runs out?
Daniel Bowden
for a GPS fix (not network) you can use a GpsStatus.Listener, although I experienced some issue with it, but maybe it's because I was indoor... http://stackoverflow.com/questions/3287389/gpsstatuslistener-no-satellites-used-in-fix-although-status-is-gpsstatus-gps-eve
Mathias Lin
Have tried using AsyncTask today and am having problems.from doInBackground I call LocationHelper location = new LocationHelper(); and location.getLocation(params[0], locationResult);When this gets to the line:locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);It throws an exception:ERROR/AndroidRuntime(6534): Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()Help please?
Daniel Bowden
please post all relevant parts of the code somewhere, pastebin or in the initial question, to take a look. Not sure why you'd need Looper.prepare - are you running a thread somewhere beside the AsyncTask? You could add that line (Looper.prepare()) and see if it works. But let's see the code first.
Mathias Lin
Added code. Thanks
Daniel Bowden
I asssume some thread stuff is automatically happening in the requestLocationUpdates which is where the exception occurs.This seems like a very complex way of doing what I want? Does it have to be this hard?
Daniel Bowden
don't call the requestLocationUpdates from within the AsyncTask/background. See the code concept I posted above (call it onCreate or onResume) - this way should work. Yes, you could probably simplify some code - refer to my code concept above.
Mathias Lin
Ok I'll give that a go. One question, if I'm replacing LocationHelper call with the while loop when do I call location.getLocation()?
Daniel Bowden
Ignore my last question I have got it working now. How would I limit the search for location to say 30 seconds then timeout? Otherwise the progress dialog is going to spin forever.
Daniel Bowden
see edited code above: Long t = Calendar.getInstance().getTimeInMillies(); while (!hasLocation || Calendar.getInstance().getTimeInMillies()-t<30000)) {};
Mathias Lin
Working good for me so far, thanks! Not quite sure how long to keep my timeout at as getting a gps or network location seems so fickle on my device but I'll keep testing values til I'm happy. Marked as Accepted. :)
Daniel Bowden
Hey Daniel, can you post the entire code after all? thx
Philipz
Thank you, i managed to get it working! thx Mathias and Daniel :D
Philipz