Home | History | Annotate | Download | only in location
      1 page.title=Displaying a Location Address
      2 trainingnavtop=true
      3 @jd:body
      4 
      5 <div id="tb-wrapper">
      6   <div id="tb">
      7 
      8     <h2>This lesson teaches you how to</h2>
      9     <ol>
     10       <li><a href="#connect">Get a geographic location</a></li>
     11       <li><a href="#fetch-address">Define an intent service to fetch the
     12         address</a></li>
     13       <li><a href="#start-intent">Start the intent service</a></li>
     14       <li><a href="#result-receiver">Receive the geocoding results</a></li>
     15     </ol>
     16 
     17     <h2>You should also read</h2>
     18       <ul>
     19         <li>
     20           <a href="{@docRoot}google/play-services/setup.html">Setting up Google
     21           Play Services</a>
     22         </li>
     23         <li>
     24           <a href="retrieve-current.html">Getting the Last Known Location</a>
     25         </li>
     26         <li>
     27           <a href="receive-location-updates.html">Receiving Location Updates</a>
     28         </li>
     29       </ul>
     30     <h2>Try it out</h2>
     31 
     32     <ul>
     33       <li>
     34         <a href="https://github.com/googlesamples/android-play-location/tree/master/LocationAddress" class="external-link">LocationAddress</a>
     35       </li>
     36     </ul>
     37   </div>
     38 </div>
     39 
     40 <p>The lessons <a href="retrieve-current.html">Getting the Last Known
     41   Location</a> and <a href="receive-location-updates.html">Receiving Location
     42   Updates</a> describe how to get the user's location in the form of a
     43   {@link android.location.Location} object that contains latitude and longitude
     44   coordinates. Although latitude and longitude are useful for calculating
     45   distance or displaying a map position, in many cases the address of the
     46   location is more useful. For example, if you want to let your users know where
     47   they are or what is close by, a street address is more meaningful than the
     48   geographic coordinates (latitude/longitude) of the location.</p>
     49 
     50 <p>Using the {@link android.location.Geocoder} class in the Android framework
     51   location APIs, you can convert an address to the corresponding geographic
     52   coordinates. This process is called <em>geocoding</em>. Alternatively, you can
     53   convert a geographic location to an address. The address lookup feature is
     54   also known as <em>reverse geocoding</em>.</p>
     55 
     56 <p>This lesson shows you how to use the
     57   {@link android.location.Geocoder#getFromLocation getFromLocation()} method to
     58   convert a geographic location to an address. The method returns an estimated
     59   street address corresponding to a given latitude and longitude.</p>
     60 
     61 <h2 id="connect">Get a geographic location</h2>
     62 
     63 <p>The last known location of the device is a useful starting point for the
     64   address lookup feature. The lesson on
     65   <a href="retrieve-current.html">Getting the Last Known Location</a> shows you
     66   how to use the
     67   <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)">{@code getLastLocation()}</a>
     68   method provided by the
     69   <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html">fused
     70   location provider</a> to find the latest location of the device.</p>
     71 
     72 <p>To access the fused location provider, create an instance of the
     73   Google Play services API client. To learn how to connect your client, see
     74   <a href="{@docRoot}training/location/retrieve-current.html#play-services">Connect
     75   to Google Play Services</a>.</p>
     76 
     77 <p>To enable the fused location provider to retrieve a precise street
     78   address, set the location permission in your app manifest to
     79   {@code ACCESS_FINE_LOCATION}, as shown in the following example:</p>
     80 
     81 <pre>
     82 &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
     83     package="com.google.android.gms.location.sample.locationupdates" &gt;
     84 
     85   &lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/&gt;
     86 &lt;/manifest&gt;
     87 </pre>
     88 
     89 <h2 id="fetch-address">Define an intent service to fetch the address</h2>
     90 
     91 <p>The {@link android.location.Geocoder#getFromLocation getFromLocation()}
     92   method provided by the {@link android.location.Geocoder} class accepts a
     93   latitude and longitude and returns a list of addresses. The method is
     94   synchronous and may take a long time to do its work, so you should not call
     95   it from the main, user interface (UI) thread of your app.</p>
     96 
     97 <p>The {@link android.app.IntentService IntentService} class provides a
     98   structure for running a task on a background thread. Using this class, you can
     99   handle a long-running operation without affecting your UI's responsiveness.
    100   Note that the {@link android.os.AsyncTask AsyncTask} class also allows you to
    101   perform background operations, but it's designed for short operations. An
    102   {@link android.os.AsyncTask AsyncTask} shouldn't keep a reference to the UI if
    103   the activity is re-created, such as when the device is rotated. In
    104   contrast, an {@link android.app.IntentService IntentService} doesn't need to
    105   be cancelled when the activity is rebuilt.</p>
    106 
    107 <p>Define a {@code FetchAddressIntentService} class that extends
    108   {@link android.app.IntentService}. This class is your address lookup service.
    109   The intent service handles an intent asynchronously on a worker thread and
    110   stops itself when it runs out of work. The intent extras provide the data
    111   needed by the service, including a {@link android.location.Location} object
    112   for conversion to an address and a {@link android.os.ResultReceiver} object
    113   to handle the results of the address lookup. The service uses a {@link
    114   android.location.Geocoder} to fetch the address for the location and sends
    115   the results to the {@link android.os.ResultReceiver}.</p>
    116 
    117 <h3>Define the intent service in your app manifest</h3>
    118 
    119 <p>Add an entry to your app manifest that defines the intent service, as shown here:</p>
    120 
    121 <pre>
    122 &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    123     package="com.google.android.gms.location.sample.locationaddress" &gt;
    124     &lt;application
    125         ...
    126         &lt;service
    127             android:name=".FetchAddressIntentService"
    128             android:exported="false"/&gt;
    129     &lt;/application&gt;
    130     ...
    131 &lt;/manifest&gt;
    132 </pre>
    133 
    134 <p class="note"><strong>Note:</strong> The {@code &lt;service&gt;} element in
    135   the manifest doesn't need to include an intent filter because your main
    136   activity creates an explicit intent by specifying the name of the class to use
    137   for the intent.</p>
    138 
    139 <h3>Create a geocoder</h3>
    140 
    141 <p>The process of converting a geographic location to an address is called
    142   <em>reverse geocoding</em>. To perform the main work of the intent service (your reverse
    143   geocoding request), implement
    144   {@link android.app.IntentService#onHandleIntent onHandleIntent()} within the
    145   {@code FetchAddressIntentService} class. Create a
    146   {@link android.location.Geocoder} object to handle the reverse geocoding.</p>
    147 
    148 <p>A locale represents a specific geographical or linguistic region. Locale
    149   objects adjust the presentation of information, such as numbers or
    150   dates, to suit the conventions in the region that is represented by the locale. Pass a
    151   <a href="{@docRoot}reference/java/util/Locale.html">{@code Locale}</a> object
    152   to the {@link android.location.Geocoder} object to ensure that the resulting
    153   address is localized to the user's geographic region. Here is an example:</p>
    154 
    155 <pre>
    156 &#64;Override
    157 protected void onHandleIntent(Intent intent) {
    158     Geocoder geocoder = new Geocoder(this, Locale.getDefault());
    159     ...
    160 }
    161 </pre>
    162 
    163 <h3 id="retrieve-street-address">Retrieve the street address data</h3>
    164 
    165 <p>You can now retrieve the street address from the geocoder, handle
    166   any errors that may occur, and send the results back to the activity that
    167   requested the address. To report the results of the geocoding
    168   process, you need two numeric constants that indicate success or failure.
    169   Define a {@code Constants} class to contain the values, as shown in this code
    170   snippet:</p>
    171 
    172 <pre>
    173 public final class Constants {
    174     public static final int SUCCESS_RESULT = 0;
    175     public static final int FAILURE_RESULT = 1;
    176     public static final String PACKAGE_NAME =
    177         "com.google.android.gms.location.sample.locationaddress";
    178     public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
    179     public static final String RESULT_DATA_KEY = PACKAGE_NAME +
    180         ".RESULT_DATA_KEY";
    181     public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
    182         ".LOCATION_DATA_EXTRA";
    183 }
    184 </pre>
    185 
    186 <p>To get a street address corresponding to a geographical location, call
    187   {@link android.location.Geocoder#getFromLocation getFromLocation()},
    188   passing it the latitude and longitude from the location object and the
    189   maximum number of addresses that you want returned. In this case, you want just one
    190   address. The geocoder returns an array of addresses. If no addresses are
    191   found to match the given location, it returns an empty list. If there is no
    192   backend geocoding service available, the geocoder returns null.</p>
    193 
    194 <p>Check for the following errors, as shown in the code sample below:</p>
    195 
    196 <ul>
    197   <li><strong>No location data provided</strong> &ndash; The intent extras do not
    198     include the {@link android.location.Location} object that is required for reverse
    199     geocoding.</li>
    200   <li><strong>Invalid latitude or longitude used</strong> &ndash; The latitude
    201     and/or longitude values that are provided in the {@link android.location.Location}
    202     object are invalid.</li>
    203   <li><strong>No geocoder available</strong> &ndash; The background geocoding service
    204     is not available due to a network error or IO exception.</li>
    205   <li><strong>Sorry, no address found</strong> &ndash; The geocoder can't find an
    206     address for the given latitude/longitude.</li>
    207 </ul>
    208 
    209 <p>If an error
    210   occurs, place the corresponding error message in the {@code errorMessage}
    211   variable so that you can send it back to the requesting activity.
    212 
    213 <p>To get the individual lines of an address object, use the
    214   {@link android.location.Address#getAddressLine getAddressLine()}
    215   method that is provided by the {@link android.location.Address} class. Join the
    216   lines into a list of address fragments ready to return to the activity that
    217   requested the address.</p>
    218 
    219 <p>To send the results back to the requesting activity, call the
    220   {@code deliverResultToReceiver()} method (defined in
    221   <a href="#return-address">Return the address to the requestor</a>). The
    222   results consist of the previously-mentioned numeric success/failure code and
    223   a string. In the case of a successful reverse geocoding, the string contains
    224   the address. In the case of a failure, the string contains the error message,
    225   as shown in this code sample:</p>
    226 
    227 <pre>
    228 &#64;Override
    229 protected void onHandleIntent(Intent intent) {
    230     String errorMessage = "";
    231 
    232     // Get the location passed to this service through an extra.
    233     Location location = intent.getParcelableExtra(
    234             Constants.LOCATION_DATA_EXTRA);
    235 
    236     ...
    237 
    238     List&lt;Address&gt; addresses = null;
    239 
    240     try {
    241         addresses = geocoder.getFromLocation(
    242                 location.getLatitude(),
    243                 location.getLongitude(),
    244                 // In this sample, get just a single address.
    245                 1);
    246     } catch (IOException ioException) {
    247         // Catch network or other I/O problems.
    248         errorMessage = getString(R.string.service_not_available);
    249         Log.e(TAG, errorMessage, ioException);
    250     } catch (IllegalArgumentException illegalArgumentException) {
    251         // Catch invalid latitude or longitude values.
    252         errorMessage = getString(R.string.invalid_lat_long_used);
    253         Log.e(TAG, errorMessage + ". " +
    254                 "Latitude = " + location.getLatitude() +
    255                 ", Longitude = " +
    256                 location.getLongitude(), illegalArgumentException);
    257     }
    258 
    259     // Handle case where no address was found.
    260     if (addresses == null || addresses.size()  == 0) {
    261         if (errorMessage.isEmpty()) {
    262             errorMessage = getString(R.string.no_address_found);
    263             Log.e(TAG, errorMessage);
    264         }
    265         deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
    266     } else {
    267         Address address = addresses.get(0);
    268         ArrayList&lt;String&gt; addressFragments = new ArrayList&lt;String&gt;();
    269 
    270         // Fetch the address lines using {@code getAddressLine},
    271         // join them, and send them to the thread.
    272         for(int i = 0; i < address.getMaxAddressLineIndex(); i++) {
    273             addressFragments.add(address.getAddressLine(i));
    274         }
    275         Log.i(TAG, getString(R.string.address_found));
    276         deliverResultToReceiver(Constants.SUCCESS_RESULT,
    277                 TextUtils.join(System.getProperty("line.separator"),
    278                         addressFragments));
    279     }
    280 }
    281 </pre>
    282 
    283 <h3 id="return-address">Return the address to the requestor</h3>
    284 
    285 <p>The final action that the intent service must complete is sending the address back to a
    286   {@link android.os.ResultReceiver} in the activity that started the service.
    287   The {@link android.os.ResultReceiver} class allows you to send a
    288   numeric result code as well as a message containing the result data. The
    289   numeric code is useful for reporting the success or failure of the geocoding
    290   request. In the case of a successful reverse geocoding, the message contains
    291   the address. In the case of a failure, the message contains some text
    292   describing the reason for the failure.</p>
    293 
    294 <p>You have already retrieved the address from the geocoder, trapped any errors
    295   that may occur, and called the {@code deliverResultToReceiver()} method, so now
    296   you must define the {@code deliverResultToReceiver()} method that sends
    297   a result code and message bundle to the result receiver.</p>
    298 
    299 <p>For the result code, use the value that you've passed to the
    300   {@code deliverResultToReceiver()} method in the {@code resultCode} parameter.
    301   To construct the message bundle, concatenate the {@code RESULT_DATA_KEY}
    302   constant from your {@code Constants} class (defined in
    303   <a href="#retrieve-street-address">Retrieve the street address data</a>) and
    304   the value in the {@code message} parameter that is passed to the
    305   {@code deliverResultToReceiver()} method, as shown in the following sample:
    306 </p>
    307 
    308 <pre>
    309 public class FetchAddressIntentService extends IntentService {
    310     protected ResultReceiver mReceiver;
    311     ...
    312     private void deliverResultToReceiver(int resultCode, String message) {
    313         Bundle bundle = new Bundle();
    314         bundle.putString(Constants.RESULT_DATA_KEY, message);
    315         mReceiver.send(resultCode, bundle);
    316     }
    317 }
    318 </pre>
    319 
    320 <h2 id="start-intent">Start the intent service</h2>
    321 
    322 <p>The intent service, as defined in the previous section, runs in the
    323   background and fetches the address corresponding to a
    324   given geographic location. When you start the service, the Android framework
    325   instantiates and starts the service if it isn't already running, and it creates a
    326   process if needed. If the service is already running, it remains running.
    327   Because the service extends {@link android.app.IntentService IntentService},
    328   it shuts down automatically after all intents are processed.</p>
    329 
    330 <p>Start the service from your app's main activity
    331   and create an {@link android.content.Intent} to pass data to the service. You
    332   need an <em>explicit</em> intent because you want only your service
    333   to respond to the intent. For more information, see
    334   <a href="{@docRoot}guide/components/intents-filters.html#Types">Intent
    335   Types</a>.</p>
    336 
    337 <p>To create an explicit intent, specify the name of the
    338   class to use for the service: {@code FetchAddressIntentService.class}.
    339   Pass this information in the intent extras:</p>
    340 
    341 <ul>
    342   <li>A {@link android.os.ResultReceiver} to handle the results of the address
    343     lookup.</li>
    344   <li>A {@link android.location.Location} object containing the latitude and
    345     longitude that you want to convert to an address.</li>
    346 </ul>
    347 
    348 <p>The following code sample shows you how to start the intent service:</p>
    349 
    350 <pre>
    351 public class MainActivity extends ActionBarActivity implements
    352         ConnectionCallbacks, OnConnectionFailedListener {
    353 
    354     protected Location mLastLocation;
    355     private AddressResultReceiver mResultReceiver;
    356     ...
    357 
    358     protected void startIntentService() {
    359         Intent intent = new Intent(this, FetchAddressIntentService.class);
    360         intent.putExtra(Constants.RECEIVER, mResultReceiver);
    361         intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation);
    362         startService(intent);
    363     }
    364 }
    365 </pre>
    366 
    367 <p class="caution"><strong>Caution</strong>: To ensure that your app is secure, always use an
    368 explicit intent when starting a {@link android.app.Service} and do not declare intent filters for
    369 your services. Using an implicit intent to start a service is a security hazard because you cannot
    370 be certain of the service that will respond to the intent, and the user cannot see which service
    371 starts.</p>
    372 
    373 <p>Call the above {@code startIntentService()} method when the
    374   user takes an action that requires a geocoding address lookup. For example,
    375   the user may press a <em>Fetch address</em> button on your app's UI. Before
    376   starting the intent service, you need to check that the connection to Google
    377   Play services is present. The following code snippet shows the call to the
    378   {@code startIntentService()} method in the button handler:</p>
    379 
    380 <pre>
    381 public void fetchAddressButtonHandler(View view) {
    382     // Only start the service to fetch the address if GoogleApiClient is
    383     // connected.
    384     if (mGoogleApiClient.isConnected() && mLastLocation != null) {
    385         startIntentService();
    386     }
    387     // If GoogleApiClient isn't connected, process the user's request by
    388     // setting mAddressRequested to true. Later, when GoogleApiClient connects,
    389     // launch the service to fetch the address. As far as the user is
    390     // concerned, pressing the Fetch Address button
    391     // immediately kicks off the process of getting the address.
    392     mAddressRequested = true;
    393     updateUIWidgets();
    394 }
    395 </pre>
    396 
    397 <p>You must also start the intent service when the connection to Google Play
    398   services is established, if the user has already clicked the button on your
    399   app's UI. The following code snippet shows the call to the
    400   {@code startIntentService()} method in the
    401   <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">{@code onConnected()}</a>
    402   callback that is provided by the Google API Client:</p>
    403 
    404 <pre>
    405 public class MainActivity extends ActionBarActivity implements
    406         ConnectionCallbacks, OnConnectionFailedListener {
    407     ...
    408     &#64;Override
    409     public void onConnected(Bundle connectionHint) {
    410         // Gets the best and most recent location currently available,
    411         // which may be null in rare cases when a location is not available.
    412         mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
    413                 mGoogleApiClient);
    414 
    415         if (mLastLocation != null) {
    416             // Determine whether a Geocoder is available.
    417             if (!Geocoder.isPresent()) {
    418                 Toast.makeText(this, R.string.no_geocoder_available,
    419                         Toast.LENGTH_LONG).show();
    420                 return;
    421             }
    422 
    423             if (mAddressRequested) {
    424                 startIntentService();
    425             }
    426         }
    427     }
    428 }
    429 </pre>
    430 
    431 <h2 id="result-receiver">Receive the geocoding results</h2>
    432 
    433 <p>After the intent service handles the geocoding request, it uses a
    434   {@link android.os.ResultReceiver} to return the results to the activity that
    435   made the request. In the activity that makes the request, define an
    436   {@code AddressResultReceiver} that extends {@link android.os.ResultReceiver}
    437   to handle the response from {@code FetchAddressIntentService}.</p>
    438 
    439 <p>The result includes a numeric result code (<code>resultCode</code>) as well
    440   as a message containing the result data (<code>resultData</code>). If the
    441   reverse geocoding process is successful, the <code>resultData</code> contains
    442   the address. In the case of a failure, the <code>resultData</code> contains
    443   text describing the reason for the failure. For details of the possible errors,
    444   see <a href="#return-address">Return the address to the requestor</a>.</p>
    445 
    446 <p>Override the
    447   {@link android.os.ResultReceiver#onReceiveResult onReceiveResult()} method
    448   to handle the results that are delivered to the result receiver, as shown in the
    449   following code sample:</p>
    450 
    451 <pre>
    452 public class MainActivity extends ActionBarActivity implements
    453         ConnectionCallbacks, OnConnectionFailedListener {
    454     ...
    455     class AddressResultReceiver extends ResultReceiver {
    456         public AddressResultReceiver(Handler handler) {
    457             super(handler);
    458         }
    459 
    460         &#64;Override
    461         protected void onReceiveResult(int resultCode, Bundle resultData) {
    462 
    463             // Display the address string
    464             // or an error message sent from the intent service.
    465             mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
    466             displayAddressOutput();
    467 
    468             // Show a toast message if an address was found.
    469             if (resultCode == Constants.SUCCESS_RESULT) {
    470                 showToast(getString(R.string.address_found));
    471             }
    472 
    473         }
    474     }
    475 }
    476 </pre>
    477