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
- Download the starter app for this practical, WalkMyAndroid-Starter.
- Open the starter app in Android Studio, rename the app to WalkMyAndroid, and run it.
- 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:
- Open Android Studio.
- Select Tools > Android > SDK Manager.
- Select the SDK Tools tab.
- 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.
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:
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.
- 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.
To request location permission at runtime:
- Create an
OnClickListener
for the Get Location button inonCreate()
inMainActivity
. - Create a method stub called
getLocation()
that takes no arguments and doesn't return anything. Invoke thegetLocation()
method from the button'sonClick()
method. In the
getLocation()
method, check for theACCESS_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"); } }
- In your
MainActivity
class, define an integer constantREQUEST_LOCATION_PERMISSION.
This constant is used to identify the permission request when the results come back in theonRequestPemissionsResult()
method. It can be any integer greater than0
. - Override the
onRequestPermissionsResult()
method. If the permission was granted, callgetLocation()
. Otherwise, show aToast
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(); } break; } }
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:
In
strings.xml
, add a string resource calledlocation_text
. Uselocation_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 theFormatter
documentation.In your
MainActivity
class, create a member variable of theLocation
type calledmLastLocation
.- Find the location
TextView
by ID (textview_location
) inonCreate()
. Assign theTextView
to a member variable calledmLocationTextView
. - Create a member variable of the
FusedLocationProviderClient
type calledmFusedLocationClient
. Initialize
mFusedLocationClient
inonCreate()
with the following code:mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
The
getLastLocation()
method returns aTask
that results in aLocation
object (after the Task'sonSuccess()
callback method is called, signifying that theTask
was successful).Retrieve the latitude and longitude coordinates of a geographic location from the resulting
Location
object: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 tomLastLocation
.- If the returned location is not
null
, set theTextView
to show the coordinates and time stamp of theLocation
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 theTextView
that says that the location is not available.mFusedLocationClient.getLastLocation().addOnSuccessListener( new 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); } }
- If the returned location is not
- 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:
- In Android Studio, create a new virtual device and select hardware for it.
- 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:
- 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.
- Click Location.
- Enter or change the coordinates in the Longitude and Latitude fields.
Click Send to update the fused location provider.
Clicking Send does not affect the location returned by
getLastLocation()
, becausegetLastLocation()
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:
- Start the Google Maps app and accept the terms and conditions, if you haven't already.
- Use the steps above to update the fused location provider. Google Maps will force the local cache to update.
- 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 thedoInBackground()
method. For this app, the passed-in parameter is theLocation
object. - Use the
Progress
type to mark progress in theonProgressUpdate()
method. For this app, you are not interested in theProgress
type, because reverse geocoding is typically quick. - Use the
Results
type to publish results in theonPostExecute()
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:
- Create a new class called
FetchAddressTask
that is a subclass ofAsyncTask
. Parameterize theAsyncTask
using the three types described above:private class FetchAddressTask extends AsyncTask<Location, Void, String> {}
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
doInBackground()
includes a parameter of theLocation
type, and returns aString
; this comes from parameterized types in the class declaration.Override the
onPostExecute()
method by going to the menu and selecting Code > Override Methods and selectingonPostExecute()
. Again notice that the passed-in parameter is automatically typed as a String, because this what you put in theFetchAddressTask
class declaration.Create a constructor for the
AsyncTask
that takes aContext
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.
- 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());
- Obtain a
Location
object. The passed-in parameter is a Javavarargs
argument that can contain any number of objects. In this case we only pass in oneLocation
object, so the desired object is the first item in thevarargs
array:Location location = params[0];
- Create an empty
List
ofAddress
objects, which will be filled with the address obtained from theGeocoder
. Create an emptyString
to hold the final result, which will be either the address or an error:List<Address> addresses = null; String resultMessage = "";
- 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 theLocation
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); }
- Open a
catch
block to catchIOException
exceptions that are thrown if there is a network error or a problem with theGeocoder
service. In thiscatch
block, set theresultMessage
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); }
- Open another
catch
block to catchIllegalArgumentException
exceptions. Set theresultMessage
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); }
- You need to catch the case where
Geocoder
is not able to find the address for the given coordinates. In thetry
block, check the address list and theresultMessage
string. If the address list is empty ornull
and theresultMessage
string is empty, then set theresultMessage
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); } }
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
ofStrings
. - Iterate over the
List
ofAddress
objects and read them into the newArrayList
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); }
- Create an empty
- At the bottom of
doInBackground()
method, return theresultMessage
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.
- Create a new string resource with two replacement variables.
<string name="address_text">"Address: %1$s \n Timestamp: %2$tr"</string>
- Create an interface in
FetchAddressTask
calledOnTaskCompleted
that has one method, calledonTaskCompleted()
. This method should take a string as an argument:interface OnTaskCompleted { void onTaskCompleted(String result); }
Add a parameter for the
OnTaskCompleted
interface to theFetchAddressTask
constructor, and assign it to a member variable:private OnTaskCompleted mListener; FetchAddressTask(Context applicationContext, OnTaskCompleted listener) { mContext = applicationContext; mListener = listener; }
- In the
onPostExecute()
method, callonTaskCompleted()
on themListener
interface, passing in the result string:@Override protected void onPostExecute(String address) { mListener.onTaskCompleted(address); super.onPostExecute(address); }
- Back in the
MainActivity
, update the activity to implement theFetchAddressTask.OnTaskCompleted
interface you created and override the requiredonTaskCompleted()
method. - 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())); }
- In the
getLocation()
method, inside theonSuccess()
callback, replace the lines that assigns the passed-in location tomLastLocation
and sets theTextView
with the following line of code. This code creates a newFetchAddressTask
and executes it, passing in theLocation
object. You can also remove the now unusedmLastLocation
member variable.// Start the reverse geocode AsyncTask new FetchAddressTask(MainActivity.this, MainActivity.this).execute(location);
- At the end of the
getLocation()
method, show loading text while theFetchAddressTask
runs:mLocationTextView.setText(getString(R.string.address_text, getString(R.string.loading), System.currentTimeMillis()));
- 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 itsonLocationResult()
method. TheonLocationResult()
method is where your app receives location updates. You do this step in 4.3 Create the LocationCallback object. - Call
requestLocationUpdates()
on theFusedLocationProviderClient
. Pass in theLocationRequest
and theLocationCallback
. 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:
- In
MainActivity
, declare the member variablesmAndroidImageView
(of typeImageView
) andmRotateAnim
(of typeAnimatorSet
). In the
onCreate()
method, find the AndroidImageView
by ID and assign it tomAndroidImageView
. Then find the animation included in the starter code by ID and assign it tomRotateAnim
. Finally set the AndroidImageView
as the target for the animation:mAndroidImageView = (ImageView) findViewById(R.id.imageview_android); mRotateAnim = (AnimatorSet) AnimatorInflater.loadAnimator (this, R.animator.rotate); mRotateAnim.setTarget(mAndroidImageView);
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."
Refactor and rename the
getLocation()
method tostartTrackingLocation()
.- Create a private method stub called
stopTrackingLocation()
that takes no arguments and returnsvoid
. - Create a boolean member variable called
mTrackingLocation
. Boolean primitives default tofalse
, so you do not need to initializemTrackingLocation
. Change the
onClick()
method for the button'sonClickListener
:- If
mTrackingLocation
isfalse
, callstartTrackingLocation()
. - If
mTrackingLocation
istrue
, callstopTrackingLocation()
.@Override public void onClick(View v) { if (!mTrackingLocation) { startTrackingLocation(); } else { stopTrackingLocation(); } }
- If
- At the end of the
startTrackingLocation()
method, start the animation by callingmRotateAnim.start()
. SetmTrackingLocation
to totrue
and change the button text to "Stop Tracking Location". - In the
stopTrackingLocation()
method, check if the you are tracking the location. If you are, stop the animation by callingmRotateAnim.end()
, setmTrackingLocation
to tofalse
, change the button text back to "Start Tracking Location" and reset the locationTextView
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 thesetFastestInterval()
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:
- Create a method called
getLocationRequest()
that takes no arguments and returns aLocationRequest
. - 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:
- At the bottom of
onCreate()
, create a newLocationCallback
object and assign it to a member variable calledmLocationCallback
. - 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:
- To request periodic location updates, replace the call to
getLastLocation()
instartTrackingLocation()
(along with theOnSuccessListener
) with the following method call. Pass in theLocationRequest
andLocationCallback
:mFusedLocationClient.requestLocationUpdates (getLocationRequest(), mLocationCallback, null /* Looper */);
- In the
stopTrackingLocation()
method, callremoveLocationUpdates()
onmFusedLocationClient
. Pass in theLocationCallback
object.mFusedLocationClient.removeLocationUpdates(mLocationCallback);
- In the
onLocationResult()
callback, checkmTrackingLocation
. IfmTrackingLocation
istrue
, executeFetchAddressTask()
, and use theLocationResult.getLastLocation()
method to obtain the most recentLocation
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()); }
- In
onTaskComplete()
, where the UI is updated, wrap the code in anif
statement that checks themTrackingLocation
boolean. If the user turns off the location updates while theAsyncTask
is running, the results are not displayed to theTextView
. - Run the app. Your app tracks your location, updating the location approximately every ten seconds.
places_gps_data.gpx
GPX file, which contains several locations:
- Download the
places_gps_data.gpx
file. - Open your emulator, click the ... icon at the bottom of this vertical settings menu, and select the Location tab.
- Click Load GPX/KML and select the downloaded file.
- 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 Activity
is not in focus (in the paused state) and resume location updates when the Activity
regains focus:
- Override the
Activity
object'sonResume()
andonPause()
methods. - In
onResume()
, checkmTrackingLocation
. IfmTrackingLocation
istrue
, callstartTrackingLocation()
. - In
onPause()
, checkmTrackingLocation
. IfmTrackingLocation
istrue,
callstopTrackingLocation()
but setmTrackingLocation
totrue
so the app continues tracking the location when it resumes. - 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.
- Override the
Activity
object'sonSaveInstanceState()
method. - Create a string constant called
TRACKING_LOCATION_KEY
. You use this constant as a key for themTrackingLocation
boolean. - In
onSaveInstanceState()
, save the state of themTrackingLocation
boolean by using theputBoolean()
method:@Override protected void onSaveInstanceState(Bundle outState) { outState.putBoolean(TRACKING_LOCATION_KEY, mTrackingLocation); super.onSaveInstanceState(outState); }
- In
onCreate()
, restore themTrackingLocation
variable before you create theLocationCallback
instance (because the code checks for themTrackingLocation
boolean before starting theFetchAddressTask
):if (savedInstanceState != null) { mTrackingLocation = savedInstanceState.getBoolean( TRACKING_LOCATION_KEY); }
- 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
Coding challenge
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 theFusedLocationProviderClient
. - 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 withrequestLocationUpdates()
. - 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: