0% found this document useful (0 votes)
13 views

Unit4Lesson7

This document outlines how to create an Android app called WalkMyAndroid that utilizes location services to access and display the device's last known location, convert geographic coordinates into a physical address, and receive periodic location updates. It covers essential tasks such as setting up location services, obtaining permissions, and implementing reverse geocoding using an AsyncTask. The document emphasizes efficient use of location requests to optimize network and battery consumption.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Unit4Lesson7

This document outlines how to create an Android app called WalkMyAndroid that utilizes location services to access and display the device's last known location, convert geographic coordinates into a physical address, and receive periodic location updates. It covers essential tasks such as setting up location services, obtaining permissions, and implementing reverse geocoding using an AsyncTask. The document emphasizes efficient use of location requests to optimize network and battery consumption.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 37

7.

1: Using the device location


Contents:

Introduction
What you should already KNOW What you will LEARN
What you will DO App overview
Task 1. Set up location services
Task 2. Get the last known location
Task 3. Get the location as an address Task 4. Receive location updates
Solution code
Coding challenge Summary
Related concept Learn more
Users are constantly moving around in the world, usually with their phones in their pockets.
Advances in GPS and network technologies have made it possible to create Android apps
with precise location awareness, using the location services APIs included in Google Play
services.
When an app requests the device location, it impacts network and battery consumption,
which impacts device performance. To improve the performance of your app, keep the
frequency of location requests as low as possible.
In this practical, you learn how to access the device's last known location. You also learn
how to convert a set of geographic coordinates (longitude and latitude) into a street address,
and how to perform periodic location updates.

What you should already KNOW


You should be familiar with:

Creating, building, and running apps in Android Studio. The Activity lifecycle.
Making data persistent across configuration changes. Requesting permissions at runtime.
Using an AsyncTask to do background work.

What you will LEARN


You will learn how to:

Get the last known location of the device.


Obtain a physical address from a set of coordinates (reverse geocoding). Perform
periodic location updates.

What you will DO

Create the WalkMyAndroid app and have it display the device's last known location as
latitude and longitude coordinates.
Reverse geocode the latitude and longitude coordinates into a physical address. Extend
the app to receive periodic location updates.

App overview
The WalkMyAndroid app prompts the user to change the device location settings, if
necessary, to allow the app to access precise location data. The app shows the device
location as latitude and longitude coordinates, then shows the location as a physical address.
The completed app does periodic location updates and shows an animation that gives the
user a visual cue that the app is tracking the device's location.
You create the WalkMyAndroid app in phases:

In tasks 1 and 2, you implement a button that gets the most recent location for the device
and displays the coordinates and timestamp of the location in a TextView . In task 3, you
turn the coordinates into a physical address, through a process called reverse geocoding .
In task 4, you learn how to trigger periodic updates of the location.
Task 1. Set up location services
Obtaining the location information for a device can be complicated: Devices contain
different types of GPS hardware, and a satellite or network connection (cell or Wi-Fi) is not
always available. Activating GPS and other hardware components uses power. (For more
information about GPS and power use, see the chapter on performance.)
To find the device location efficiently without worrying about which provider or network
type to use, use the FusedLocationProviderClient interface. Before you can use
FusedLocationProviderClient , you need to set up Google Play services. In this task, you
download the starter code and include the required dependencies.

1.1 Download the starter app


1. Download the starter app for this practical, WalkMyAndroid-Starter.

2. Open the starter app in Android Studio, rename the app to WalkMyAndroid, and run
it.
3. You might need to update your Android SDK Build-Tools. To do this, use the Android
SDK Manager.
The UI has a Get Location button, but tapping it doesn't do anything yet.

1.2 Set up Google Play services


Install the Google Repository and update the Android SDK Manager:
1. Open Android Studio.

2. Select Tools > Android > SDK Manager.

3. Select the SDK Tools tab.

4. Expand Support Repository, select Google Repository, and click OK. Now you can
include Google Play services packages in your app.
To add Google Play services to your project, add the following line of code to the
dependencies section in your app-level build.gradle (Module: app) file:

compile 'com.google.android.gms:play-services-location:XX.X.X'

