Sorry, you need to enable JavaScript to visit this website.

Time to leave

Purpose

This article shows how to use the TomTom Maps SDK for Android to create an application that calculates the time to leave previous to reaching the destination point at the desired time.

Getting Started

A user can plan their route to reach the destination by using the following:

  • Departure and destination locations.
  • Travel mode.
  • The time they want to reach the destination.
  • Optionally preparation time before the departure.

Next:

  1. The route is calculated and displayed on a map.
  2. The time to leave timer starts to count down.
  3. The route is recalculated each minute to check for any traffic delays.
  4. In case of any changes in traffic, the countdown timer gets updated and the user is notified.

This article does not cover the whole of the application's code description, only the code sections where the TomTom SDK is used.

You can find and clone the working application on our GitHub. Have fun with modifying it and testing our APIs! If you want to follow a step-by-step application creation from scratch, check out the “Search Along the Route” tutorial.

The following sections describe the usage of:

  • The TomTom LocationSource API to get the user's current location
  • The TomTom Search SDK module to search for addresses and geocode map positions
  • The TomTom Map SDK module to display a map
  • The TomTom Routing SDK module to plan routes

Prerequisites:

  1. Clone a GitHub repository, with the source code of the TimeToLeave application:

    git clone https://github.com/tomtom-international/tomtom-use-case-time-to-leave-android.git
  2. If you do not have an API key follow these steps:

    Create an API key now

    To create a new API key, first log in or register on the portal.

    Next, create a new application in your Dashboard:

    Choose all the APIs:

    You are now set up. Click on your newly created app and copy your API key:

  3. Copy and paste the API key into your AndroidManifest.xml inside the <application> tag:
    <meta-data 
        android:name="OnlineMaps.Key" 
        android:value="_______YOUR_KEY_GOES_HERE_______" /> 
    <meta-data 
        android:name="OnlineSearch.Key" 
        android:value="_______YOUR_KEY_GOES_HERE_______" /> 
    <meta-data 
        android:name="OnlineRouting.Key" 
        android:value="_______YOUR_KEY_GOES_HERE_______" /> 

Current location

To get the users device location, a LocationSource class is used. This class uses LocationUpdateListener interface to notify about new location updates.

import com.tomtom.online.sdk.location.LocationSource;
import com.tomtom.online.sdk.location.LocationSourceFactory;
import com.tomtom.online.sdk.location.LocationUpdateListener;
(...)
public class MainActivity extends AppCompatActivity implements LocationUpdateListener {
  private LocationSource locationSource;
  (...)
}
                                    

The application needs to have proper permissions granted which is handled by onRequestPermissionsResult callback, so that location callbacks can be retrieved successfully. A PermissionChecker class is used to check whether the application has already assigned proper permissions. If not, then a requestPermissions function is called so that the user sees a permission request dialog. If location permissions have been granted inside an onRequestPermissionsResult function, a locationSource.activate() method is invoked. As a result, the application receives a GPS location in an onLocationChanged callback function.

private void initLocationSource() {
    PermissionChecker permissionChecker = AndroidPermissionChecker.createLocationChecker(this);
    if(permissionChecker.ifNotAllPermissionGranted()) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_LOCATION);
    }
    LocationSourceFactory locationSourceFactory = new LocationSourceFactory();
    locationSource = locationSourceFactory.createDefaultLocationSource(this, this,  LocationRequest.create()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setFastestInterval(2000)
            .setInterval(5000));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case PERMISSION_REQUEST_LOCATION:
            if(grantResults.length >= 2 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                locationSource.activate();
            }
            else {
                Toast.makeText(this, R.string.location_permissions_denied, Toast.LENGTH_SHORT).show();
            }
            break;
    }
}
                                    
@Override
public void onLocationChanged(Location location) {
    if (latLngCurrentPosition == null) {
        latLngCurrentPosition = new LatLng(location);
        locationSource.deactivate();
    }
} 
                                    

TomTom Map display module

A CountDown activity uses map and routing modules from the TomTom Maps SDK. It implements an OnMapReadyCallback interface, so that an onMapReady method is called after the TomTom map is ready to be used.

