Intro
For this lab you will be developing a new GPS recording application called WalkAbout. The purpose of the application is to allow users to record their GPS location information as they travel. While the application records the user's GPS data, it displays it back to the user in the form of a path drawn on a Google Map. While recording data, the user can launch a Camera activity that will capture and store pictures on a storage device. When finished recording, the application gives the user the option of storing the current GPS data as a private application file to be loaded and displayed at a later time. The application will populate the map with shapes and markers to indicate actions taken, and be able to save and load this data to repopulate the map at a later time.
Objectives
At the end of this lab you will be expected to know:
Activities
For this lab you will be working with a brand new application, completely independent from the previous labs. Over the course of the lab, you will be iteratively refining and adding functionality to the WalkAbout app. With each iteration you will be improving upon the previous iteration's functionality.
You'll start by setting up and familiarizing yourself with the Eclipse project. You will then register for a Google Maps API key and begin incrementally developing the main map-viewing Activity. These first few exercises will have you display a map and the user's current position. Next, you will add functionality to record the user's GPS location by registering for and receiving data from what is known as the GPS Location Provider. After that, you will implement a Camera activity for taking pictures and saving them to the SD card. In the final section, you will save a user's GPS path to a file that is private to the application, and allow the application to restore itself from the file as well.
IMPORTANT:
You will be given a Skeleton Project to work with. This project contains all of the Java and resource files you will need to complete the lab. Some method stubs, member variables, and resource values and ids have been added as well. It is important that you not change the names of these methods, variables, and resource values and ids. These are given to you because there are unit tests included in this project as well that depend on these items being declared exactly as they are. These unit tests will be used to evaluate the correctness of your lab. You have complete access to these test cases during development, which gives you the ability to run these tests yourself. In fact, you are encouraged to run these tests every so often to ensure that your application is functioning properly.
IMPORTANT:
It is highly recommended that you run this application on a physical Android device that has access to GPS and a camera. There are several methods to simulate GPS on an emulator (some of which are covered in part 3.2.2 below) but this lab assumes you will walk around with a physical device plugged into GPS instead of simulating GPS.
IMPORTANT:
Go to the Google Play Store and install Google Maps if your device doesn't have it installed yet. This lab will assume that you have Google Maps installed; it will not cover how to check to see if Google Maps is installed on the test device. For more information on how to make the application check for a Google Maps installation, see here.
Contents
A table of contents will be added soon.
1. Setting Up
To begin, download and extract the skeleton project for the WalkAbout application. Click here to download the skeleton project.
Next you will need to set up an Android project for this app. Since the skeleton project was created in Eclipse, the easiest thing is to import this project into Eclipse.
Note: This project contains errors from the get-go. You will fix some of them right now. Feel free to clean and rebuild the project throughout these steps if you think your project should be compiling properly.
If you haven't set up your project to use ActionBarSherlock (ABS), do so now. If you already have the library project for ABS, you should be able to skip this step.
Next you will need to set the correct build target. In the Android SDK Manager (available from Windows menu in Eclipse), ensure that you have Google APIs for all of the versions of Android that run on the devices you wish to test this application on (minimum API of 10). You will at least want the latest Google APIs package (17 at the time of writing this lab, this is the default build target in the stub project).
Once you have the Google APIs add-on installed, you need to use it as the target for your project. The project is currently set to API 17, but this is incorrect. You want to target the latest Google APIs version, or at least the version your test device is compatible with.
After all this, there will still be errors. You will fix them in section 2 and hopefully see them fade away slowly.
Note: You cannot attempt to run this application successfully until step 2.1.4.
1.2 Familiarize Yourself with the Project
The project is extremely small. It contains a single Java class file and a single XML layout file which you will have to implement. WalkAbout.java will contain the definition for the main WalkAbout Activity class. This is the class that will display the map and the user's recorded path. The WalkAbout class makes use of a very simple XML layout file called map_layout.xml which you will have to fill in.
2. Using Google Maps
In this section of the lab you will be working extensively with the Google Maps package. You will follow numerous setup steps to be able to incorporate a Google Maps map in your application, and then provide basic map utilities such as the My Location button and Zoom controls. We will not be calling the Google Maps app directly; rather, we will implement our own MapFragment in our application and use Google's services to maintain it.
Before we dive into the code, it is important to distinguish between the Google Maps API and the Google Maps Android API. The latter is a child of the former, and is maintained specifically for using Google Maps services in Android devices. The term "Google Maps API" represents a collection of APIs related to using Google Maps services. The primary Google Maps API is the JavaScript web-based version that allows for embedding and manipulating Google Maps in web pages. This is the version that many sites, including maps.google.com, use to display Google Maps in a web browser (for example, if you go to maps.google.com and view the page source, you will see a ton of JavaScript). The JavaScript Google Maps API (for web) is currently up to version 3 (v3), and the Google Maps Android API is currently up to version 2 (v2). Keep this distinction in mind when searching for Google Maps API information.
2.1 Displaying a Map
You will begin by implementing the functionality necessary to display a full-screen map in the WalkAbout Activity class. The layout for the WalkAbout Activity should be specified in the res/layout/map_layout.xml file, which you will have to fill in. This will require you to use the SupportMapFragment class. In order to do this, you will have to register the debug keystore (that Eclipse uses to run your applications) with Google in order to receive a Maps API Key. When finished, your application should appear as depicted in the figure below:
Map of the world, centered. Device: Nexus 7
For more information and examples on working with the Google Maps Package, see the documentation site.
2.1.1 Get a Google Maps Android API Key
In order to use the Google Maps Android API and classes you will have to sign into your Google account, then register the keystore you use with the appropriate service in the Google APIs Console. Once your keystore is registered, you will be provided with a Google Maps Android API key that can be used with any application signed by your keystore. We will be using Google Maps Android v2, the current Android Maps version at the time of rewriting this lab.
Important: Google Maps v2 does not reuse the setup process from v1, the latter of which has been deprecated at the time of writing this lab.
Every distribution of the Android Development Toolkit comes with a debug keystore that is used to sign your application when it is launched and run from Eclipse. This is how you are able to run your applications on a device or emulator without signing it yourself. For the purposes of this lab, you need only register with a debug keystore. If you end up releasing an application to the Google Play Store, however, you will need to register an actual keystore to officially sign your application correctly before publishing it in the store.
Note: If you get stuck at any point in this or the next substep, try to pinpoint problems using the startup guide or the Map Fragment Quick Start document.
Open these instructions and read the first few paragraphs of information. Now follow the steps listed below while going through the instructions at the linked page:
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<uses-library
android:required="true"
android:name="com.google.android.maps"
/>
2.1.2 Add Google Play and Android Support
Having the key, permissions etc. set up is only half the battle. We now need to set up Eclipse and our project so that it uses the Google Play Services development kit and the Android Support Library (explained below).
Our application requires a dependency on the Google Play Services project, so add it as a library project as follows:
The Google Maps Android API v1 used MapActivity to display maps. However, the Google Maps Android API v2 uses a different (and quicker and more flexible) way of rendering maps: they are now contained inside MapFragments. However, MapFragment only works for API 12 or higher. Using the Android Support library, we can utilize the MapFragment functionality through a class called SupportMapFragment that works for API versions below 12.
Note: There is already a JAR file for this in the libs folder. However, it is not guaranteed to be up-to-date.
Now we have finally set up our environment to work with Google's map services and obtained our API key. Let's delve into development! Keep the instructions page open, as it will still be our reference.
2.1.3 Fill in the MapView XML Layout File
The main WalkAbout MapActivity class uses a very simple layout consisting of a single GoogleMap variable. This variable will utilize the (Support)MapFragment element that you set here in order to render a (you guessed it) Google Map in your application.
Implement this layout by filling in the map_layout.xml file:
2.1.4 Display the Map
The WalkAbout MapActivity will display the map. All initialization relating to the layout should be done in WalkAbout.initLayout(), which gets called by onCreate():
You should be now be able to run your application and see a Google Map. The Zoom Controls should be displayed in the bottom-right corner of the screen. Scrolling the map via touch should move the map around and load new map tiles.
The code to display a map was simple compared to the setup, wasn't it? Now let's move on to the bulk of the lab.
2.2 Adding My Location and Compass
In v1 of the Google Maps Android API, we had to use Overlays to draw objects such as an indicator for the user's location and a compass over a MapView, which represented the map. However, in v2 we are treated to simpler ways of adding these objects over the map via accessing the UISettings of a GoogleMap object.
You will add indicators for the user's location and the compass on your map. When finished, your application should appear as depicted in the figure below:
Map with My Location layer enabled.
2.2.1 Display Your Location and Compass
Enable the map to display your current location and the compass. Note that the compass only appears when you turn the map using gestures on the screen, as the orientation is defaulted to North pointing directly upwards, East pointing directly right, etc. The compass points North with red and South with white.
You should be able to run your application and see a Google Map that will point and zoom to your current location if you press the My Location button in the corner, as well as a compass displaying your current orientation if the map is turned via gesturing in the other corner.
The compass only shows when the map's orientation is changed from the default (use a two-finger twist gesture).
There are more ways to allow the user to interact with your map. For more information, visit the Interacting with the map documentation.
2.3 Initialize the WalkAbout Options Menu
The WalkAbout Activity has an Options Menu that will display five different menu items with no submenu items. Create the Options Menu as follows. Do not worry about adding actual menu functionality yet, since you will do that later.
Add a bare-bones onOptionsItemSelected(...) method:
Now there's a menu! (Click image for full size)
Testing the menu items' responses. The Start menu item was just pressed.
3. Using Location Services
The Android System provides services for determining the current location of the device. The framework for working with these location based services lives under the android.location package. A number of useful classes live inside this package:
In general, you usually query the LocationManager for the information you are seeking (this should sound familiar from lab 4, where you queried the LoaderManager several times). Just to name a few of the LocationManager's powers, you can use it to check the current status of a LocationProvider, you can check the last known location reported by a LocationProvider, and you can register to receive updates from a LocationProvider. To receive updates on location information directly from a LocationProvider, you need to implement the LocationListener interface and register yourself with the LocationProvider.
In the subsections that follow, you will add functionality to monitor and enable the GPS LocationProvider in the WalkAbout class. You will record changes in location as the user's path. Finally, you will display the path the user took on the map using a series of line segments and circles.
3.1 Enabling the GPS LocationProvider
It is entirely possible that the user has disabled the GPS hardware. Before you can monitor and record data from the GPS LocationProvider, the GPS hardware must be enabled. In this particular subsection, you will begin by querying the LocationManager for whether the GPS hardware is enabled on the device. If GPS is not enabled, you will allow the user to launch the Location Settings Activity to enable it from the "Enable GPS" Options MenuItem. After the user is done editing the Location Settings, they can return to the WalkAbout Activity by hitting the back button. When the GPS hardware is enabled the Recording menu item will become enabled and the "Enable GPS" menu item will no longer be visible in the Action Bar.
This is how your application should behave after finishing this substep (click image for full size):
Single use case for step 3.1.
3.1.1 Initialize LocationManager
In various parts of the WalkAbout class, you will request location information from the LocationManager attached to this application context. The WalkAbout class will store a reference to this LocationManager object in the m_locManager member variable for convenience.
Initialize this member variable in the WalkAbout.initLocationData() method:
3.1.2 Dynamically Update Options Menu
The Options menu should display the "Start/Stop" MenuItem as disabled if the GPS Location Provider is disabled. Additionally, the Options menu should NOT display the "Enable GPS" MenuItem at all if the GPS Location Provider is enabled.
Each time the Options menu is displayed, you should check to see if the GPS Location Provider is enabled and update the "Start/Stop" and "Enable GPS" MenuItems accordingly.
You should be able to run your application and test that the "Start/Stop" and "Enable GPS" MenuItems are enabled and invisible respectively when the GPS Provider is enabled. They should also be disabled and visible respectively when the GPS Provider is disabled.
3.1.3 Implement "Enable GPS" MenuItem
When the GPS Location Provider is disabled, the user will be able to launch the settings activity to enable the GPS Location Provider from the "Enable GPS" Options MenuItem.
Fill in the logic for the "Enable GPS" MenuItem in onOptionsItemSelected(...):
You should be able to run your application and test that you can launch the settings activity to enable the GPS provider from the "Enable GPS" MenuItem. When you hit the Back button you should return to the WalkAbout Activity. However, you'll notice that the menu isn't refreshing properly to reflect GPS enabling. We'll fix that in the next part.
3.1.4 Handle the Location Settings Activity Result
After the user is done with the Location Settings Activity and returns to the WalkAbout Activity, you need to make the menu reflect that change. On older devices that relied on a physical menu button, this refresh happened automatically since the menu was always (re)constructed when the menu button was pressed (since the menu was hidden otherwise), and therefore changes to the menu in onPrepareOptionsMenu(...) would be invoked. However, the Action Bar is always onscreen and it is not reconstructed upon returning to the WalkAbout application. Since you called the Activity.startActivityForResult(...) method to launch the Location Settings Activity, the WalkAbout.onActivityResult(...) method will be called.
The requestCode parameter in this method will contain the value that you passed into the startActivityForResult(...) method. You should test this parameter to see if it matches the WalkAbout.ENABLE_GPS_REQUEST_CODE constant. You test the requestCode because this same method will be called when any Activity that you launch returns a result (You must test this value because you will be launching another activity later and you want to be able to identify which Activity is returning a result).
Override and fill in the onActivityResult(...) method in WalkAbout:
You should now run your application with all Location Providers disabled and make sure that the Action Bar menu behaves appropriately once you enable the GPS LocationProvider via changing the GPS settings (see image above for guidance).
3.2 Retrieving GPS Location Information
In this subsection, you will implement the functionality necessary to monitor and record GPS location information. You will monitor GPS location information by making the WalkAbout Activity register itself with the GPS LocationProvider. By doing this, the WalkAbout Activity will be notified by the GPS LocationProvider when the location changes. However, before the WalkAbout Activity class can register itself with the GPS LocationProvider, it must implement the android.location.LocationListener interface. You will start by making the WalkAbout Activity implement this interface.
You will then implement a "Start/Stop" MenuItem that will toggle the WalkAbout Activity between actively-recording and recording-stopped states. While in the actively-recording state, the WalkAbout Activity will be registered to receive updates from the GPS LocationProvider. As the WalkAbout Activity receives notices about location changes, it will re-center its MapView about the new location. It will also store changes in location as latitude-longitude coordinates in an ArrayList. Additionally, while in the actively-recording state the "Start/Stop" MenuItem will display the word "Stop" to indicate that clicking on the MenuItem will cause the WalkAbout Activity to switch to the recording-stopped state.
While in the recording-stopped state, the WalkAbout Activity will no longer be registered to receive updates from the GPS LocationProvider. Additionally, while in the actively-recording state the "Start/Stop" MenuItem will display the word "Start" to indicate that clicking on the MenuItem will cause the WalkAbout Activity to switch to the recording-stopped state. Note that when the Application starts up, it is by default in the recording-stopped state. When the user switches from the recording-stopped state to the actively-recording state, the ArrayList used to record changes in location should be cleared.
3.2.1 Implement LocationListener Interface
The WalkAbout Activity class will receive updates from the GPS Location Provider by registering itself with the Provider. In order to register with the Provider, the WalkAbout Activity must implement the LocationListener interface. This interface provides a set of callback methods that the Provider will call when the location changes, the Provider is enabled, the Provider is disabled, or when the status of the Provider changes.
In particular, you will monitor and record location changes, and stop recording in the event that the Provider is disabled for some reason. The WalkAbout class records location changes in an ArrayList<LatLng> member variable named m_arrPathPoints. It also maintains the current recording state (whether it is currently recording or not) in a boolean member variable named m_bRecording.
Perform the following initializations in the initLocationData() method:
Make the WalkAbout class implement the LocationListener interface:
3.2.2 Implement "Start/Stop" MenuItem
When the "Start/Stop" MenuItem is clicked, the recording state should be toggled. If the activity is currently recording, then the activity should stop recording. Conversely, if the activity is not recording, then the activity should start recording.
The functionality for setting the recording state will be encapsulated in the WalkAbout.setRecordingState(...) method. Passing in a value of true indicates that the activity should start recording and passing in a value of false indicates that the activity should stop recording.
Fill in the "Start/Stop" portion of the onOptionsItemSelected(...) method so that it toggles the recording state by calling WalkAbout.setRecordingState(...) with the proper value.
Fill in the setRecordingState(...) method:
If you run your application, it should now record GPS Location changes. You can test this by adding a Toast notification to the WalkAbout.onLocationChanged(...) method that prints out the new latitude and longitude.
Below is a picture of the app running with a Toast appearing onscreen after pressing Start and moving the testing device to a new location (click image for full size):
Make sure Stop displays on the menu item after you press Start, and vice-versa.
You can check to make sure you're receiving proper latitude and longitude values by using an online latitude/longitude to location converter, such as the one found here, and comparing them to the printed Toast values.
Note: Although we are assuming you will run this application on a physical device (the recommended method since walking around is optimal for this lab), if you are running this on the emulator you can simulate location changes on the emulator two different ways. You can do this from the DDMS perspective in Eclipse, or from the Console. For instructions on how to open up a console, see the Android reference on Using the Console.
3.3 Shapes
Break out the crayons, because it's time to doodle all over the map! If you're familiar with Google Maps, you know that it is capable of showing paths, markers or icons at certain locations on the map (such as when you Get Directions from point A to point B). You will add functionality to your application to visually trace the path you take as WalkAbout records your traveling path. It's nice to constantly see where we are on the map, but it would be even better if we could see where we've gone from the beginning of recording until the end. This is astoundingly simple compared to v1 of the Google Maps API thanks to the advent of Shapes.
The two shapes we will be using are Polylines and Circles.
3.3.1 A Polyline Path
A Polyline is a series of lines that are drawn between certain LatLng points on a Google Map. Why are they special? Because in v1 of the Android Maps API, we had to pull all sorts of strings to draw lines in the correct order on the map, with a specific color, location and projection. For those with computer graphics experience, it was akin to drawing a simple line between points (such as GL_POINTS in OpenGL): you had to get the appropriate projection and perform some conversions while looping over each point in the line to draw it on the map. In fact, have a taste of what the draw method for drawing just one line used to look like for this lab:
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
Projection projection = mapView.getProjection();
int ndx = 0;
if (m_arrPathPoints != null && m_arrPathPoints.size() > 0) {
projection.toPixels(m_arrPathPoints.get(0), m_point);
m_paint.setARGB(255, 0, 255, 0);
m_paint.setAntiAlias(true);
m_rect.set(m_point.x-START_RADIUS, m_point.y-START_RADIUS, m_point.x+START_RADIUS, m_point.y+START_RADIUS);
canvas.drawOval(m_rect, m_paint);
for (ndx = 1; ndx < m_arrPathPoints.size(); ndx++) {
projection.toPixels(m_arrPathPoints.get(ndx), m_point2);
projection.toPixels(m_arrPathPoints.get(ndx-1), m_point);
m_paint.setARGB(255, 255, 0, 0);
m_paint.setStrokeWidth(PATH_WIDTH);
canvas.drawLine(m_point.x, m_point.y, m_point2.x, m_point2.y, m_paint);
}
}
}
This is a method that was in an entirely different class that had to be integrated into WalkAbout.java as an Overlay. Each line had to be looped over and drawn directly on the canvas that existed above the Google Map. And this is just the drawing process, the above code does not highlight the process of incorporating the lines into (or rather, over) the map. Simply put, this v1 method of drawing lines over a Google Map is horrible, but we had little choice.
In v2, the drawing and incorporation process has been simplified down to just a few lines of code in the WalkAbout class. But it is not entirely straightforward: those few lines need to be implemented in specific methods inside WalkAbout.java.
In initLayout():
In onLocationChanged(...):
Run your application and gaze in wonder at the trail of green that follows you everywhere you go (click image for full view):
It's that simple.
3.3.2 Circle Points
That was almost too easy, so let's also add a second Shape as well: Circles. You can read more about Circles here. You are going to draw a circle at each point you move to on the map. You can also guess how elegant and easy implementing these bad boys will be...Circles are implemented similarly to Polylines, only they are even simpler. Terrifying, isn't it?
In onLocationChanged(...):
Run your application from the beginning, start recording your path and walk around. You'll now see circles at each point that you visited (click image for full view):
Zoomed in extra-close to show each circle's fill color.
One more thing: we have to properly wipe the map of Polylines and Circles if we stop and start recording again. Because Polylines are tied directly to the list of LatLng points we maintain, and we clear that list in setRecordingState(...), we don't have to worry about having to wipe the map of Polylines. Circles, however...
In setRecordingState(...):
Now we have a clear visual aid that shows our path as we walk it. Now let's move onto something tougher: the camera. No, not the camera looking down through a projection at the Google Map, the camera in your Android device that lets you take pictures.
4. Camera & Picture Storage
It's picture time! Android allows applications direct access to the Camera Hardware. This means we can immediately invoke Camera features in any applications we create. If you're a user of Facebook or other applications that allow for picture-taking, you are familiar with how smooth Camera integration is (that is to say, very smooth).
Below is how your application will look and function after completing this step (click image for full view):
Several in-between steps omitted for brevity's sake.
Note: the Nexus 7 has no built-in file manager, so the first and last screens show the device's files in a file manager app.
4.1 Utilizing the Camera
By reading the Camera developer guide, you will see that there are two primary ways to take pictures: By accessing the Camera API, or by starting and capturing the result of a camera Intent. We will implement the latter. You know what that means, don't you? That's right, it's the return of URIs! You know from Lab 4 that URIs are used to access content (specifically using content URIs). Just like how the Content Provider, well...provided content, the Camera can provide us with picture-taking services if we request it.
This time around, we still need to store our application's data (the pictures) somewhere. However, a database is a terrible choice for storing images (and do we really need to integrate a database for this problem?). When you take a picture, where do you normally store images? Some form of physical internal memory, right? Let's cut to the chase--the SD card is the best place to store such images since there are known ways of loading data from and unloading data to an SD card. However, not all devices have the capacity for an SD card. The earliest model of the Nexus 7, for instance, has no such capability. Have no fear--we will be using a method for retrieving a device-specific storage path. That way, if there is an SD card that can be accessed and written to then it will be used, but if not then some other device-specific path will be written to instead. However, this means you may have a different path or method structure.
First, however, we want to set up for our Camera intent.
In the Manifest file:
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
In WalkAbout:
4.1.1 Media Files
When you take a picture, the picture gets saved somewhere as something. We'll get to the somewhere later, but for now let's focus on the image file itself, the something.
Android is capable of handling a variety of media file formats. Naturally Android supports various image formats, including common ones such as JPEG and PNG. We won't go into image format details. Instead, we're going to just pick the JPEG picture file format and use it in our application. The process of taking pictures in our application will go as follows:
-Run application
-Start Recording
-Click "Take Picture" menu item
-Launch Camera via intent
-Take a picture
-Exit Camera
-Handle Camera return
-Save picture to storage
We will start by creating helper methods for our application to properly save an image in JPEG (.JPG) format and create a URI to pass to the Camera Intent. See the documentation as a general guide (not word for word) for the following steps.
In WalkAbout:
4.1.2 Launching the Camera
Now invoking the Camera using an Intent is a piece of cake thanks to the above helper methods.
We still have to take care of business after the Camera activity exits before we can test picture-taking functionality.
4.2 Handling the Camera's Exit
Assuming the Camera works properly once we invoke it (a fairly safe assumption to make), we now have to pick up where it leaves off when it exits.
In onActivityResult(...):
Run your application and make sure that it functions as outlined in the storyboard image at the top of section 4. If not, read on for possible fixes to common problems.
4.2.1 Troubleshooting
If you are having trouble getting the Camera functionality working, there are some possible fixes. Consult the issues and solutions below for more details if you are having trouble.
1. Devices that run older versions of Android may have issues with the media storage directory path if it is formatted with the timestamp using DateFormat. Try removing spaces or other unwanted characters from the path obtained from mediaStorageDir in getOutputMediaFile(...) before assigning a value to mediaFile.
2. Depending on your test device, launching the Camera may result in WalkAbout being destroyed due to orientation change or other reasons. If this is the case, you can either prevent configuration or orientation changes, or you will need to save and load your application's data before and after your activity is destroyed and recreated, respectively.
You are tasked to do this on your own. If you choose to do the latter you can use your knowledge of Instance State from Lab 4, or by using other means. Several hints follow:
5. Markers
So we have a working camera feature, great! However, right now it has nothing to do with the map whatsoever. Let's change that by marking each location you take a picture at on the map with a timestamp. We'll do this using Markers. Markers are added similarly to Circles, but they can contain much more information inside them. To keep things simple, we'll just display each marker with a title that contains the timestamp.
In WalkAbout:
Run your application, and now whenever you take a picture at a given location WalkAbout should mark it on the map. Touching a marker gives it focus and causes it to display its title if it has one: (click image for full size):
You can touch the map to hide a marker's title.
6. Private Application Files
The home stretch! Our application is nearing completion, but if the previous lab taught us anything, we want to preserve as much data as possible. In the case of WalkAbout, this corresponds to the last recorded path and all of its markers.
Android allows you to Create, Write, Read and Delete local files. These files become private to the application that created them by default. You have the option of overriding this privacy mechanism, allowing them to be shared with other applications. You will save certain map data to a private WalkAbout file and then load it later, refreshing your app data and the Google Map in the process.
The following shows how your application should behave after implementing saving and loading (click image for full size):
6.1 Creating, Writing, & Deleting a File
You will now implement WalkAbout Activity's "Save" menu item logic. When selected, this should only make a single call to the WalkAbout.saveRecording() method. The functionality necessary to save the current recorded path should be composed in the WalkAbout.saveRecording() method. You should fill in this method so that it properly writes out only the contents of m_arrPathPoints and m_arrPicturePoints to a private application file. You should only ever create one file and it should be truncated/cleared each time you write to it.
The format of the file is simple. It will contain two lines. The contents of m_arrPathPoints will be written into a single continuous line, and then on the next line will be the contents of m_arrPicturePoints.
On the first line, the latitude and longitude of each point in m_arrPathPoints should be written out in that order, separated by a comma, and no spaces between them. Each point should be separated by a semicolon, and no spaces between them. So for example:
Given:
m_arrPathPoints = [(lat1,lng1),(lat2,lng2),(lat3,lng3)]
The line in the file should look like:
lat1,lng1;lat2,lng2;lat3,lng3;
On the second line, the latitude, longitude and title (timestamp) of each point in m_arrPicturePoints should be written out in that order, separated by a comma, and no spaces between them. Each point should be separated by a semicolon, and no spaces between them. So for example:
Given:
m_arrPicturePoints = [(lat1,lng1,title1),(lat2,lng2,title2)]
The line in the file should look like:
lat1,lng1,title1;lat2,lng2,title2;
If the Save was performed successfully, then a Toast notification should be displayed containing the R.string.saveSuccess resource string. If an exception is thrown you should display a Toast notification containing the R.string.saveFailed resource string. If there is no data to save, as is the case on the initial loading of the application, then a Toast notification should be displayed containing the R.string.saveNoData resource string.
You are tasked to do this on your own. However, you should make use of the following hints:
6.2 Reading a File
Your last task is to implement the WalkAbout Activity's "Load" menu item logic. When selected, this should only set the recording state to false and make a call to the WalkAbout.loadRecording() method. You should fill in the loadRecording() method so that it properly initializes m_arrPathPoints and m_arrPicturePoints to contain only the data in the file that saveRecording() writes out, and then repopulate your Google Map with all of the circles, the polyline, and markers from before. Once finished, the path loaded from the file should be displayed exactly as it was when it was first recorded.
If there is nothing to be loaded (before Save has been pressed), then a Toast notification should be displayed containing the R.string.loadNoFile resource string. If the Load was performed successfully, then a Toast notification should be displayed containing the R.string.loadSuccess resource string. If an exception is thrown, you should display a Toast notification containing the R.string.loadFailed resource string.
You are to do this task on your own. However, you should make use of the following hints:
Run your application and make sure that the saving and loading functionality is working properly. When you demo to Dr. Janzen, be sure you have a way to show the picture that was taken. For instance, you might install a file manager (e.g. Astro File Manager from Play Store), and know the path for where your app is storing images (e.g. /storage/emulated/0/Pictures/WalkAbout).
In conclusion...
In this lab you created an application that tracks a user in an interactive Google Map using various Shapes. You summoned the device's camera to take pictures and store them internally in phone storage, and the user's picture-taking location was recorded on the map in the form of a Marker with the picture timestamp. You also made it so the user can save their recorded path and then load it into the map later.
As you have seen and can likely guess, there is much more you can do with Google Maps for Android. This lab offered only a small sample of the power that map fragments have. The Android Google Maps API v2 is a simple, elegant and powerful API for map-related activities. You are highly encouraged to further explore Google Maps for Android beyond the scope of this lab. If you choose to do so, start at the Maps documentation homepage here or refer to previously visited documentation pages that you visited while working through this lab. Happy Mapping!
7. Deliverables
To complete this lab you will be required to:
Primary Authors: James Reed and Kennedy Owen
Adviser: Dr. David Janzen