Replace XX.X.X with the latest version number for Google Play services, for example
11.0.2 . Android Studio will let you know if you need to update it. For more information
and the latest version number, see Add Google Play Services to Your Project.
Note: If your app references more than 65K methods, the app may fail to compile. To
mitigate this problem, compile your app using only the Google Play services APIs that your
app uses. To learn how to selectively compile APIs into your executable, see Set Up Google
Play Services in the developer documentation. To learn how to enable an app configuration
known as multidex , see Configure Apps with Over 64K Methods.
Now that Google Play services is installed, you're ready to connect to the LocationServices
API.

Task 2. Get the last known location


The LocationServices API uses a fused location provider to manage the underlying
technology and provides a straightforward API so that you can specify requirements at a
high level, like high accuracy or low power. It also optimizes the device's use of battery
power. In this step, you will use it to obtain the device's last known location.

2.1 Set location permission in the manifest


Using the Location API requires permission from the user. Android offers two location
permissions:

ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION
The permission you choose determines the accuracy of the location returned by the API. For
this lesson, use the ACCESS_FINE_LOCATION permission, because you want the most
accurate location information possible.
1. Add the following element to your manifest file, above the<application>
element:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

2.2 Request permission at runtime


Starting with Android 6.0 (API level 23), it's not always enough to include a permission
statement in the manifest. For "dangerous" permissions, you also have to request permission
programmatically, at runtime.
Dangerous permissions cover areas where the app wants data or resources that involve the
user's private information, or could potentially affect the user's stored data or the operation
of other apps.
To request location permission at runtime:

1. Create an OnClickListenerfor the Get Location button in onCreate() in


MainActivity .
2. Create a method stub called getLocation() that takes no arguments and doesn't return
anything. Invoke the getLocation() method from the button's onClick() method.
3. In the getLocation() method, check for the ACCESS_FINE_LOCATION
permission.

If the permission has not been granted, request it.


If the permission has been granted, display a message in the logs. (The code below shows
a TAG variable, which you declare later, in Task 3.1.)
For information on runtime permissions, see Requesting Permissions at Run Time.

private void getLocation() {

if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)