public class CountdownActivity extends AppCompatActivity implements OnMapReadyCallback 
{(…)
    private TomtomMap tomtomMap;
    private RoutingApi routingApi;
                                    
(…)
                                    
@Override
public void onMapReady(@NonNull TomtomMap tomtomMap) {
    this.tomtomMap = tomtomMap;
    this.tomtomMap.setMyLocationEnabled(true);
    this.tomtomMap.clear();
    showDialog(dialogInProgress);
    requestRoute(departure, destination, travelMode, arriveAt);
}
                                    
<fragment
  android:id="@+id/mapFragment"
  android:name="com.tomtom.online.sdk.map.MapFragment"
  android:layout_width="@dimen/size_none"
  android:layout_height="@dimen/size_none"
  android:layout_marginTop="@dimen/size_none"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@id/layout_countdown_timer"/>
                                    

After the startActivity method is called inside a CountDownActivity.onCreate(...) method, TomTom services and all other settings gathered from previous activity are initialized.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_countdown);
                                    
    initTomTomServices();
    initToolbarSettings();
    Bundle settings = getIntent().getBundleExtra(BUNDLE_SETTINGS);
    initActivitySettings(settings);
}
                                    
private void initTomTomServices() {
    routingApi = OnlineRoutingApi.create(getApplicationContext());
    MapFragment mapFragment = (MapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
    mapFragment.getAsyncMap(this);
}
                                    

When the map is ready to be used, TomTom SDK automatically invokes an onMapReady callback method where:

  • The tomtomMap private field is initialized.
  • An 'in progress' dialog is displayed.
  • A requestRoute method is called.

TomTom Routing module

The CountDown activity uses TomTom Routing APIs to perform routing requests and operations like drawing a route on the map or displaying start and end route icons.

  1. First, a private RoutingApi class member is declared and initialized inside an initTomTomServices function which is shown in several previous paragraphs.

    The requestRoute method takes four parameters:

    1. LatLng departure position,
    2. LatLng destination position,
    3. TravelMode which describes which type of transport user choosed,
    4. A date which determines expected arrival time.
    private static final int ONE_MINUTE_IN_MILLIS = 60000;
    private static final int ROUTE_RECALCULATION_DELAY = ONE_MINUTE_IN_MILLIS;
                                        
    private void requestRoute(final LatLng departure, final LatLng destination, TravelMode byWhat, Date arriveAt) {
                                        
    if (!isInPauseMode) {
    (…)
    }
    else {
        timerHandler.removeCallbacks(requestRouteRunnable);
        timerHandler.postDelayed(requestRouteRunnable, ROUTE_RECALCULATION_DELAY);
    }
                                        

    There is a check whether the application is not in a pause mode in the first line of the requestRoute function.

    • If the application is in the pause mode, a requestRouteRunnable object is posted in a timerHandler with a one-minute delay.

    • The RequestRouteRunnable and TimerHandler objects are defined as a private members of the CountDownActivity class:

      private Handler timerHandler = new Handler();
      private Runnable requestRouteRunnable = new Runnable() {
          @Override
          public void run() {
              requestRoute(departure, destination, travelMode, arriveAt);
          }
      };
                                                  
                                                  
    • If the application is not in the pause mode, the RouteQueryBuilder object is used to prepare a routeQuery object.

      if (!isInPauseMode) {
          RouteQuery routeQuery = new RouteQueryBuilder(departure, destination)
                  .withRouteType(RouteType.FASTEST)
                  .withConsiderTraffic(true)
                  .withTravelMode(byWhat)
                  .withArriveAt(arriveAt);
                  .build();
                                      
  2. Methods from RouteQueryBuilder are used to construct a routing query object:

    • WithRouteType - to allow the users to select one type of route from: fastest, shortest, thrilling or eco.
    • WithConsiderTraffic - when enabled, current traffic information is used during the route planning, when disabled - only historical traffic data.
    • withTravelMode – you can choose one of the following: car, pedestrian, bicycle, truck, other params are listed here.
    • withArriveAt - a date and time of arrival at the destination point. It’s an important method inside the application because it changes a departure date which later is used to calculate time left before the departure.
  3. When the routeQuery object is created, it is passed as a parameter to a planRoute function from the routingApi object. This function returns a RouteResponse observable object which you can subscribe to using for example DisposableSingleObserver<RouteResponse> object. In case of an error, an onError method is called, inside this method the current activity is finished. As a result, the application returns to the previous screen.

    @Override
    public void onError(Throwable e) {
        hideDialog(dialogInProgress);
        Toast.makeText(CountdownActivity.this, getString(R.string.toast_error_message_cannot_find_route), Toast.LENGTH_LONG).show();
        CountdownActivity.this.finish();
    }
                                                    
                                                    
  4. When the planRoute function is finished, an onSuccess(RouteResponse routeResponse) function is called and proper actions can be executed.

    @Override
    public void onSuccess(RouteResponse routeResponse) {
        hideDialog(dialogInProgress);
        if (routeResponse.hasResults()) {
            FullRoute fullRoute = routeResponse.getRoutes().get(0);
            int currentTravelTime = fullRoute.getSummary().getTravelTimeInSeconds();
            if (previousTravelTime != currentTravelTime) {
                int travelDifference = previousTravelTime - currentTravelTime;
                if (previousTravelTime != 0) {
                    showWarningSnackbar(prepareWarningMessage(travelDifference));
                }
                previousTravelTime = currentTravelTime;
                displayRouteOnMap(fullRoute.getCoordinates());
                setupCountDownTimer(fullRoute.getSummary().getDepartureTime());
            } else {
                infoSnackbar.show();
            }
        }
        timerHandler.removeCallbacks(requestRouteRunnable);
        timerHandler.postDelayed(requestRouteRunnable, ROUTE_RECALCULATION_DELAY);
    }
                                                    
  5. The most important task in previous code example is to get a current travel time and compare it to the previous one.

    • If travel times are the same, a Snackbar notification is shown to the user informing him, that there are no changes in the route time since the last check.

    • Otherwise a travel time difference is calculated and stored in a travelDifference variable.

    • Then, a snackbar notification is shown to the user with the calculated travel time difference.

  6. Next, the current travel time is stored in a previousTravelTime variable.

    • The route is displayed on the map.

    • A setupCountDownTimer function is called

    private void setupCountDownTimer(Date departure) {
        if (isCountdownTimerSet()) {
            countDownTimer.cancel();
        }
        Date currentDate = Calendar.getInstance().getTime();
        final int preparationTimeMillis = preparationTime * ONE_MINUTE_IN_MILLIS;
        long timeToLeave = departure.getTime() - now.getTime();
        countDownTimer = new CountDownTimer(timeToLeave, ONE_SECOND_IN_MILLIS) {
            public void onTick(long millisUntilFinished) {
                updateCountdownTimerTextViews(millisUntilFinished);
                if (!isPreparationMode && millisUntilFinished <= preparationTimeMillis) {
                    isPreparationMode = true;
                    setCountdownTimerColor(COUNTDOWN_MODE_PREPARATION);
                    if (!isInPauseMode) {
                        showPreparationInfoDialog();
                    }
                }
            }
                                                    
            public void onFinish() {
                timerHandler.removeCallbacks(requestRouteRunnable);
                setCountdownTimerColor(COUNTDOWN_MODE_FINISHED);
                if (!isInPauseMode) {
                    createDialogWithCustomButtons();
                }
            }
        }.start();
        textViewTravelTime.setText(getString(R.string.travel_time_text, formatTimeFromSecondsDisplayWithoutSeconds(previousTravelTime)));
    }
                                                    
  7. The setupCountDownTimer function takes the departure date as a parameter. It checks if there is already a countdown timer service already running in the background, and if there is, it cancels it. Next, a current date is stored in a currentDate variable and the preparation time taken from the first activity is stored in a preparationTimeMillis variable. A time difference between the departure time and the current time is stored in a timeToLeave variable. It is later passed to a CountDownTimer constructor.

  8. The CountDownTimer class implements two callbacks:

    • The first callback is called onTick(long millisUntilFinished).
      • It updates time info and checks whether the application is in a preparation mode.
      • If this is the case, the color of the timer text views changes, and a preparation dialog is displayed to the user.
    • onFinish() is called by the timer when counting down is completed.
      • In this function, requestRouteRunnable callback is removed from timerHandler’s queue by calling removeCallbacks method.
      • The color of a timer text views is updated again, and final alert dialog window is displayed to the user with nicely designed message that there is a time to leave!

Happy coding!

Summary

This tutorial describes the TomTom SDK and APIs used to build a Time To Leave application. Source code can be downloaded from Github.