Hands on

Build your own fitness app with the HERE Android SDK

By Richard Süselbeck | 27 April 2016

A few weeks ago, my fiancé gave me a fitness tracker as a present. I'm guessing she did so, because I love gadgets and had been thinking about getting one for a while.

She may also have been trying to send me a not-so-subtle message.

Let's be honest, I haven't really been keeping up with my fitness program since moving to Berlin, and it's starting to show.

The key thing my shiny new toy does, is to try and encourage me to take at least 10.000 steps a day. I'm not entirely sold on the science behind it, but apparently that's the threshold where you start to see tangible health benefits.

The device can track how many steps I take and as it turns out, I walk about 8.000 steps on a normal workday. Not bad, but not quite enough to hit that magic number of 10.000. Worse, on weekends I usually manage even fewer steps, which drags down my average.

My first attempt at getting more steps out of a day, involved leaving the S-Bahn a station too early on my way home, and taking a stroll along the East Side Gallery. This worked, but it quickly got boring, not to mention that I've probably ended up in the background of hundreds of tourist selfies. (Look for me on the Instagram feed of your Japanese friends on their tour of Europe!).

I needed a better way. I needed an app.

To be more precise, I needed an app that could find interesting destinations within a certain number of steps from my current position. For example, let's say I want to take a walk on my lunch break and get about 4.000 steps done. Our office is in the middle of Berlin, so there should be plenty of interesting locations about 2.000 steps away. If I had an app that could find these locations and show me a pedestrian-friendly route to them, I would probably take more (and more interesting) walks.

Well, this sounded like a job for the HERE Android Premium SDK and so I decided to build it myself. As it turns out, you can build your own simple fitness app in less time than it takes to walk 10.000 steps! Let's have a look at how I did it. If you follow along, you will learn about displaying a map in your app, finding interesting places, displaying markers, using routing, and more.

Setting up the map

The obvious first step towards our app is to display a map. Fortunately, this is already covered in our quick start guide. Just follow the instructions in the quick start and you'll have working map in your app in no time. There's also an even faster way. The SDK download already contains a project called BasicMapSolution in the tutorials folder.  You can use this as your starting point.

Some quick tips: Remember that you need to use your own credentials in order to access the map services from your app.  If you don't have credentials yet, don't worry! We've got you covered with a free 90-day trial. Also, when you create your app during signup, remember the namespace you've chosen (or look it up in your account). Your credentials are tied to that namespaces, so make sure to use it for your app. If something doesn't work, this is the first thing to check. In addition, verify that your credentials are correctly set in AndroidManifest.xml. With that out of the way, let's get started. Here's how the end result should look.

app.gif


Getting our location

Now that your basic app is working and displaying a map, the next step is to figure out what where we are and to show this location. The PositioningManager and PositionIndicator classes will help us get this done. PositionManager provides you with information about the device's geographical location, such as current position and average speed. The PositionIndicator is a special map marker that allows you to show the current position on the map. Every map object has this indicator, but it is set invisible by default. Go ahead and create a PositioningManager object, start it and then set the PositionIndicator for our map to visible. You should do this after the map fragment has been initialized. Have a look at the code below or check out the positioning user guide for more details.

  private PositioningManager posManager;
   
  ...
   
  private void onMapFragmentInitializationCompleted() {
  // retrieve a reference of the map from the map fragment
  map = mapFragment.getMap();
   
  // start the position manager
  posManager = PositioningManager.getInstance();
  PositioningManager.getInstance();
  posManager.start(PositioningManager.LocationMethod.GPS_NETWORK);
   
  // Display position indicator
  map.getPositionIndicator().setVisible(true);
   
  ...
  }

Finding interesting places

