Build Your Own Uber/Lyft App with Geolocation Tracking
Published: 2024-02-22 07:27:48

**In this post from our archives, we explain how to build your own Uber or Lyft clone, using PubNub to track the vehicle position. Whilst some of the code snippets might reference older library versions, everything in this article is appropriate for today's audience**  
  

Tracking the real-time location of another device is the basis of many popular mobile, location-focused apps, especially when it comes to building

like Lyft and Uber. Building this functionality is challenging and can be tedious, but we’ll simplify it with an easy publish/subscribe model between the drivers and passengers, powered by PubNub.

﻿

Tutorial Overview
-----------------

**[Full source code](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo) for this tutorial is available here.**

In this tutorial, we’ll show you how to implement this common use case in an Android application. The sample app we will build first asks the user if they are the driver or the passenger. If a user is a driver, their current location will be published to a channel, updated every 5 seconds (in this sample), or however many times your app requires.

If a user is a passenger, they will subscribe to the same channel to receive updates on the current location of their driver. This publish/subscribe model is displayed in the visual below.

[![Uber/Lyft App](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png "Uber/Lyft App")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png)

Google Maps API and PubNub Setup
--------------------------------

We first need to write our Gradle dependencies to use the [PubNub Android SDK](https://pubnub.com/docs/sdks/java/android/) and Google Play Services for the map functionality. We also will implement 

```js
com.fasterxml.jackson
```

libraries, in order to complete some basic JSON parsing of PubNub messages.

In the build.gradle file for the app module under dependencies, input the codeblock of command below to ensure that we have what we need. Be sure that Google Play Services is installed from the Android SDK Manager on the tools portion of the menu, so we may implement the necessary libraries.

```js
implementation group: 'com.pubnub', name: 'pubnub-gson', version: '4.12.0'
implementation 'com.google.android.gms:play-services-maps:15.0.1'
implementation "com.google.android.gms:play-services-location:15.0.1"
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.2'
```

Now that we have the necessary dependencies, let’s make sure that we have the necessary permissions in our Android Manifest file. With the permission to access network state and establish internet connection, the user can now make the connection to the PubNub API. We will use the location permission later, when we request for the driver’s location.

```js
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
```

In order to set up PubNub in your Android app, you will need to create a PubNub app in the [PubNub Admin Dashboard](https://dashboard.pubnub.com/signup/) **(it’s free)**. Upon creating the app, you will be assigned a subscribe key and publish key. In the following part, we will use these credentials in our MainActivity class to establish the connection. 

Now, we must set up our Google Maps API key, so that we can use it in our application. In order to do this head over to [Google’s Developer API console](https://console.cloud.google.com/projectselector2/apis/dashboard?supportedpurview=project). Here, we will create a new project and then generate an API key. Now that we have our API key, we can write the following code to set up our Google Maps API and configure it with our generated API key.

```js
<meta-data
   android:name="com.google.android.gms.version"
   android:value="@integer/google_play_services_version" />
<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value=”ENTER_KEY_HERE" />
```

Main Activity
-------------

The MainActivity class will have three important functionalities:

1.  Directing our user to their respective interface, depending on whether they are a driver or passenger.
2.  Establishing a connection to PubNub for all users of the app
3.  Checking location access permissions

First, we want to separate our users into our drivers and passengers and provide them with their respective user interface. We will do this by creating a DriverActivity class and a PassengerActivity class, which we will discuss in detail in parts 2 and parts 3.

In MainActivity we will make a simple button-based interface which prompts the user to let the app know their role as a driver or passenger. Using Android’s Intent class, we can transition from the MainActivity class into the DriverActivity class or from the MainActivity class into the PassengerActivity class, depending on which button is pressed.

The interface will appear as:

[![screenshot](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png "screenshot")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png)

```js
driverButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, DriverActivity.class));
   }
});
passengerButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, PassengerActivity.class));
   }
});
```

Next, using our PubNub credentials from the [Admin Dashboard](https://dashboard.pubnub.com/signup), we will create a 

```js
PNConfiguration
```

 instance, which allows us to create a

```js
PubNub
``` instance connecting our app and PubNub. This PubNub instance in the MainActivity class will be public and static so it is accessible from both the DriverActivity and the PassengerActivity. We will call the below method in our 

```js
onCreate()
``` method of the Main Activity, so that PubNub is properly initialized for all of our app’s users. This is all shown in the following code.

```js
private void initPubnub() {
        PNConfiguration pnConfiguration = new PNConfiguration();
        pnConfiguration.setSubscribeKey(Constants.PUBNUB_SUBSCRIBE_KEY);
        pnConfiguration.setPublishKey(Constants.PUBNUB_PUBLISH_KEY);
        pnConfiguration.setSecure(true);
        pubnub = new PubNub(pnConfiguration);
    }
```

Finally, we must ensure that our user has enabled their location services for our app. This can be done with the following code. If our user does not grant permission to access fine location or coarse location, our app will request this permission.

```js
public void checkPermission() {
   if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
           ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
           ) {//Can add more as per requirement
       ActivityCompat.requestPermissions(this,
               new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
               123);
   }
}
```

Driver Activity
---------------

The sole function of the DriverActivity class is to obtain the driver’s location and publish this data at a certain time interval to the PubNub channel.

First, let’s look at how to make a location request. We will use the 

```js
FusedLocationProviderClient
```

 class from the Google Location API in order to request location updates from the driver. We will also use the 

```js
LocationRequest
``` class in order to specify important parameters of our location request, such as the priority, smallest displacement, and time interval.

For our sample, we will use a time interval of 5000 milliseconds, so that the location is updated every 5 seconds and a smallest displacement of 10 meters to ensure that the location is only being updated if there is a change of at least 10 meters. Finally, we want the location request to use the mobile device’s GPS, for high accuracy location tracking. This high accuracy is important in enabling the passenger to see a detailed display of their driver (shown as car icon) moving across the map to the destination.

```js
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(5000);
locationRequest.setSmallestDisplacement(10);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
```

Now that we have defined our location request parameters, we will pass this LocationRequest object into our FusedLocationProviderClient object’s request for location updates. 

```js
mFusedLocationClient.requestLocationUpdates(locationRequest, new LocationCallback() {
   @Override
   public void onLocationResult(LocationResult locationResult) {
       Location location = locationResult.getLastLocation();
...
```

Using the location we get from our request, we must convert it into a LinkedHashMap so that it fits the desired format of a location message in the PubNub channel. The LinkedHashMap will have two keys “lat” and “lng” and the corresponding values for these keys, both as Strings.

Now with our message in the format of a LinkedHashMap, we are able to publish it to the PubNub channel. This is shown in the following code. Notice that we must use 

```js
MainActivity.pubnub
```

to obtain the PubNub instance that we earlier in the onCreate method of the Main Activity. We do not want to establish multiple connections by initializing PubNub again. Instead, we just use the one we already created in the MainActivity class.

```js
MainActivity.pubnub.publish()
       .message(message)
       .channel(Constants.PUBNUB_CHANNEL_NAME)
       .async(new PNCallback<PNPublishResult>() {
           @Override
           public void onResponse(PNPublishResult result, PNStatus status) {
               // handle publish result, status always present, result if successful
               // status.isError() to see if error happened
               if (!status.isError()) {
                   System.out.println("pub timetoken: " + result.getTimetoken());
               }
               System.out.println("pub status code: " + status.getStatusCode());
           }
       });
```

Passenger Activity
------------------

In our PassengerActivity class, we want to create a MapView to show the location of the driver being updated. In order to add the MapView UI element to our Passenger Activity we must include the following code in our corresponding layout.xml file for this activity.

```js
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/map"
   android:name="com.google.android.gms.maps.SupportMapFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.mapwithmarker.MapsMarkerActivity" />
```

Then, instantiate the 

```js
MapFragment
```

 object using the ID we define in the layout file. In our sample code, we used “map” as the ID. We will also set up our 

```js
onMapReady
``` method by having PassengerActivity implement

```js
onMapReadyCallback
```

. Then we will pass 

```js
PassengerActivity.this
``` as a parameter into our MapFragment object’s 

```js
getMapAsync
``` method. This allows us to setup our onMapReady callback method for our MapFragment object.

```js
mMapFragment = (SupportMapFragment) getSupportFragmentManager()
       .findFragmentById(R.id.map);
mMapFragment.getMapAsync(PassengerActivity.this);
```

Now, we want to subscribe to the driver location channel so we receive updates on the driver’s current location. This must be done once the map is ready, so we will include this code in our callback method. Additionally, we must add a listener with a

```js
SubscribeCallback
```

, so our app knows how to act upon receiving a message.

As specified in the PubNub Android docs, we must carry this out in the order where we first add the listener and then subscribe the PubNub instance to the channel. Once we receive a message, we want to convert the JSON output into a LinkedHashMap, which we can easily parse to obtain the longitude and latitude. In order to convert the JSON output into a LinkedHashMap, we must import the 

```js
JsonUtil
```

 class, which contains the method 

```js
fromJson()
``` that allows us to do this. This class is shown in the full source code. Once we have the driver’s location, we must update the UI by calling our method 

```js
updateUI()
``` and passing the new location as the parameter.

```js
MainActivity.pubnub.addListener(new SubscribeCallback() {
    @Override
    public void status(PubNub pub, PNStatus status) {
    }
    @Override
    public void message(PubNub pub, final PNMessageResult message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Map<String, String> newLocation = JsonUtil.fromJson(message.getMessage().toString(), LinkedHashMap.class);
                    updateUI(newLocation);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    @Override
    public void presence(PubNub pub, PNPresenceEventResult presence) {
    }
});
MainActivity.pubnub.subscribe()
        .channels(Arrays.asList(Constants.PUBNUB_CHANNEL_NAME)) // subscribe to channels
        .execute();
```

Our updateUI method has two cases it will encounter.

The first case is that there does not already exist a marker for the driver and in this case, we will instantiate the 

```js
Marker
```

 object and set its position to the first tracked location of the driver. We must also zoom in on the map in order to maintain a street view of the driver’s car.

The second case is that there already exists a marker for the driver and in this case, we do not have to re-instantiate the Marker object. Rather, we simply move the location of the marker across the map to its new location. We do this by calling the method

```js
animateCar()
```

. We must also ensure that we move the camera of the MapView to the new location, if it is out of the bounds of the camera. Thus, the driver’s marker will always be in the bounds of the MapView. This is shown in the following code.

```js
private void updateUI(Map<String, String> newLoc) {
        LatLng newLocation = new LatLng(Double.valueOf(newLoc.get("lat")), Double.valueOf(newLoc.get("lng")));
        if (driverMarker != null) {
            animateCar(newLocation);
            boolean contains = mGoogleMap.getProjection()
                    .getVisibleRegion()
                    .latLngBounds
                    .contains(newLocation);
            if (!contains) {
                mGoogleMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation));
            }
        } else {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    newLocation, 15.5f));
            driverMarker = mGoogleMap.addMarker(new MarkerOptions().position(newLocation).
                    icon(BitmapDescriptorFactory.fromResource(R.drawable.car)));
        }
    }
```

In our animateCar method, we must move our car marker to its new location, in a smooth line shaped manner. The animation must last 5 seconds, because our app receives updates on the driver’s location every 5 seconds. This way every time the animation is over, it will have the new location update to start the next animation. That way, we minimize the lag in the animation of the car.

We will also utilize the 

```js
LatLngInterpolator
```

interface below. This allows us to implement the method

```js
interpolate()
```

, which returns a new LatLng coordinate that’s a fraction of the way to the final newLocation. This fraction is determined with the method

```js
getAnimatedFraction()
```

. We will call this method from a 

```js
ValueAnimator
```

instance, which we get obtain from the 

```js
onAnimationUpdate()
```

callback method. This fraction will increase at a steady rate so that by 5 seconds, it is 1 (all the way to the new location). This animating of the car is shown in the following code snippets.

```js
private void animateCar(final LatLng destination) {
        final LatLng startPosition = driverMarker.getPosition();
        final LatLng endPosition = new LatLng(destination.latitude, destination.longitude);
        final LatLngInterpolator latLngInterpolator = new LatLngInterpolator.LinearFixed();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(5000); // duration 5 seconds
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                try {
                    float v = animation.getAnimatedFraction();
                    LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, endPosition);
                    driverMarker.setPosition(newPosition);
                } catch (Exception ex) {
                }
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        valueAnimator.start();
    }
```

```js
private interface LatLngInterpolator {
       LatLng interpolate(float fraction, LatLng a, LatLng b);
       class LinearFixed implements LatLngInterpolator {
           @Override
           public LatLng interpolate(float fraction, LatLng a, LatLng b) {
               double lat = (b.latitude - a.latitude) * fraction + a.latitude;
               double lngDelta = b.longitude - a.longitude;
               if (Math.abs(lngDelta) > 180) {
                   lngDelta -= Math.signum(lngDelta) * 360;
               }
               double lng = lngDelta * fraction + a.longitude;
               return new LatLng(lat, lng);
           }
       }
   }
```

Further Improvements
--------------------

If you wish to further develop this sample, one feature to add would be having the marker turn as the car turns. This feature would require us to add one key-value pair to our message structure. We would need to include the key “bearing” and the value. Then we would simply rotate the marker depending on the driver’s bearing.

Congrats!
---------

Now that we have created the MainActivity, DriverActivity, and PassengerActivity, we have successfully built the publish/subscribe model necessary to create a simple Lyft/Uber Android demo. Click here for the [full source code](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo).

  
**If the embedded content is not available** on this page, it can also be viewed at [https://www.youtube.com/embed/kQ6EzqoQu5A](https://www.youtube.com/embed/kQ6EzqoQu5A)

More Resources
--------------

-   [Should You Add a Geolocation API to Your Application?](https://www.pubnub.com/guides/should-you-add-a-geolocation-api-to-your-app/)
    
-   [Our Geolocation Demo](https://www.pubnub.com/demos/geolocation-demo/)
    
-   [Geolocation Developers Path](https://www.pubnub.com/developers/geolocation-developer-path/)
    

How can PubNub help you?
========================

This article was originally published on [PubNub.com](https://www.pubnub.com/blog/build-your-own-uber-lyft-app-geolocation-tracking-android/)

Our platform helps developers build, deliver, and manage real-time interactivity for web apps, mobile apps, and IoT devices.

The foundation of our platform is the industry's largest and most scalable real-time edge messaging network. With over 15 points-of-presence worldwide supporting 800 million monthly active users, and 99.999% reliability, you'll never have to worry about outages, concurrency limits, or any latency issues caused by traffic spikes.

Experience PubNub
-----------------

Check out [Live Tour](https://www.pubnub.com/tour/introduction/) to understand the essential concepts behind every PubNub-powered app in less than 5 minutes

Get Setup
---------

Sign up for a [PubNub account](https://admin.pubnub.com/signup/) for immediate access to PubNub keys for free

Get Started
-----------

The [PubNub docs](https://www.pubnub.com/docs) will get you up and running, regardless of your use case or [SDK](https://www.pubnub.com/docs)