diff --git a/docs/html/training/location/display-address.jd b/docs/html/training/location/display-address.jd index 621b08224329b..516f14f06781d 100644 --- a/docs/html/training/location/display-address.jd +++ b/docs/html/training/location/display-address.jd @@ -1,280 +1,468 @@ page.title=Displaying a Location Address - trainingnavtop=true - @jd:body - -
The lessons Getting the Last Known + Location and Receiving Location + Updates describe how to get the user's location in the form of a + {@link android.location.Location} object that contains latitude and longitude + coordinates. Although latitude and longitude are useful for calculating + distance or displaying a map position, in many cases the address of the + location is more useful. For example, if you want to let your users know where + they are or what is close by, a street address is more meaningful than the + geographic coordinates (latitude/longitude) of the location.
+ +Using the {@link android.location.Geocoder} class in the Android framework + location APIs, you can convert an address to the corresponding geographic + coordinates. This process is called geocoding. Alternatively, you can + convert a geographic location to an address. The address lookup feature is + also known as reverse geocoding.
+ +This lesson shows you how to use the + {@link android.location.Geocoder#getFromLocation getFromLocation()} method to + convert a geographic location to an address. The method returns an estimated + street address corresponding to a given latitude and longitude.
+ +The last known location of the device is a useful starting point for the + address lookup feature. The lesson on + Getting the Last Known Location shows you + how to use the + {@code getLastLocation()} + method provided by the + fused + location provider to find the latest location of the device.
+ +To access the fused location provider, you need to create an instance of the + Google Play services API client. To learn how to connect your client, see + Connect + to Google Play Services.
+ +In order for the fused location provider to retrieve a precise street + address, set the location permission in your app manifest to + {@code ACCESS_FINE_LOCATION}, as shown in the following example:
-- The lessons Retrieving the Current Location and - Receiving Location Updates describe how to get the - user's current location in the form of a {@link android.location.Location} object that - contains latitude and longitude coordinates. Although latitude and longitude are useful for - calculating distance or displaying a map position, in many cases the address of the location is - more useful. -
-- The Android platform API provides a feature that returns an estimated street addresses for - latitude and longitude values. This lesson shows you how to use this address lookup feature. -
-- Note: Address lookup requires a backend service that is not included in the - core Android framework. If this backend service is not available, - {@link android.location.Geocoder#getFromLocation Geocoder.getFromLocation()} returns an empty - list. The helper method {@link android.location.Geocoder#isPresent isPresent()}, available - in API level 9 and later, checks to see if the backend service is available. -
-- The snippets in the following sections assume that your app has already retrieved the - current location and stored it as a {@link android.location.Location} object in the global - variable {@code mLocation}. -
- --To get an address for a given latitude and longitude, call -{@link android.location.Geocoder#getFromLocation Geocoder.getFromLocation()}, which returns a -list of addresses. The method is synchronous, and may take a long time to do its work, so you -should call the method from the {@link android.os.AsyncTask#doInBackground -doInBackground()} method of an {@link android.os.AsyncTask}. -
--While your app is getting the address, display an indeterminate activity -indicator to show that your app is working in the background. Set the indicator's initial state -to {@code android:visibility="gone"}, to make it invisible and remove it from the layout -hierarchy. When you start the address lookup, you set its visibility to "visible". -
--The following snippet shows how to add an indeterminate {@link android.widget.ProgressBar} to -your layout file: -
-<ProgressBar -android:id="@+id/address_progress" -android:layout_width="wrap_content" -android:layout_height="wrap_content" -android:layout_centerHorizontal="true" -android:indeterminate="true" -android:visibility="gone" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.gms.location.sample.locationupdates" > + + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> +</manifest>-
-To create the background task, define a subclass of {@link android.os.AsyncTask} that calls -{@link android.location.Geocoder#getFromLocation getFromLocation()} and returns an address. -Define a {@link android.widget.TextView} object {@code mAddress} to contain the returned -address, and a {@link android.widget.ProgressBar} object that allows you to control the -indeterminate activity indicator. For example: -
+ +The {@link android.location.Geocoder#getFromLocation getFromLocation()} + method provided by the {@link android.location.Geocoder} class accepts a + latitude and longitude, and returns a list of addresses. The method is + synchronous, and may take a long time to do its work, so you should not call + it from the main, user interface (UI) thread of your app.
+ +The {@link android.app.IntentService IntentService} class provides a + structure for running a task on a background thread. Using this class, you can + handle a long-running operation without affecting your UI's responsiveness. + Note that the {@link android.os.AsyncTask AsyncTask} class also allows you to + perform background operations, but it's designed for short operations. An + {@link android.os.AsyncTask AsyncTask} shouldn't keep a reference to the UI if + the activity is recreated, for example when the device is rotated. In + contrast, an {@link android.app.IntentService IntentService} doesn't need to + be cancelled when the activity is rebuilt.
+ +Define a {@code FetchAddressIntentService} class that extends + {@link android.app.IntentService}. This class is your address lookup service. + The intent service handles an intent asynchronously on a worker thread, and + stops itself when it runs out of work. The intent extras provide the data + needed by the service, including a {@link android.location.Location} object + for conversion to an address, and a {@link android.os.ResultReceiver} object + to handle the results of the address lookup. The service uses a {@link + android.location.Geocoder} to fetch the address for the location, and sends + the results to the {@link android.os.ResultReceiver}.
+ +Add an entry to your app manifest defining the intent service:
+
-public class MainActivity extends FragmentActivity {
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.gms.location.sample.locationaddress" >
+ <application
+ ...
+ <service
+ android:name=".FetchAddressIntentService"
+ android:exported="false"/>
+ </application>
...
- private TextView mAddress;
- private ProgressBar mActivityIndicator;
+</manifest>
+
+
+Note: The {@code <service>} element in + the manifest doesn't need to include an intent filter, because your main + activity creates an explicit intent by specifying the name of the class to use + for the intent.
+ +The process of converting a geographic location to an address is called + reverse geocoding. To perform the main work of the intent service, + that is, your reverse geocoding request, implement + {@link android.app.IntentService#onHandleIntent onHandleIntent()} within the + {@code FetchAddressIntentService} class. Create a + {@link android.location.Geocoder} object to handle the reverse geocoding.
+ +A locale represents a specific geographical or linguistic region. Locale + objects are used to adjust the presentation of information, such as numbers or + dates, to suit the conventions in the region represented by the locale. Pass a + {@code Locale} object + to the {@link android.location.Geocoder} object, to ensure that the resulting + address is localized to the user's geographic region.
+ +
+@Override
+protected void onHandleIntent(Intent intent) {
+ Geocoder geocoder = new Geocoder(this, Locale.getDefault());
+ ...
+}
+
+
+The next step is to retrieve the street address from the geocoder, handle + any errors that may occur, and send the results back to the activity that + requested the address. To report the results of the geocoding + process, you need two numeric constants that indicate success or failure. + Define a {@code Constants} class to contain the values, as shown in this code + snippet:
+ +
+public final class Constants {
+ public static final int SUCCESS_RESULT = 0;
+ public static final int FAILURE_RESULT = 1;
+ public static final String PACKAGE_NAME =
+ "com.google.android.gms.location.sample.locationaddress";
+ public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
+ public static final String RESULT_DATA_KEY = PACKAGE_NAME +
+ ".RESULT_DATA_KEY";
+ public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
+ ".LOCATION_DATA_EXTRA";
+}
+
+
+To get a street address corresponding to a geographical location, call + {@link android.location.Geocoder#getFromLocation getFromLocation()}, + passing it the latitude and longitude from the location object, and the + maximum number of addresses you want returned. In this case, you want just one + address. The geocoder returns an array of addresses. If no addresses were + found to match the given location, it returns an empty list. If there is no + backend geocoding service available, the geocoder returns null.
+ +Check for the following errors as shown in the code sample below. If an error + occurs, place the corresponding error message in the {@code errorMessage} + variable, so you can send it back to the requesting activity:
+ +To get the individual lines of an address object, use the + {@link android.location.Address#getAddressLine getAddressLine()} + method provided by the {@link android.location.Address} class. Then join the + lines into a list of address fragments ready to return to the activity that + requested the address.
+ +To send the results back to the requesting activity, call the + {@code deliverResultToReceiver()} method (defined in + Return the address to the requestor). The + results consist of the previously-mentioned numeric success/failure code and + a string. In the case of a successful reverse geocoding, the string contains + the address. In the case of a failure, the string contains the error message, + as shown in the code sample below:
+ +
+@Override
+protected void onHandleIntent(Intent intent) {
+ String errorMessage = "";
+
+ // Get the location passed to this service through an extra.
+ Location location = intent.getParcelableExtra(
+ Constants.LOCATION_DATA_EXTRA);
+
+ ...
+
+ List<Address> addresses = null;
+
+ try {
+ addresses = geocoder.getFromLocation(
+ location.getLatitude(),
+ location.getLongitude(),
+ // In this sample, get just a single address.
+ 1);
+ } catch (IOException ioException) {
+ // Catch network or other I/O problems.
+ errorMessage = getString(R.string.service_not_available);
+ Log.e(TAG, errorMessage, ioException);
+ } catch (IllegalArgumentException illegalArgumentException) {
+ // Catch invalid latitude or longitude values.
+ errorMessage = getString(R.string.invalid_lat_long_used);
+ Log.e(TAG, errorMessage + ". " +
+ "Latitude = " + location.getLatitude() +
+ ", Longitude = " +
+ location.getLongitude(), illegalArgumentException);
+ }
+
+ // Handle case where no address was found.
+ if (addresses == null || addresses.size() == 0) {
+ if (errorMessage.isEmpty()) {
+ errorMessage = getString(R.string.no_address_found);
+ Log.e(TAG, errorMessage);
+ }
+ deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
+ } else {
+ Address address = addresses.get(0);
+ ArrayList<String> addressFragments = new ArrayList<String>();
+
+ // Fetch the address lines using {@code getAddressLine},
+ // join them, and send them to the thread.
+ for(int i = 0; i < address.getMaxAddressLineIndex(); i++) {
+ addressFragments.add(address.getAddressLine(i));
+ }
+ Log.i(TAG, getString(R.string.address_found));
+ deliverResultToReceiver(Constants.SUCCESS_RESULT,
+ TextUtils.join(System.getProperty("line.separator"),
+ addressFragments));
+ }
+}
+
+
+The final thing the intent service must do is send the address back to a + {@link android.os.ResultReceiver} in the activity that started the service. + The {@link android.os.ResultReceiver} class allows you to send a + numeric result code as well as a message containing the result data. The + numeric code is useful for reporting the success or failure of the geocoding + request. In the case of a successful reverse geocoding, the message contains + the address. In the case of a failure, the message contains some text + describing the reason for failure.
+ +You have already retrieved the address from the geocoder, trapped any errors + that may occur, and called the {@code deliverResultToReceiver()} method. Now + you need to define the {@code deliverResultToReceiver()} method that sends + a result code and message bundle to the result receiver.
+ +For the result code, use the value that you've passed to the + {@code deliverResultToReceiver()} method in the {@code resultCode} parameter. + To construct the message bundle, concatenate the {@code RESULT_DATA_KEY} + constant from your {@code Constants} class (defined in + Retrieve the street address data) and + the value in the {@code message} parameter passed to the + {@code deliverResultToReceiver()} method, as shown in the following sample: +
+ +
+public class FetchAddressIntentService extends IntentService {
+ protected ResultReceiver mReceiver;
+ ...
+ private void deliverResultToReceiver(int resultCode, String message) {
+ Bundle bundle = new Bundle();
+ bundle.putString(Constants.RESULT_DATA_KEY, message);
+ mReceiver.send(resultCode, bundle);
+ }
+}
+
+
+The intent service, as defined in the previous section, runs in the + background and is responsible for fetching the address corresponding to a + given geographic location. When you start the service, the Android framework + instantiates and starts the service if it isn't already running, and creates a + process if needed. If the service is already running then it remains running. + Because the service extends {@link android.app.IntentService IntentService}, + it shuts down automatically when all intents have been processed.
+ +Start the service from your app's main activity, + and create an {@link android.content.Intent} to pass data to the service. You + need an explicit intent, because you want only your service + to respond to the intent. For more information, see + Intent + Types.
+ +To create an explicit intent, specify the name of the + class to use for the service: {@code FetchAddressIntentService.class}. + Pass two pieces of information in the intent extras:
+ +The following code sample shows you how to start the intent service:
+ +
+public class MainActivity extends ActionBarActivity implements
+ ConnectionCallbacks, OnConnectionFailedListener {
+
+ protected Location mLastLocation;
+ private AddressResultReceiver mResultReceiver;
+ ...
+
+ protected void startIntentService() {
+ Intent intent = new Intent(this, FetchAddressIntentService.class);
+ intent.putExtra(Constants.RECEIVER, mResultReceiver);
+ intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation);
+ startService(intent);
+ }
+}
+
+
+Call the above {@code startIntentService()} method when the + user takes an action that requires a geocoding address lookup. For example, + the user may press a Fetch address button on your app's UI. Before + starting the intent service, you need to check that the connection to Google + Play services is present. The following code snippet shows the call to the + {@code startIntentService()} method in the button handler:
+ +
+public void fetchAddressButtonHandler(View view) {
+ // Only start the service to fetch the address if GoogleApiClient is
+ // connected.
+ if (mGoogleApiClient.isConnected() && mLastLocation != null) {
+ startIntentService();
+ }
+ // If GoogleApiClient isn't connected, process the user's request by
+ // setting mAddressRequested to true. Later, when GoogleApiClient connects,
+ // launch the service to fetch the address. As far as the user is
+ // concerned, pressing the Fetch Address button
+ // immediately kicks off the process of getting the address.
+ mAddressRequested = true;
+ updateUIWidgets();
+}
+
+
+You must also start the intent service when the connection to Google Play + services is established, if the user has already clicked the button on your + app's UI. The following code snippet shows the call to the + {@code startIntentService()} method in the + {@code onConnected()} + callback provided by the Google API Client:
+ +
+public class MainActivity extends ActionBarActivity implements
+ ConnectionCallbacks, OnConnectionFailedListener {
...
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ...
- mAddress = (TextView) findViewById(R.id.address);
- mActivityIndicator =
- (ProgressBar) findViewById(R.id.address_progress);
- }
- ...
- /**
- * A subclass of AsyncTask that calls getFromLocation() in the
- * background. The class definition has these generic types:
- * Location - A {@link android.location.Location} object containing
- * the current location.
- * Void - indicates that progress units are not used
- * String - An address passed to onPostExecute()
- */
- private class GetAddressTask extends
- AsyncTask<Location, Void, String> {
- Context mContext;
- public GetAddressTask(Context context) {
- super();
- mContext = context;
- }
- ...
- /**
- * Get a Geocoder instance, get the latitude and longitude
- * look up the address, and return it
- *
- * @params params One or more Location objects
- * @return A string containing the address of the current
- * location, or an empty string if no address can be found,
- * or an error message
- */
- @Override
- protected String doInBackground(Location... params) {
- Geocoder geocoder =
- new Geocoder(mContext, Locale.getDefault());
- // Get the current location from the input parameter list
- Location loc = params[0];
- // Create a list to contain the result address
- List<Address> addresses = null;
- try {
- /*
- * Return 1 address.
- */
- addresses = geocoder.getFromLocation(loc.getLatitude(),
- loc.getLongitude(), 1);
- } catch (IOException e1) {
- Log.e("LocationSampleActivity",
- "IO Exception in getFromLocation()");
- e1.printStackTrace();
- return ("IO Exception trying to get address");
- } catch (IllegalArgumentException e2) {
- // Error message to post in the log
- String errorString = "Illegal arguments " +
- Double.toString(loc.getLatitude()) +
- " , " +
- Double.toString(loc.getLongitude()) +
- " passed to address service";
- Log.e("LocationSampleActivity", errorString);
- e2.printStackTrace();
- return errorString;
+ public void onConnected(Bundle connectionHint) {
+ // Gets the best and most recent location currently available,
+ // which may be null in rare cases when a location is not available.
+ mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
+ mGoogleApiClient);
+
+ if (mLastLocation != null) {
+ // Determine whether a Geocoder is available.
+ if (!Geocoder.isPresent()) {
+ Toast.makeText(this, R.string.no_geocoder_available,
+ Toast.LENGTH_LONG).show();
+ return;
}
- // If the reverse geocode returned an address
- if (addresses != null && addresses.size() > 0) {
- // Get the first address
- Address address = addresses.get(0);
- /*
- * Format the first line of address (if available),
- * city, and country name.
- */
- String addressText = String.format(
- "%s, %s, %s",
- // If there's a street address, add it
- address.getMaxAddressLineIndex() > 0 ?
- address.getAddressLine(0) : "",
- // Locality is usually a city
- address.getLocality(),
- // The country of the address
- address.getCountryName());
- // Return the text
- return addressText;
- } else {
- return "No address found";
+
+ if (mAddressRequested) {
+ startIntentService();
}
}
- ...
}
- ...
}
--The next section shows you how to display the address in the user interface. -
- -- {@link android.os.AsyncTask#doInBackground doInBackground()} returns the result of the address - lookup as a {@link java.lang.String}. This value is passed to - {@link android.os.AsyncTask#onPostExecute onPostExecute()}, where you do further processing - on the results. Since {@link android.os.AsyncTask#onPostExecute onPostExecute()} - runs on the UI thread, it can update the user interface; for example, it can turn off the - activity indicator and display the results to the user: -
+ +The intent service has handled the geocoding request, and uses a + {@link android.os.ResultReceiver} to return the results to the activity that + made the request. In the activity that makes the request, define an + {@code AddressResultReceiver} that extends {@link android.os.ResultReceiver} + to handle the response from {@code FetchAddressIntentService}.
+ +The result includes a numeric result code (resultCode) as well
+ as a message containing the result data (resultData). If the
+ reverse geocoding process was successful, the resultData contains
+ the address. In the case of a failure, the resultData contains
+ text describing the reason for failure. For details of the possible errors,
+ see Return the address to the requestor.
Override the + {@link android.os.ResultReceiver#onReceiveResult onReceiveResult()} method + to handle the results delivered to the result receiver, as shown in the + following code sample:
+
- private class GetAddressTask extends
- AsyncTask<Location, Void, String> {
- ...
- /**
- * A method that's called once doInBackground() completes. Turn
- * off the indeterminate activity indicator and set
- * the text of the UI element that shows the address. If the
- * lookup failed, display the error message.
- */
+public class MainActivity extends ActionBarActivity implements
+ ConnectionCallbacks, OnConnectionFailedListener {
+ ...
+ class AddressResultReceiver extends ResultReceiver {
+ public AddressResultReceiver(Handler handler) {
+ super(handler);
+ }
+
@Override
- protected void onPostExecute(String address) {
- // Set activity indicator visibility to "gone"
- mActivityIndicator.setVisibility(View.GONE);
- // Display the results of the lookup.
- mAddress.setText(address);
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+
+ // Display the address string
+ // or an error message sent from the intent service.
+ mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
+ displayAddressOutput();
+
+ // Show a toast message if an address was found.
+ if (resultCode == Constants.SUCCESS_RESULT) {
+ showToast(getString(R.string.address_found));
+ }
+
}
- ...
}
-
-- The final step is to run the address lookup. -
- -- To get the address, call {@link android.os.AsyncTask#execute execute()}. For example, the - following snippet starts the address lookup when the user clicks the "Get Address" button: -
-
-public class MainActivity extends FragmentActivity {
- ...
- /**
- * The "Get Address" button in the UI is defined with
- * android:onClick="getAddress". The method is invoked whenever the
- * user clicks the button.
- *
- * @param v The view object associated with this method,
- * in this case a Button.
- */
- public void getAddress(View v) {
- // Ensure that a Geocoder services is available
- if (Build.VERSION.SDK_INT >=
- Build.VERSION_CODES.GINGERBREAD
- &&
- Geocoder.isPresent()) {
- // Show the activity indicator
- mActivityIndicator.setVisibility(View.VISIBLE);
- /*
- * Reverse geocoding is long-running and synchronous.
- * Run it on a background thread.
- * Pass the current location to the background task.
- * When the task finishes,
- * onPostExecute() displays the address.
- */
- (new GetAddressTask(this)).execute(mLocation);
- }
- ...
- }
- ...
}
-- The next lesson, Creating and Monitoring Geofences, demonstrates - how to define locations of interest called geofences and how to use geofence monitoring - to detect the user's proximity to a location of interest. -