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, you need to 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>In order for 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 recreated, for example 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 defining the intent service:</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 <service>} 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,
    143   that is, your reverse 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 are used to adjust the presentation of information, such as numbers or
    150   dates, to suit the conventions in the region 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.</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>The next step is to 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 you want returned. In this case, you want just one
    190   address. The geocoder returns an array of addresses. If no addresses were
    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. If an error
    195   occurs, place the corresponding error message in the {@code errorMessage}
    196   variable, so you can send it back to the requesting activity:</p>
    197 
    198 <ul>
    199   <li><strong>No location data provided</strong> - The intent extras do not
    200     include the {@link android.location.Location} object required for reverse
    201     geocoding.</li>
    202   <li><strong>Invalid latitude or longitude used</strong> - The latitude
    203     and/or longitude values provided in the {@link android.location.Location}
    204     object are invalid.</li>
    205   <li><strong>No geocoder available</strong> - The background geocoding service
    206     is not available, due to a network error or IO exception.</li>
    207   <li><strong>Sorry, no address found</strong> - The geocoder could not find an
    208     address for the given latitude/longitude.</li>
    209 </ul>
    210 
    211 <p>To get the individual lines of an address object, use the
    212   {@link android.location.Address#getAddressLine getAddressLine()}
    213   method provided by the {@link android.location.Address} class. Then join the
    214   lines into a list of address fragments ready to return to the activity that
    215   requested the address.</p>
    216 
    217 <p>To send the results back to the requesting activity, call the
    218   {@code deliverResultToReceiver()} method (defined in
    219   <a href="#return-address">Return the address to the requestor</a>). The
    220   results consist of the previously-mentioned numeric success/failure code and
    221   a string. In the case of a successful reverse geocoding, the string contains
    222   the address. In the case of a failure, the string contains the error message,
    223   as shown in the code sample below:</p>
    224 
    225 <pre>
    226 &#64;Override
    227 protected void onHandleIntent(Intent intent) {
    228     String errorMessage = "";
    229 
    230     // Get the location passed to this service through an extra.
    231     Location location = intent.getParcelableExtra(
    232             Constants.LOCATION_DATA_EXTRA);
    233 
    234     ...
    235 
    236     List&lt;Address&gt; addresses = null;
    237 
    238     try {
    239         addresses = geocoder.getFromLocation(
    240                 location.getLatitude(),
    241                 location.getLongitude(),
    242                 // In this sample, get just a single address.
    243                 1);
    244     } catch (IOException ioException) {
    245         // Catch network or other I/O problems.
    246         errorMessage = getString(R.string.service_not_available);
    247         Log.e(TAG, errorMessage, ioException);
    248     } catch (IllegalArgumentException illegalArgumentException) {
    249         // Catch invalid latitude or longitude values.
    250         errorMessage = getString(R.string.invalid_lat_long_used);
    251         Log.e(TAG, errorMessage + ". " +
    252                 "Latitude = " + location.getLatitude() +
    253                 ", Longitude = " +
    254                 location.getLongitude(), illegalArgumentException);
    255     }
    256 
    257     // Handle case where no address was found.
    258     if (addresses == null || addresses.size()  == 0) {
    259         if (errorMessage.isEmpty()) {
    260             errorMessage = getString(R.string.no_address_found);
    261             Log.e(TAG, errorMessage);
    262         }
    263         deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
    264     } else {
    265         Address address = addresses.get(0);
    266         ArrayList&lt;String&gt; addressFragments = new ArrayList&lt;String&gt;();
    267 
    268         // Fetch the address lines using {@code getAddressLine},
    269         // join them, and send them to the thread.
    270         for(int i = 0; i < address.getMaxAddressLineIndex(); i++) {
    271             addressFragments.add(address.getAddressLine(i));
    272         }
    273         Log.i(TAG, getString(R.string.address_found));
    274         deliverResultToReceiver(Constants.SUCCESS_RESULT,
    275                 TextUtils.join(System.getProperty("line.separator"),
    276                         addressFragments));
    277     }
    278 }
    279 </pre>
    280 
    281 <h3 id="return-address">Return the address to the requestor</h3>
    282 
    283 <p>The final thing the intent service must do is send the address back to a
    284   {@link android.os.ResultReceiver} in the activity that started the service.
    285   The {@link android.os.ResultReceiver} class allows you to send a
    286   numeric result code as well as a message containing the result data. The
    287   numeric code is useful for reporting the success or failure of the geocoding
    288   request. In the case of a successful reverse geocoding, the message contains
    289   the address. In the case of a failure, the message contains some text
    290   describing the reason for failure.</p>
    291 
    292 <p>You have already retrieved the address from the geocoder, trapped any errors
    293   that may occur, and called the {@code deliverResultToReceiver()} method. Now
    294   you need to define the {@code deliverResultToReceiver()} method that sends
    295   a result code and message bundle to the result receiver.</p>
    296 
    297 <p>For the result code, use the value that you've passed to the
    298   {@code deliverResultToReceiver()} method in the {@code resultCode} parameter.
    299   To construct the message bundle, concatenate the {@code RESULT_DATA_KEY}
    300   constant from your {@code Constants} class (defined in
    301   <a href="#retrieve-street-address">Retrieve the street address data</a>) and
    302   the value in the {@code message} parameter passed to the
    303   {@code deliverResultToReceiver()} method, as shown in the following sample:
    304 </p>
    305 
    306 <pre>
    307 public class FetchAddressIntentService extends IntentService {
    308     protected ResultReceiver mReceiver;
    309     ...
    310     private void deliverResultToReceiver(int resultCode, String message) {
    311         Bundle bundle = new Bundle();
    312         bundle.putString(Constants.RESULT_DATA_KEY, message);
    313         mReceiver.send(resultCode, bundle);
    314     }
    315 }
    316 </pre>
    317 
    318 <h2 id="start-intent">Start the Intent Service</h2>
    319 
    320 <p>The intent service, as defined in the previous section, runs in the
    321   background and is responsible for fetching the address corresponding to a
    322   given geographic location. When you start the service, the Android framework
    323   instantiates and starts the service if it isn't already running, and creates a
    324   process if needed. If the service is already running then it remains running.
    325   Because the service extends {@link android.app.IntentService IntentService},
    326   it shuts down automatically when all intents have been processed.</p>
    327 
    328 <p>Start the service from your app's main activity,
    329   and create an {@link android.content.Intent} to pass data to the service. You
    330   need an <em>explicit</em> intent, because you want only your service
    331   to respond to the intent. For more information, see
    332   <a href="{@docRoot}guide/components/intents-filters.html#Types">Intent
    333   Types</a>.</p>
    334 
    335 <p>To create an explicit intent, specify the name of the
    336   class to use for the service: {@code FetchAddressIntentService.class}.
    337   Pass two pieces of information in the intent extras:</p>
    338 
    339 <ul>
    340   <li>A {@link android.os.ResultReceiver} to handle the results of the address
    341     lookup.</li>
    342   <li>A {@link android.location.Location} object containing the latitude and
    343     longitude that you want to convert to an address.</li>
    344 </ul>
    345 
    346 <p>The following code sample shows you how to start the intent service:</p>
    347 
    348 <pre>
    349 public class MainActivity extends ActionBarActivity implements
    350         ConnectionCallbacks, OnConnectionFailedListener {
    351 
    352     protected Location mLastLocation;
    353     private AddressResultReceiver mResultReceiver;
    354     ...
    355 
    356     protected void startIntentService() {
    357         Intent intent = new Intent(this, FetchAddressIntentService.class);
    358         intent.putExtra(Constants.RECEIVER, mResultReceiver);
    359         intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation);
    360         startService(intent);
    361     }
    362 }
    363 </pre>
    364 
    365 <p>Call the above {@code startIntentService()} method when the
    366   user takes an action that requires a geocoding address lookup. For example,
    367   the user may press a <em>Fetch address</em> button on your app's UI. Before
    368   starting the intent service, you need to check that the connection to Google
    369   Play services is present. The following code snippet shows the call to the
    370   {@code startIntentService()} method in the button handler:</p>
    371 
    372 <pre>
    373 public void fetchAddressButtonHandler(View view) {
    374     // Only start the service to fetch the address if GoogleApiClient is
    375     // connected.
    376     if (mGoogleApiClient.isConnected() && mLastLocation != null) {
    377         startIntentService();
    378     }
    379     // If GoogleApiClient isn't connected, process the user's request by
    380     // setting mAddressRequested to true. Later, when GoogleApiClient connects,
    381     // launch the service to fetch the address. As far as the user is
    382     // concerned, pressing the Fetch Address button
    383     // immediately kicks off the process of getting the address.
    384     mAddressRequested = true;
    385     updateUIWidgets();
    386 }
    387 </pre>
    388 
    389 <p>You must also start the intent service when the connection to Google Play
    390   services is established, if the user has already clicked the button on your
    391   app's UI. The following code snippet shows the call to the
    392   {@code startIntentService()} method in the
    393   <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">{@code onConnected()}</a>
    394   callback provided by the Google API Client:</p>
    395 
    396 <pre>
    397 public class MainActivity extends ActionBarActivity implements
    398         ConnectionCallbacks, OnConnectionFailedListener {
    399     ...
    400     &#64;Override
    401     public void onConnected(Bundle connectionHint) {
    402         // Gets the best and most recent location currently available,
    403         // which may be null in rare cases when a location is not available.
    404         mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
    405                 mGoogleApiClient);
    406 
    407         if (mLastLocation != null) {
    408             // Determine whether a Geocoder is available.
    409             if (!Geocoder.isPresent()) {
    410                 Toast.makeText(this, R.string.no_geocoder_available,
    411                         Toast.LENGTH_LONG).show();
    412                 return;
    413             }
    414 
    415             if (mAddressRequested) {
    416                 startIntentService();
    417             }
    418         }
    419     }
    420 }
    421 </pre>
    422 
    423 <h2 id="result-receiver">Receive the Geocoding Results</h2>
    424 
    425 <p>The intent service has handled the geocoding request, and uses a
    426   {@link android.os.ResultReceiver} to return the results to the activity that
    427   made the request. In the activity that makes the request, define an
    428   {@code AddressResultReceiver} that extends {@link android.os.ResultReceiver}
    429   to handle the response from {@code FetchAddressIntentService}.</p>
    430 
    431 <p>The result includes a numeric result code (<code>resultCode</code>) as well
    432   as a message containing the result data (<code>resultData</code>). If the
    433   reverse geocoding process was successful, the <code>resultData</code> contains
    434   the address. In the case of a failure, the <code>resultData</code> contains
    435   text describing the reason for failure. For details of the possible errors,
    436   see <a href="#return-address">Return the address to the requestor</a>.</p>
    437 
    438 <p>Override the
    439   {@link android.os.ResultReceiver#onReceiveResult onReceiveResult()} method
    440   to handle the results delivered to the result receiver, as shown in the
    441   following code sample:</p>
    442 
    443 <pre>
    444 public class MainActivity extends ActionBarActivity implements
    445         ConnectionCallbacks, OnConnectionFailedListener {
    446     ...
    447     class AddressResultReceiver extends ResultReceiver {
    448         public AddressResultReceiver(Handler handler) {
    449             super(handler);
    450         }
    451 
    452         &#64;Override
    453         protected void onReceiveResult(int resultCode, Bundle resultData) {
    454 
    455             // Display the address string
    456             // or an error message sent from the intent service.
    457             mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
    458             displayAddressOutput();
    459 
    460             // Show a toast message if an address was found.
    461             if (resultCode == Constants.SUCCESS_RESULT) {
    462                 showToast(getString(R.string.address_found));
    463             }
    464 
    465         }
    466     }
    467 }
    468 </pre>
    469