Unit4Lesson7
Unit4Lesson7
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.
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.
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.
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.
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.
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"/>
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 {
switch (requestCode) {
case REQUEST_LOCATION_PERMISSION:
} 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.
\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 .
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
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.
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.
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:
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:
FetchAddressTask(Context applicationContext)
{ mContext = applicationContext;
@Override
@Override
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:
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(),
}
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) {
.getString(R.string.service_not_available);
Log.e(TAG, resultMessage, ioException);
resultMessage to a string that says "Invalid coordinates were supplied to the Geocoder,"
and log the error and result message:
catch (IllegalArgumentException illegalArgumentException) {
.getString(R.string.invalid_lat_long_used);
Log.e(TAG, resultMessage + ". " +
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:
mListener = listener;
}
5. Back in the MainActivity , update the activity to implement the
// 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.
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 .
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:
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:
mRotateAnim.setTarget(mAndroidImageView);
@Override
startTrackingLocation();
} else {
stopTrackingLocation();
/**
mTrackingLocation = false;
mLocationButton.setText(R.string.start_tracking_location);
mLocationTextView.setText(R.string.textview_hint);
mRotateAnim.end();
LocationRequest .
2. Set the interval, fastest interval, and priority parameters.
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
return locationRequest;
};
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
mFusedLocationClient.requestLocationUpdates
(getLocationRequest(), mLocationCallback,
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
@Override
.execute(locationResult.getLastLocation());
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
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.
}
4. In onCreate() , restore the mTrackingLocation variable before you create the
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
Learn more
Android developer documentation:
Location
Geocoder
FusedLocationProviderClient