summaryrefslogtreecommitdiffstats
path: root/docs/html/training/location/display-address.jd
blob: 516f14f06781d92affb5f6ed80c595cbea055238 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
page.title=Displaying a Location Address
trainingnavtop=true
@jd:body

<div id="tb-wrapper">
  <div id="tb">

    <h2>This lesson teaches you how to</h2>
    <ol>
      <li><a href="#connect">Get a Geographic Location</a></li>
      <li><a href="#fetch-address">Define an Intent Service to Fetch the
        Address</a></li>
      <li><a href="#start-intent">Start the Intent Service</a></li>
      <li><a href="#result-receiver">Receive the Geocoding Results</a></li>
    </ol>

    <h2>You should also read</h2>
      <ul>
        <li>
          <a href="{@docRoot}google/play-services/setup.html">Setting up Google
          Play Services</a>
        </li>
        <li>
          <a href="retrieve-current.html">Getting the Last Known Location</a>
        </li>
        <li>
          <a href="receive-location-updates.html">Receiving Location Updates</a>
        </li>
      </ul>
    <h2>Try it out</h2>

    <ul>
      <li>
        <a href="https://github.com/googlesamples/android-play-location/tree/master/LocationAddress" class="external-link">LocationAddress</a>
      </li>
    </ul>
  </div>
</div>

<p>The lessons <a href="retrieve-current.html">Getting the Last Known
  Location</a> and <a href="receive-location-updates.html">Receiving Location
  Updates</a> 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.</p>

<p>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 <em>geocoding</em>. Alternatively, you can
  convert a geographic location to an address. The address lookup feature is
  also known as <em>reverse geocoding</em>.</p>

<p>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.</p>

<h2 id="connect">Get a Geographic Location</h2>

<p>The last known location of the device is a useful starting point for the
  address lookup feature. The lesson on
  <a href="retrieve-current.html">Getting the Last Known Location</a> shows you
  how to use the
  <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)">{@code getLastLocation()}</a>
  method provided by the
  <a href="{@docRoot}reference/com/google/android/gms/location/FusedLocationProviderApi.html">fused
  location provider</a> to find the latest location of the device.</p>

<p>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
  <a href="{@docRoot}training/location/retrieve-current.html#play-services">Connect
  to Google Play Services</a>.</p>

<p>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:</p>

<pre>
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.gms.location.sample.locationupdates" &gt;

  &lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/&gt;
&lt;/manifest&gt;
</pre>

<h2 id="fetch-address">Define an Intent Service to Fetch the Address</h2>

<p>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.</p>

<p>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.</p>

<p>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}.</p>

<h3>Define the Intent Service in your App Manifest</h3>

<p>Add an entry to your app manifest defining the intent service:</p>

<pre>
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.gms.location.sample.locationaddress" &gt;
    &lt;application
        ...
        &lt;service
            android:name=".FetchAddressIntentService"
            android:exported="false"/&gt;
    &lt;/application&gt;
    ...
&lt;/manifest&gt;
</pre>

<p class="note"><strong>Note:</strong> The {@code &lt;service&gt;} 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.</p>

<h3>Create a Geocoder</h3>

<p>The process of converting a geographic location to an address is called
  <em>reverse geocoding</em>. 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.</p>

<p>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
  <a href="{@docRoot}reference/java/util/Locale.html">{@code Locale}</a> object
  to the {@link android.location.Geocoder} object, to ensure that the resulting
  address is localized to the user's geographic region.</p>

<pre>
&#64;Override
protected void onHandleIntent(Intent intent) {
    Geocoder geocoder = new Geocoder(this, Locale.getDefault());
    ...
}
</pre>

<h3 id="retrieve-street-address">Retrieve the street address data</h3>

<p>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:</p>

<pre>
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";
}
</pre>

<p>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.</p>

<p>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:</p>

<ul>
  <li><strong>No location data provided</strong> - The intent extras do not
    include the {@link android.location.Location} object required for reverse
    geocoding.</li>
  <li><strong>Invalid latitude or longitude used</strong> - The latitude
    and/or longitude values provided in the {@link android.location.Location}
    object are invalid.</li>
  <li><strong>No geocoder available</strong> - The background geocoding service
    is not available, due to a network error or IO exception.</li>
  <li><strong>Sorry, no address found</strong> - The geocoder could not find an
    address for the given latitude/longitude.</li>
</ul>

<p>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.</p>

<p>To send the results back to the requesting activity, call the
  {@code deliverResultToReceiver()} method (defined in
  <a href="#return-address">Return the address to the requestor</a>). 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:</p>

<pre>
&#64;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&lt;Address&gt; 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&lt;String&gt; addressFragments = new ArrayList&lt;String&gt;();

        // 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));
    }
}
</pre>

<h3 id="return-address">Return the address to the requestor</h3>

<p>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.</p>

<p>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.</p>

<p>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
  <a href="#retrieve-street-address">Retrieve the street address data</a>) and
  the value in the {@code message} parameter passed to the
  {@code deliverResultToReceiver()} method, as shown in the following sample:
</p>

<pre>
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);
    }
}
</pre>

<h2 id="start-intent">Start the Intent Service</h2>

<p>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.</p>
  
<p>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 <em>explicit</em> intent, because you want only your service
  to respond to the intent. For more information, see
  <a href="{@docRoot}guide/components/intents-filters.html#Types">Intent
  Types</a>.</p>

<p>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:</p>

<ul>
  <li>A {@link android.os.ResultReceiver} to handle the results of the address
    lookup.</li>
  <li>A {@link android.location.Location} object containing the latitude and
    longitude that you want to convert to an address.</li>
</ul>

<p>The following code sample shows you how to start the intent service:</p>

<pre>
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);
    }
}
</pre>

<p>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 <em>Fetch address</em> 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:</p>

<pre>
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();
}
</pre>

<p>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
  <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">{@code onConnected()}</a>
  callback provided by the Google API Client:</p>

<pre>
public class MainActivity extends ActionBarActivity implements
        ConnectionCallbacks, OnConnectionFailedListener {
    ...
    &#64;Override
    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 (mAddressRequested) {
                startIntentService();
            }
        }
    }
}
</pre>

<h2 id="result-receiver">Receive the Geocoding Results</h2>

<p>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}.</p>

<p>The result includes a numeric result code (<code>resultCode</code>) as well
  as a message containing the result data (<code>resultData</code>). If the
  reverse geocoding process was successful, the <code>resultData</code> contains
  the address. In the case of a failure, the <code>resultData</code> contains
  text describing the reason for failure. For details of the possible errors,
  see <a href="#return-address">Return the address to the requestor</a>.</p>

<p>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:</p>

<pre>
public class MainActivity extends ActionBarActivity implements
        ConnectionCallbacks, OnConnectionFailedListener {
    ...
    class AddressResultReceiver extends ResultReceiver {
        public AddressResultReceiver(Handler handler) {
            super(handler);
        }

        &#64;Override
        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));
            }

        }
    }
}
</pre>