Now that we know where we are, it's to find some interesting places to walk to. The Places features of the SDK will help us do just that. Among other things, Places allows us to search for points of interest, which happens to be exactly what we're looking for. To perform a search, we must first create a new ExploreRequest and set the options we want.

  public void findPlaces(View view) {
  // collect data to set options
  GeoCoordinate myLocation = posManager.getPosition().getCoordinate();
  EditText editSteps = (EditText)findViewById(R.id.editSteps);
  int noOfSteps;
   
  try {
  noOfSteps = Integer.valueOf(editSteps.getText().toString());
  } catch (NumberFormatException e) {
  // if input is not a number set to default of 2500
  noOfSteps = 2500;
  }
  range = ((noOfSteps * 0.762f) / 2);
   
  // create an exploreRequest and set options
  ExploreRequest request = new ExploreRequest();
  request.setSearchArea(myLocation, (int)range);
  request.setCollectionSize(50);
  request.setCategoryFilter(new CategoryFilter().add(Category.Global.SIGHTS_MUSEUMS));
   
  try {
  ErrorCode error = request.execute(new SearchRequestListener());
  if( error != ErrorCode.NONE ) {
  // Handle request error
  }
  } catch (IllegalArgumentException ex) {
  // Handle invalid create search request parameters
  }
  }

Let's have a look at the options in more detail. The search area defines the space in which we are looking for points of interest. It is defined by our current location and a range. We can easily get our current location using the PositioningManager, but the range should be based on the number of steps the user wants to walk. In this case we use an EditBox to let user input that information into the app.

However, since the range is set in meters, we also need to convert the number of steps into meters. There are various formulas available to this, some of which involve measuring your step length, but we are using a simple version, where we simply multiply the number of steps with 0.762. I have found this to be surprisingly accurate in practice.

The collection size determines how many results we want to receive per result page. We're only going to be using one page and set this to a value of 20. That should offer plenty of choices, but feel free to experiment. More important is the CategoryFilter, which allows us to select the type of results we want to get. In our case we're looking for interesting places to walk to, so "sight & museums" looks like a good choice. Feel free to experiment here as well, for example by using "leisure & outdoor". You can have a look at the search & discovery guide for more details.

Now that we created the request and set its options, we can start the search. Note that we need to provide a ResultListener, whose onCompleted() callback will be executed when the request has finished. This looks as follows.

  class SearchRequestListener implements ResultListener<DiscoveryResultPage> {
  @Override
  public void onCompleted(DiscoveryResultPage data, ErrorCode error) {
  if (error != ErrorCode.NONE) {
  // Handle error
  } else {
  // results can be of different types
  // we are only interested in PlaceLinks
  List<PlaceLink> results = data.getPlaceLinks();
  if (results.size() > 0) {
  for (PlaceLink result : results) {
  // get all results that are far away enough to be a good candidate
  if (result.getDistance() < range && result.getDistance() > (range * 0.7f)) {
  GeoCoordinate c = result.getPosition();
   
  com.here.android.mpa.common.Image img =
  new com.here.android.mpa.common.Image();
  try {
  img.setImageAsset("pin.png");
  } catch (IOException e) {
  // handle exception
  }
  MapMarker marker = new MapMarker(c, img);
  marker.setTitle(result.getTitle());
   
  // using a container to group the markers
  placesContainer.addMapObject(marker);
  }
  }
  } else {
  // handle empty result case
  }
  }
  }
  }

As you can see, the search returns us a list of PlaceLinks. However, these aren't quite the results we are looking for yet. Remember, we asked for all sights & museums within a certain range around the user. That also includes places right next to him or her. So we still need to filter the results to only show those that are far away enough to meet the user's step goal.  We can use result.getDistance() to do this.

For each remaining result, we then create a MapMarker object. MapMarkers allow you to display an icon on the map. Note two things. First, we place the markers in a MapContainer object called placesContainer. MapContainer are useful for grouping other MapObjects (like MapMarkers) and also help determine the stacking order of objects on the map. Second, in addition to setting an icon for each marker, we also give them titles based on the name of the result. We are using this later when displaying the route.

Now all we need to do is to add a button to our app, make it call the findPlaces() function and we should see our potential walking destinations displayed on the map.

Selecting a destination and calculating a route

