Rehan Yousaf
Rehan Yousaf

Reputation: 245

Using Geocoder to retrieve address based on Latitude and Longitude causes lag. Android

I have a map activity which also has an EditText on top of it and I want to show the changing address in that EditText whenever there's a drag on the map. I am currently using Geocoder class to retrieve address based on the coordinates provided by the map. But this way is causing a lot of lag in the fluent dragging motion of the map. Is there a better way to retrieve address or a more efficient way to use Geocoder class? Right now, I have implemented the getAddress() method inside the OnCameraChangeListener event of my map. Here is the code.

mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition cameraPosition) {
                double latitude=mMap.getCameraPosition().target.latitude;
                double longitude=mMap.getCameraPosition().target.longitude;
                String _Location="";
                Geocoder geocoder = new Geocoder(getApplicationContext(), Locale.getDefault());
                try {
                    List<Address> listAddresses = geocoder.getFromLocation(latitude, longitude, 1);
                    if(null!=listAddresses&&listAddresses.size()>0){
                        _Location = listAddresses.get(0).getAddressLine(0);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                EditText addressTxt=(EditText) findViewById(R.id.search_address_txt);
                addressTxt.setText(_Location);
            }
        });

I have seen another app implemented where the address searched was at the end of the map drag and there was no lag in the map's motion.

Upvotes: 2

Views: 1155

Answers (1)

hello_world
hello_world

Reputation: 768

The Geocoder$getFromLocation() is a blocking call, which means it will freeze the UI thread till the api returns the address. The only solution is to run this call on a background thread.

There are two ways you can achieve this:

  1. Use Google recommended way - Using an Intent Service. You can find the details on how to here. Personally, I feel this is overkill and very complicated.
  2. Use an AsyncTask. See this to get a general idea and implement according to your needs.

Also, if you are using Rx, you can start a new observable from the geocoder and observe on a different thread and subscribe on the main thread.

UPDATE: I pulled this code from one of my old projects, see if it helps.

AsyncGeocoderObject.java // object to pass to asynctask

public class AsyncGeocoderObject {

    public Location location; // location to get address from
    Geocoder geocoder; // the geocoder
    TextView textView; // textview to update text

    public AsyncGeocoderObject(Geocoder geocoder, Location location, TextView textView) {
        this.geocoder = geocoder;
        this.location = location;
        this.textView = textView;
    }
}


The AsyncTask implementation

public class AsyncGeocoder extends AsyncTask<AsyncGeocoderObject, Void, List<Address>> {

private TextView textView;

@Override
protected List<Address> doInBackground(AsyncGeocoderObject... asyncGeocoderObjects) {
    List<Address> addresses = null;
    AsyncGeocoderObject asyncGeocoderObject = asyncGeocoderObjects[0];
    textView = asyncGeocoderObject.textView;
    try {
        addresses = asyncGeocoderObject.geocoder.getFromLocation(asyncGeocoderObject.location.getLatitude(),
                asyncGeocoderObject.location.getLongitude(), 1);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return addresses;
}

@Override
protected void onPostExecute(List<Address> addresses) {
    Log.v("onPostExecute", "location: " + addresses);
    String address;
    if (addresses != null)
        address = addresses.get(0).getLocality() + ", " + addresses.get(0).getCountryName();
    else address = "Service unavailable.";
    textView.setText(address);
}
}

Calling the method when location changes:

new AsyncGeocoder().execute(new AsyncGeocoderObject(
                new Geocoder(this),
                location,
                locationTV
        ));

Hope this helps!

UPDATE 2:
Notes on how to immplement the above:

  1. Create new class AsyncGeocoderObject and copy the contents
  2. Create another class (can be a sub-class within the activity or in a different java file) AsyncGeocoder and copy the contents.
  3. Now, say you want to get the users location in MainActivity and display the address in a textview, say locationTV. After instantiating the textview, create a new object of AsyncGeocoder and pass in the parameters. eg:

    // in onCreate()
    new AsyncGeocoder().execute(new AsyncGeocoderObject(
            new Geocoder(this), // the geocoder object to get address
            location, // location object, 
            locationTV // the textview to set address on
    )); 
    

    Also, if you dont have a location object, create on using the latitude and longitude you have. See this post for how to.

Hope this helps you!

Upvotes: 1

Related Questions