Building A Location Distance Tracking App | Android

Every time when I see Uber, Careem or any other transport application, I always try to figure out how those developers must’ve developed those apps. There are so much great features in these apps. One of that feature was to calculate the distance when the driver starts the ride. In this article, I will try to demonstrate that how I ended up making such Distance Tracking App in my own way.

By the way, this is the second post of “Location Distance Tracking App” series, please do check out the first one, if you haven’t already. In the first post, I tell my story what problems occur when I start building the location distance tracking app.

Some of the features you can find in the project are:

  1. Display user current location on the map.
  2. Animate the user marker on a map when the current location changed.
  3. Calculate the distance with Google Distance Matrix API.

Below is a demo of an application which we gonna make in this article.

Things Needed Before Start Coding

      • Android Google Maps API key
      • Google Distance Matrix API key

Android App Coding

We will not create the whole app because if we do then it will be a very long article and a lot of code to explain, so we’ll see the key points of making distance tracking app. So, let’s start coding… Below are the dependencies you need to work with the application.

// PlayServices dependency
implementation 'com.google.android.gms:play-services-maps:16.0.0'
implementation 'com.google.android.gms:play-services-location:16.0.0'

// Map services dependency
implementation 'com.google.maps:google-maps-services:0.2.4'

1. Display the current location on the map

I’m using ViewModel and LiveData to store user current location. So that if the activity is recreated due to the configuration changes, like a device rotation it immediately receives the user previous location.

// 1 
private val locationLiveData = NonNullMediatorLiveData<Location>()

// 2
fun currentLocation() : LiveData<Location> = locatinLiveData

// 3
object : LocationCallback() {

        override fun onLocationResult(locationResult: LocationResult?) {
             super.onLocationResult(locationResult)
             locationResult?.let {
                 val location = it.lastLocation
                 val accuracy = currentLocation.accuracy
                 if (!location.hasAccuracy() || accuracy > 35f) return
                 locationLiveData.value = location                
             }        
        }

Below is the explanation of the above code.

      1. I’m using NonNullMediatorLiveData class for locationLiveData. The NonNullMediatorLiveData extends from MediatorLiveData class. The purpose of it extending from MediatorLiveData to make a lot easier to use, especially making it NonNull safe.
      2. It is only for not just exposing our LiveData publically, we’ve given a public function to just get locationLiveData. By doing this we’re keeping our immutability principle.
      3. The LocationCallback used for receiving the Location. The method onLocationResult called when the device location available. In the method, we’re first checking if the location accuracy is within the radius of 35 meters. If the location within the radius then simply we’re setting the location to our locationLiveData. You can read more about location accuracy here.

Before going to further I want to show you the NonNullMediatorLiveData class.

NonNullMediatorLiveData

class NonNullMediatorLiveData<T> : MediatorLiveData<T>()

2. Animate the user marker on a map when the location changed

viewModel.currentLocation()
         .nonNull()
         .observe(this) {
              if(currentMarker != null)
                   animateMarker(currentMarker)
              else 
                   currentMarker = addNewMarker()    
          }

Add this code in the Activity and one more important thing here is, you can see there’s a slight change in how we observe our locationLiveData and we have a very nice and safe way to observe the nonNull data. In the observe method we first check if the user current location marker is null then we simply add the new marker to map else animate the existing marker to user new location.


Below is the kotlin extension function which we use above for LiveData instance.

Kotlin Extension Function

fun <T> LiveData<T>.nonNull(): NonNullMediatorLiveData<T> {
    val mediator: NonNullMediatorLiveData<T> = NonNullMediatorLiveData()
    mediator.addSource(this) { it?.let { mediator.value = it } }
    return mediator
}

fun <T> NonNullMediatorLiveData<T>.observe(owner: LifecycleOwner, observer: (t: T) -> Unit) {
    this.observe(owner, android.arch.lifecycle.Observer {
        it?.let(observer)
    })
}

3. Calculate the distance with Google Distance Matrix API

// 1
private val distanceTracker = NonNullMediatorLiveData<Long>()

// 2
fun distanceTracker(): LiveData<Long> = distanceTracker

// 3
fun startLocationTracking() {
        locationTrackingCoordinates = locationLiveData.value
        compositeDisposable.add(Observable.interval(10, TimeUnit.SECONDS)
                .subscribeOn(appRxScheduler.threadPoolSchedulers())
                .subscribe({ _ -> makeDistanceCalculationCall() }
                        , { _ -> startLocationTracking() }))
    }

// 4
private fun makeDistanceCalculationCall() {
  val tempLocation = locationLiveData.value
  val origin = arrayOf(locationTrackingCoordinates.latitude.toString() + "," + locationTrackingCoordinates.longitude)
  val destination = arrayOf(tempLocation.latitude.toString() + "," + tempLocation.longitude.toString())
  DistanceMatrixApi.getDistanceMatrix(googleMapHelper.geoContextDistanceApi(), origin, destination)
          .mode(TravelMode.WALKING)
          .setCallback(object : PendingResult.Callback<DistanceMatrix> {
                 override fun onResult(result: DistanceMatrix) {
                     locationTrackingCoordinates = tempLocation
                     val temp = result.rows[0].elements[0].distance.inMeters
                     totalDistance += temp
                     distanceTracker.postValue(totalDistance)
                 }

                 override fun onFailure(e: Throwable) {
                 }
          })
}

Add the above code in the ViewModel class. Below is the explanation of the Distance Matrix API code.

      1. Again using the NonNullMediatorLiveData for distanceTracker to update the total distance whenever the new distance is calculated.
      2. Only exposing the LiveData publically with the distanceTracker method.
      3. The startLocationTracking method called when the application receives its first location. You see in this method we have a timer with ten seconds interval. After every ten seconds, we need to make the Google Distance Matrix request like I said, in my previous article.
      4. In here first, we store the user current location in the tempLocation. After that, we simply execute the Distance Matrix synchronous request. The DistanceMatrixApi class is from the dependency which we add above in our app. You can read more about how to use the library here on Github. In the onResult method, we need to update our locationTrackingCoordinates, get the distance in meters and set the total distance to distanceTracker. One more important thing here, you see we’re using the postValue method to update the distanceTracker. This is because we’re sending data from the background thread.

There’s a great tutorial on how to use LiveData go and check it out.

Bravo! as you can see we almost complete our application. The only thing remains is to listen to the total distance from Activity.

viewModel.distanceTracker()
         .nonNull()
         .observe(this) {
            distanceCoveredTextView.text = it
          }

Alright, guys, that was all from this article. If you’ve any query regarding this post, question or comments please do comment below. You can get the source code of the above app from Github.

Thank you for being here and keep reading…

11 COMMENTS

  1. Hi. Thank you for the post. It is exactly what I was looking for since I am learning android dev by doing an Uber-like app. But the problem is I don’t know much about Kotlin and I can’t understand the code. Can you please give the equivalent code in Java?

      • Try to change the accuracy condition. Like this
        if (!location.hasAccuracy() || accuracy > 10f) return

        The problem that you’re facing, because in some area there’s may be a chance that accuracy is not accurate. Now if it tells you the accuracy is 10m, there’s a 68% chance that you are actually with in 10 meters of the location, and a 32% chance that you’re farther away. Now we only update our location if the location radius is within 10m.

LEAVE A REPLY

Please enter your comment!
Please enter your name here