The next step is to make it possible to select our markers. We do this by creating an OnGestureListener and overriding its onMapObjectsSelected() method. This allows us to react whenever the user taps on any map object.

  MapGesture.OnGestureListener listener = new MapGesture.OnGestureListener.OnGestureListenerAdapter() {
  @Override
  public boolean onMapObjectsSelected(List<ViewObject> objects) {
  // There are various types of map objects, but we only want
  // to handle the MapMarkers we have added
  for (ViewObject viewObj : objects) {
  if (viewObj.getBaseType() == ViewObject.Type.USER_OBJECT) {
  if (((MapObject)viewObj).getType() == MapObject.Type.MARKER) {
   
  // save the selected marker to use during route calculation
  selectedMapMarker = ((MapMarker) viewObj);
   
  // Create the RoutePlan and add two waypoints
  RoutePlan routePlan = new RoutePlan();
  // Use our current position as the first waypoint
  routePlan.addWaypoint(new GeoCoordinate(
  posManager.getPosition().getCoordinate()));
  // Use the marker's position as the second waypoint
  routePlan.addWaypoint(new GeoCoordinate(
  ((MapMarker) viewObj).getCoordinate()));
   
  // Create RouteOptions and set to fastest & pedestrian mode
  RouteOptions routeOptions = new RouteOptions();
  routeOptions.setTransportMode(RouteOptions.TransportMode.PEDESTRIAN);
  routeOptions.setRouteType(RouteOptions.Type.SHORTEST);
  routePlan.setRouteOptions(routeOptions);
   
  // Create a RouteManager and calculate the route
  RouteManager rm = new RouteManager();
  rm.calculateRoute(routePlan, new RouteListener());
   
  // Remove all other markers from the map
  for (MapObject mapObject : placesContainer.getAllMapObjects()) {
  if (!mapObject.equals(viewObj)) {
  placesContainer.removeMapObject(mapObject);
  }
  }
   
  // If user has tapped multiple markers, just display one route
  break;
  }
  }
  }
  // return false to allow the map to handle this callback as well
  return false;
  }
  };

When a user taps the map, the OnGestureListener gives us a list of all ViewObjects that were touched. In order to make sure we only consider our markers, we only look at objects that are of type MARKER. Using the selected marker, we can now start our route calculation.

Routing is done via the RouteManager class, which calculates a route based on the waypoints and RouteOptions that have been set in a RoutePlan. We thus create RoutePlan object and add two waypoints to it: our current location, and the location of the selected marker. We also set some appropriate options, in particular the transport mode. This should be set to PEDESTRIAN, as we are looking for a walkable route. Once we have finished route plan, we can create a RouteManager and start calculating the route. You can check out the Routing Calculation guide for more details.

Now that our OnGestureListener is finished, it's important that we don't forget to add it to the map. We do this in onCreate, after onEngineInitializationCompleted.

Displaying a route

We have now started our route calculation and the only thing that remains is to display the result on the map. As you will have noticed, the RouteManager calls a Listener when the route calculation is complete, so this is the perfect place to implement this.

  private class RouteListener implements RouteManager.Listener {
   
  public void onProgress(int percentage) {
  // You can use this to display the progress of the route calculation
  }
   
  public void onCalculateRouteFinished(RouteManager.Error error, List<RouteResult> routeResult) {
  // If the route was calculated successfully
  if (error == RouteManager.Error.NONE) {
  // Render the route on the map
  mapRoute = new MapRoute(routeResult.get(0).getRoute());
  map.addMapObject(mapRoute);
  int routeLength = routeResult.get(0).getRoute().getLength();
  float steps = 2 * (routeLength / 0.8f);
  String title = selectedMapMarker.getTitle();
  title = String.format(Locale.ENGLISH, "It's %d steps to %s and back!", ((int)steps), title);
  selectedMapMarker.setTitle(title);
  selectedMapMarker.showInfoBubble();
  }
  else {
  // Display a message indicating route calculation failure
  }
  }
  }

The result of the route calculation is a MapRoute. This is just another map object, similar to our MapMarkers, which means we can display it by simply adding it to the map using map.addMapObject(mapRoute).

Finally, we determine the step count (based on the actual length of the route), update the title of the MapMarker with that information and call showInfoBubble() on the marker. This displays its title in, you guessed it, an info bubble!

And that's it! You can now determine how many steps you want to take, find an interesting place in just the right distance, and get a pedestrian friendly route to it. It's not quite ready to top the Google Play charts for fitness apps, but it's good enough that I use it almost every day.  

This app has taken me places I wouldn't have otherwise discovered, including lots of cool street art here in Berlin. More importantly, my fitness tracker is happy: I always get my 10.000 steps now. You can check out a Gist of the complete code here, and get started with your free trial right here.