!= PackageManager.PERMISSION_GRANTED)
{ ActivityCompat.requestPermissions(this, new
String[]

{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_LOCATION_PERMISSION);

} else {

Log.d(TAG, "getLocation: permissions granted");


4. In your MainActivity class, define an integer constant
REQUEST_LOCATION_PERMISSION. This constant is used to identify the
permission request when the results come back in the onRequestPemissionsResult()
method. It can be any integer greater than 0 .
5. Override the onRequestPermissionsResult() method. If the permission was granted,
call
getLocation() . Otherwise, show a Toast saying that the permission was denied.
@Override

public void onRequestPermissionsResult(int requestCode,


@NonNull String[] permissions, @NonNull int[] grantResults) {

switch (requestCode) {

case REQUEST_LOCATION_PERMISSION:

// If the permission is granted, get the location,

// otherwise, show a Toast


if (grantResults.length > 0

&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {


getLocation();

} else {

Toast.makeText(this,

R.string.location_permission_denied,
Toast.LENGTH_SHORT).show();

6. Run the app. Clicking the button requests permission from the user. If permission is
granted, you see a log statement in the console.
After you grant permission, subsequent clicks on the Get Location button have no effect.
Because you already granted permission, the app doesn't need to ask for permission again
(unless the user manually revokes it in the Settings app), even if you close and restart the
app.

2.3 Get the last known location


The getLastLocation () method doesn't actually make a location request. It simply returns
the location most recently obtained by the FusedLocationProviderClient class.
If no location has been obtained since the device was restarted, the getLastLocation()
method may return null . Usually, the getLastLocation() method returns a Location
object that contains a timestamp of when this location was obtained.
To get the last known location:

1. In strings.xml , add a string resource called location_text . Use location_text to


display the latitude, longitude, and timestamp of the last known location.

<string name="location_text">"Latitude: %1$.4f \n Longitude: %2$.4f

\n Timestamp: %3$tr"</string>
Note: If you aren't familiar with string replacement and formatting, see Formatting and
Styling and the Formatter documentation.
2. In your class, create a member variable of the Location type called
MainActivity

mLastLocation .

3. Find the location TextView by ID ( textview_location ) in onCreate() . Assign the

TextView to a member variable called mLocationTextView .


4. Create a member variable of the FusedLocationProviderClient type called

mFusedLocationClient .
5. Initialize mFusedLocationClient in onCreate() with the following code:

mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

The getLastLocation() method returns a Task that results in a Location object (after the
Task's onSuccess() callback method is called, signifying that the Task was successful).
Retrieve the latitude and longitude coordinates of a geographic location from the resulting
Location object:
6. Replace the log statement in the getLocation() method with the following code
snippet. The code obtains the device's most recent location and assigns it to
mLastLocation .

If the returned location is not null , set the TextView to show the coordinates and time
stamp of the Location object.
If the location returned is null , the fused location provider has not obtained a location since
the device was restarted. Display a message in the TextView that says that the location is
not available.

mFusedLocationClient.getLastLocation().addOnSuccessListener( n
ew OnSuccessListener<Location>() {

@Override

public void onSuccess(Location location)


{ if (location != null) {

mLastLocation = location;
mLocationTextView.setText(

getString(R.string.location_text,
mLastLocation.getLatitude(),
mLastLocation.getLongitude(),
mLastLocation.getTime()));

} else {

mLocationTextView.setText(R.string.no_location);
7. Run the app. You now see the latest location that is stored in the fused location
provider.
Testing location on an emulator
If you test the WalkMyAndroid app on an emulator, use a system image that supports
Google APIs or Google Play:
1. In Android Studio, create a new virtual device and select hardware for it.

2. In the System Image dialog, choose an image that says "Google APIs" or "Google
Play" in the Target column.
To update the fused location provider on an emulator:

1. The emulator appears on your screen with a vertical menu to the right of the virtual
device. To access emulator options, click the ... icon at the bottom of this vertical
menu.
2. Click Location.

3. Enter or change the coordinates in the Longitude and Latitude fields.

4. Click Send to update the fused location provider.

Clicking Send does not affect the location returned by getLastLocation() , because
getLastLocation() uses a local cache that the emulator tools do not update.

When you test the app on an emulator, the getLastLocation() method might return null ,
because the fused location provider doesn't update the location cache after the device is
restarted. If getLastLocation() returns null unexpectedly:
1. Start the Google Maps app and accept the terms and conditions, if you haven't already.

2. Use the steps above to update the fused location provider. Google Maps will force the
local cache to update.
3. Go back to your app and click the Get Location button. The app updates with the new
location.
Later in this lesson, you learn how to force the fused location to update the cache using
periodic updates.

Task 3. Get the location as an address


Your app displays the device's most recent location, which usually corresponds to its current
location, using 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 of the
location.
The process of converting from latitude/longitude to a physical address is called reverse
geocoding . The getFromLocation() method provided by the Geocoder class accepts a
latitude and longitude and returns a list of addresses. The method is synchronous and
requires a network connection. It may take a long time to do its work, so do not call it from
the main user interface (UI) thread of your app.
In this task, you subclass an AsyncTask to perform reverse geocoding off the main thread.
When the AsyncTask completes its process, it updates the UI in the onPostExecute()
method.
Using an AsyncTask means that when your main Activity is destroyed when the device
orientation changes, you will not longer be able to update the UI. To handle this, you make
the location tracking state persistent in Task 4.5.

3.1 Create an AsyncTask subclass


Recall that an AsyncTask object is used to perform work off the main thread. An
AsyncTask object contains one required override method, doInBackground() which is
where the work is performed. For this use case, you need another override method,
onPostExecute() , which is called on the main thread after doInBackground() finishes. In
this step, you set up the boilerplate code for your AsyncTask .
AsyncTask objects are defined by three generic types:

Use the Params type to pass parameters into the doInBackground() method. For this
app, the passed-in parameter is the Location object.
Use the Progress type to mark progress in the onProgressUpdate() method. For this app,
you are not interested in the Progress type, because reverse geocoding is typically quick.
Use the Results type to publish results in the onPostExecute() method. For this app, the
published result is the returned address String.
To create a subclass of AsyncTask that you can use for reverse geocoding:

1. Create a new class called FetchAddressTask that is a subclass of AsyncTask .


Parameterize the AsyncTask using the three types described above:
private class FetchAddressTask extends AsyncTask<Location, Void, String> {}

2. In Android Studio, this class declaration is underlined in red, because you have not
implemented the required doInBackground() method. Press Alt + Enter (Option +
Enter on a Mac) on the highlighted line and select Implement methods. (Or select
Code > Implement methods.)
Notice that the method signature for includes a parameter of the
doInBackground()

Location type, and returns a String ; this comes from parameterized types in the class
declaration.
3. Override the onPostExecute() method by going to the menu and selecting Code >
Override Methods and selecting onPostExecute() . Again notice that the passed-in
parameter is automatically typed as a String, because this what you put in the
FetchAddressTask class declaration.

4. Create a constructor for the AsyncTask that takes a Context as a parameter and
assigns it to a member variable.
Your FetchAddressTask now looks something like this:

private class FetchAddressTask extends AsyncTask<Location, Void, String>


{ private final String TAG = FetchAddressTask.class.getSimpleName();
private Context mContext;

FetchAddressTask(Context applicationContext)
{ mContext = applicationContext;

@Override

protected String doInBackground(Location... locations)


{ return null;

@Override

protected void onPostExecute(String address) {


super.onPostExecute(address);

3.2 Convert the location into an address string


In this step, you complete the doInBackground() method so that it converts the passed-in
Location object into an address string, if possible. If there is a problem, you show an error
message.
1. Create a Geocoder object. This class handles both geocoding (converting from an
address into coordinates) and reverse geocoding:

Geocoder geocoder = new Geocoder(mContext,


Locale.getDefault());
2. Obtain a Location object. The passed-in parameter is a Java varargs argument that
can contain any number of objects. In this case we only pass in one Location object,
so the desired object is the first item in the array:
varargs

Location location = params[0];

3. C of objects, which will be


r filled with the address
e
a
t
e

a
n

e
m
p
t
y
List Address
obtained from the Geocoder . Create an empty String to hold the final result, which will be
either the address or an error:

List<Address> addresses = null;


String resultMessage = "";

4. You are now ready to start the geocoding process. Open up a try block and use the
following code to attempt to obtain a list of addresses from the Location object. The
third parameter specifies the maximum number of addresses that you want to read. In
this case you only want a single address:

try {

addresses = geocoder.getFromLocation(
location.getLatitude(),
location.getLongitude(),

// In this sample, get just a single address


1);

}
5. Open a catch block to catch IOException exceptions that are thrown if there is a
network error or a problem with the Geocoder service. In this catch block, set the
resultMessage to an error message that says "Service not available." Log the error and
result message:
catch (IOException ioException) {

// Catch network or other I/O problems


resultMessage = mContext

.getString(R.string.service_not_available);
Log.e(TAG, resultMessage, ioException);

6. Open another catch block to catch IllegalArgumentException exceptions. Set the

resultMessage to a string that says "Invalid coordinates were supplied to the Geocoder,"
and log the error and result message:
catch (IllegalArgumentException illegalArgumentException) {

// Catch invalid latitude or longitude values


resultMessage = mContext

.getString(R.string.invalid_lat_long_used);
Log.e(TAG, resultMessage + ". " +

"Latitude = " + location.getLatitude() +


", Longitude = " +

location.getLongitude(), illegalArgumentException);

7. You need to catch the case where Geocoder is not able to find the address for the
given coordinates. In the try block, check the address list and the resultMessage
string. If the address list is empty or null and the resultMessage string is empty, then
set the
resultMessage to "No address found" and log the error:
if (addresses == null || addresses.size() == 0)
{ if (resultMessage.isEmpty()) {

resultMessage = mContext

.getString(R.string.no_address_found);
Log.e(TAG, resultMessage);

8. If the address list is not empty or null , the reverse geocode was successful.

The next step is to read the first address into a string, line by line:

Create an empty ArrayList of Strings .


Iterate over the List of Address objects and read them into the new ArrayList
line by line.
Use the TextUtils.join() method to convert the list into a string. Use the \n
character to separate each line with the new-line character: Here is the code:
else {

// If an address is found, read it into resultMessage


Address address = addresses.get(0);
ArrayList<String> addressParts = new ArrayList<>();

// Fetch the address lines using getAddressLine,

// join them, and send them to the thread

for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {


addressParts.add(address.getAddressLine(i));

resultMessage = TextUtils.join("\n", addressParts);


9. At the bottom of doInBackground() method, return the resultMessage object.

3.3 Display the result of the FetchAddressTask object


When doInBackground() completes, the resultMessage string is automatically passed into
the onPostExecute() method. In this step you update the member variables of
MainActivity with new values and display the new data in the TextView using a passed in
interface.
1. Create a new string resource with two replacement variables.

<string name="address_text">"Address: %1$s \n Timestamp: %2$tr"</string>

2. Create an interface in FetchAddressTask called OnTaskCompleted that has one


method, called onTaskCompleted() . This method should take a string as an argument:
interface OnTaskCompleted {

void onTaskCompleted(String result);

3. Add a parameter for the OnTaskCompleted interface to the FetchAddressTask

constructor, and assign it to a member variable:

private OnTaskCompleted mListener;

FetchAddressTask(Context applicationContext, OnTaskCompleted listener)


{ mContext = applicationContext;

mListener = listener;

4. In the onPostExecute() method, call onTaskCompleted() on the mListener


interface, passing in the result string:
@Override

protected void onPostExecute(String address) {


mListener.onTaskCompleted(address);
super.onPostExecute(address);

}
5. Back in the MainActivity , update the activity to implement the

FetchAddressTask.OnTaskCompleted interface you created and override the required


onTaskCompleted() method.
6. In this method, updated the TextView with the resulting address and the current time:
@Override

public void onTaskCompleted(String result) {

// Update the UI
mLocationTextView.setText(getString(R.string.address_text,

result, System.currentTimeMillis()));

7. In the getLocation() method, inside the onSuccess() callback, replace the lines that
assigns the passed-in location to mLastLocation and sets the TextView with the
following line of code. This code creates a new FetchAddressTask and executes it,
passing in the Location object. You can also remove the now unused mLastLocation
member variable.

// Start the reverse geocode AsyncTask


new FetchAddressTask(MainActivity.this,

MainActivity.this).execute(location);

8. At the end of the getLocation() method, show loading text while the
FetchAddressTask
runs:

mLocationTextView.setText(getString(R.string.address_text,
getString(R.string.loading),
System.currentTimeMillis()));

9. Run the app. After briefly loading, the app displays the location address in the

TextView .

Task 4. Receive location updates


Up until now, you've used the FusedLocationProviderClient.getLastLocation() method,
which relies on other apps having already made location requests. In this task, you learn
how to:
Track the device location using periodic location requests. Make the tracking state
persistent.
Show an animation to give a visual cue that the device location is being tracked.
Check the device location settings. You need to know whether location services are turned
on, and whether they are set to the accuracy that your app needs.

4.1 Set up the UI and method stubs


If your app relies heavily on device location, using the getLastLocation() method may not
be sufficient, because getLastLocation() relies on a location request from a different app
and only returns the last value stored in the provider.
To make location requests in your app, you need to:

Create a LocationRequest object that contains the requirements for your location
requests. The requirements include update frequency, accuracy, and so on. You do this step
in 4.2 Create the LocationRequest object, below.
Create a LocationCallback object and override its onLocationResult() method. The
onLocationResult() method is where your app receives location updates. You do this step
in 4.3 Create the LocationCallback object.
Call requestLocationUpdates() on the FusedLocationProviderClient . Pass in the
LocationRequest and the LocationCallback . You do this step in 4.4 Request location
updates.
The user has no way of knowing that the app is making location requests, except for a tiny
icon in the status bar. In this step, you use an animation (included in the starter code) to add
a more obvious visual cue that the device's location is being tracked. You also change the
button text to show the user whether location tracking is on or off.
To indicate location tracking to the user:

1. In MainActivity , declare the member variables mAndroidImageView (of type

ImageView ) and mRotateAnim (of type AnimatorSet ).


2. In the onCreate() method, find the Android ImageView by ID and assign it to

mAndroidImageView . Then find the animation included in the starter code by ID and
assign it to mRotateAnim . Finally set the Android ImageView as the target for the
animation:

mAndroidImageView = (ImageView) findViewById(R.id.imageview_android);

mRotateAnim = (AnimatorSet) AnimatorInflater.loadAnimator


(this, R.animator.rotate);

mRotateAnim.setTarget(mAndroidImageView);

3. In the strings.xml file:


Change the button text to "Start Tracking Location." Do this for for both the portrait and
the landscape layouts.
Change the TextView text to "Press the button to start tracking your location."
4. Refactor and rename the getLocation() method to startTrackingLocation() .

5. Create a private method stub called stopTrackingLocation() that takes no arguments


and returns void .
6. Create a boolean member variable called mTrackingLocation . Boolean primitives
default to false , so you do not need to initialize mTrackingLocation .
7. Change the onClick() method for the button's onClickListener :

If mTrackingLocation is false , call startTrackingLocation() .

If mTrackingLocation is true , call stopTrackingLocation() .

@Override

public void onClick(View v)


{ if (!mTrackingLocation) {

startTrackingLocation();

} else {

stopTrackingLocation();

8. At the end of the startTrackingLocation() method, start the animation by calling

mRotateAnim.start() . Set mTrackingLocation to to true and change the button text to


"Stop Tracking Location".
9. In the stopTrackingLocation() method, check if the you are tracking the location. If
you are, stop the animation by calling mRotateAnim.end() , set mTrackingLocation
to to
false , change the button text back to "Start Tracking Location" and reset the location
TextView to show the original hint.

/**

* Method that stops tracking the device. It removes the location


* updates, stops the animation and reset the UI.
*/

private void stopTrackingLocation() {


if (mTrackingLocation) {

mTrackingLocation = false;
mLocationButton.setText(R.string.start_tracking_location);
mLocationTextView.setText(R.string.textview_hint);
mRotateAnim.end();

4.2 Create the LocationRequest object


The LocationRequest object contains setter methods that determine the frequency and
accuracy of location updates. For now, we're only interested in the following parameters:
Interval: The setInterval() method defines the desired update interval in milliseconds. For
this app, use 10 seconds (10000 milliseconds).
Fastest interval: The fused location provider attempts to make location requests more
efficient by batching requests from different apps. This means that you may receive updates
faster than what you set in setInterval() , which can cause problems if your UI is not ready
for updates. To limit the rate of location updates, use the
setFastestInterval() method. In this app, use 5 seconds (5000 milliseconds)
Priority: Use this parameter with one of the priority constants to specify a balance
between power consumption and accuracy. (Greater accuracy requires greater power
consumption.) For this app, use the PRIORITY_HIGH_ACCURACY constant to prioritize
accuracy.
To create the LocationRequest object:

1. Create a method called getLocationRequest() that takes no arguments and returns a

LocationRequest .
2. Set the interval, fastest interval, and priority parameters.

private LocationRequest getLocationRequest()


{ LocationRequest locationRequest = new
LocationRequest(); locationRequest.setInterval(10000);
locationRequest.setFastestInterval(5000);

locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
return locationRequest;

4.3 Create the LocationCallback object


When your app requests a location update, the fused location provider invokes the
LocationCallback.onLocationResult() callback method. The incoming argument contains a
list of Location objects containing the location's latitude and longitude.
To create a LocationCallback object:

1. At the bottom of onCreate() , create a new LocationCallback object and assign it to a


member variable called mLocationCallback .
2. Override the onLocationResult() method.
mLocationCallback = new LocationCallback() {
@Override

public void onLocationResult(LocationResult locationResult) {

};
4.4 Request location updates
You now have the required LocationRequest and LocationCallback objects to request
periodic location updates. When your app receives the LocationResult objects in
onLocationResult() , use the FetchAddressTask to reverse geocode the Location object
into an address:
1. To request periodic location updates, replace the call to getLastLocation() in

startTrackingLocation() (along with the OnSuccessListener ) with the following method


call. Pass in the LocationRequest and LocationCallback :

mFusedLocationClient.requestLocationUpdates
(getLocationRequest(), mLocationCallback,

null /* Looper */);

2. In the stopTrackingLocation() method, call removeLocationUpdates() on

mFusedLocationClient . Pass in the LocationCallback object.

mFusedLocationClient.removeLocationUpdates(mLocationCallback);

3. In the onLocationResult() callback, check mTrackingLocation . If


mTrackingLocation is
true , execute FetchAddressTask() , and use the LocationResult.getLastLocation()
method to obtain the most recent Location object.

@Override

public void onLocationResult(LocationResult locationResult) {

// If tracking is turned on, reverse geocode into an address


if (mTrackingLocation) {

new FetchAddressTask(MainActivity.this, MainActivity.this)

.execute(locationResult.getLastLocation());

4. In onTaskComplete() , where the UI is updated, wrap the code in an if statement that


checks the mTrackingLocation boolean. If the user turns off the location updates
while the AsyncTask is running, the results are not displayed to the TextView .
5. Run the app. Your app tracks your location, updating the location approximately every
ten seconds.
Testing the location-update functionality on an emulator can be tough: the UI will say
"Loading" until you send a new location, and seeing the timing of the set interval is
impossible. You can use a GPX file to simulate different locations over time. For testing,
you can use the places_gps_data.gpx GPX file, which contains several locations:
1. Download the places_gps_data.gpx file.

2. Open your emulator, click the ... icon at the bottom of this vertical settings menu, and
select the Location tab.
3. Click Load GPX/KML and select the downloaded file.
4. Change the duration of each item to 10 seconds, and click the play button. If you start
tracking when the GPX file is playing, you see a changing address displayed in the UI.
Right now, the app continues to request location updates until the user clicks the button, or
until the Activity is destroyed. To conserve power, stop location updates when your is
Activity
not in focus (in the paused state) and resume location updates when
Activity
the regains focus:
1. Ov object's a methods.
onPause()
erri
mTrackingLocation is true ,
de Activity onResume()
call
the , check mTrackingLocation .
If
2. In onResume()

startTrackingLocation() .
3. In onPause() , check mTrackingLocation . If mTrackingLocation is true, call

stopTrackingLocation() but set mTrackingLocation to true so the app continues tracking


the location when it resumes.
4. Run the app and turn on location tracking. Exiting the app stops the location updates
when the activity is not visible.

4.5 Make the tracking state persistent


If you run the app and rotate the device, the app resets to its initial state. The
mTrackingLocation boolean is not persistent across configuration changes, and it defaults
to
false when the Activity is recreated. This means the UI defaults to the initial state.

In this step, you use the saved instance state to make mTrackingLocation persistent so that
the app continues to track location when there is a configuration change.
1. Override the Activity object's onSaveInstanceState() method.

2. Create a string constant called TRACKING_LOCATION_KEY . You use this


constant as a key for the mTrackingLocation boolean.
3. In onSaveInstanceState() , save the state of the mTrackingLocation boolean by using
the putBoolean() method:
@Override

protected void onSaveInstanceState(Bundle outState)


{ outState.putBoolean(TRACKING_LOCATION_KEY, mTrackingLocation);
super.onSaveInstanceState(outState);

}
4. In onCreate() , restore the mTrackingLocation variable before you create the

LocationCallback instance (because the code checks for the mTrackingLocation


boolean before starting the FetchAddressTask ):
if (savedInstanceState != null) {

mTrackingLocation = savedInstanceState.getBoolean(
TRACKING_LOCATION_KEY);

5. Run the app and start location tracking. Rotate the device. A new FetchAddressTask
is triggered, and the device continues to track the location.

Solution code
WalkMyAndroid-Solution

Coding challenge
Note: All coding challenges are optional.
Challenge: Extend the location TextView to include the distance traveled from the first
location obtained. (See the distanceTo () method.)

Summary

Location information is available through the FusedLocationProviderClient .


Using location services requires location permissions.
Location permissions are categorized as "dangerous permissions," so you must include them
in the manifest and request them at runtime.
Use the getLastLocation() method to obtain the device's last known location from the
FusedLocationProviderClient .
The process of converting a set of coordinates (longitude and latitude) into a physical
address is called reverse geocoding . Reverse geocoding is available through the
Geocoder class' getFromLocation() method.
The getFromLocation() method is synchronous and may take a while to complete, so you
should not use it on the main thread.
Use a LocationRequest to specify the accuracy and frequency requirements of your
location updates.
Provided that the device settings are appropriate, use the FusedLocationProviderClient
to request periodic location updates with requestLocationUpdates() . Stop the location
updates with the FusedLocationProviderClient
removeLocationUpdates() method.
Related concept
The related concept documentation is in 7.1 C: Location services.

Learn more
Android developer documentation:

Making Your App Location-Aware

Location

Geocoder

FusedLocationProviderClient

You might also like