Real-time Chat Blog

How to Build a Rideshare App for Android (Uber/Lyft Clone)

11 min read Darryn Campbell on Dec 7, 2022
Try PubNub Today

Free up to 1MM monthly messages. No credit card required.

Subscribe to our newsletter

By submitting this form, you are agreeing to our Terms and Conditions and Privacy Policy.

Build your own on-demand ridesharing app for Android, including all the core features of Uber and Lyft for a fast and seamless rideshare experience.

On-demand services rely on communicating data in real-time and optimally matching supply and demand. This business model lives across multiple use cases today: rideshare apps (taxi service, Uber, Lyft, Grab & similar clone apps), food (Uber Eats) and general delivery (Postmates and Instacart), gig economy services (TaskRabbit and Gigwalk), freelancing (Freelancer and Upwork), courier services, education (Udemy), emerging self-driving car networks, and online rental systems (Airbnb).

To learn more about the on-demand economy, read this great overview of the on-demand economy, sharing economy, and gig economy.

A typical technological theme across all these on-demand applications is real-time communication. From live mapping to chat to alerts and notifications, seamless real-time connectivity is key to delivering the interactive and efficient on-demand experience users expect.

In this tutorial, we’ll focus on the ridesharing service use case and the target audience is anyone associated with mobile app development in this market, either startup or an established development company). We’ll build an Uber Android app and Lyft clone, including UI/UX, ride-hailing and dispatch, real-time mapping, chat, and push notifications for instant alerts.

The complete GitHub repository for this project is available here.

User Experience (UX) for a Rideshare App

Let’s begin by planning the customer journey for our app users. In this case, we have two personas: the customer requesting the service (passenger) and the service provider fulfilling it (driver).

We will look into the passenger’s user experience first. This skeleton will allow us to build out the app's advanced features and ultimately tie it together to build our own on-demand ridesharing app.

Customer Journey of Rideshare App

Our UX is a basic Android app that puts drivers and passengers onto a shared network, or channel, allows them to book a driver, and draws an optimal route between the location of the nearest pair in real-time.

Rideshare Application Setup

To get set up with the app development process, create a PubNub account and start with a new mobile app. You’ll need to obtain your API keys to enable Pub/Sub Messaging.

You will also need to create a Google Cloud Platform account to use the Google Maps APIs, which will power a large portion of the taxi app development. After creating a new project, enable the Google Maps Android SDK API and the Directions API from the API manager (Menu->APIs & Services->Enable APIs and Services).

You will need to enable billing on your account for the Directions API to work (which will power the route optimization between driver and passenger). You can obtain your keys from the Credentials tab (Create credentials -> API key), and along with your PubNub keys, store them in the Constants.java file. Include your Google Maps API key in AndroidManifest.xml.

Regarding app development, we will use Android Studio with Google Play Services enabled. Although this tutorial uses Java, the same principles would also apply to developing a taxi app with Kotlin. Add the following gradle dependencies in your app if not already present build.gradle:

implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.android.gms:play-services-maps:12.0.1'
implementation 'com.google.android.gms:play-services:12.0.1'
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.github.ar-android:DrawRouteMaps:1.0.0'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'nl.psdcompany:duo-navigation-drawer:2.0.8'
implementation group: 'com.pubnub', name: 'pubnub-gson', version: '4.12.0'
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'
implementation 'com.github.jd-alexander:library:1.1.0'

Rideshare App Software Architecture

Software Architecture Diagram

Before we jump into the code, let’s look at the overall software architecture of the app.

The Main Activity (or Activity_List.java in the repo) will hold the PubNub instance and instantiate it; this will be the home of the three PubNub channels we create. The driver side will publish the location messages through PubNub in a LinkedHashMap. The Passenger will be subscribed to the channels with listeners for messages in the channels, allowing it to access real-time messages over those respective channels.

Requesting GPS Location Permissions using Google Maps

Both applications require Android location permissions for geo-tracking work. The following Google Maps packages are required to request location:

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat;

We also need to include the access permissions in our AndroidManifest.xml

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

This is where we include our Google Maps API key. Replace YOUR_GOOGLE_API_KEY with the API keys you obtained earlier.

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="YOUR_GOOGLE_API_KEY" />

When the app is instantiated, we can check whether permission is granted. If it isn’t, we can prompt the user to share location with the following lines in your main activity:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED)
    mMap.setMyLocationEnabled(true);
else
    ActivityCompat.requestPermissions(
        this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);

This should prompt the following window on app launch:

Location Request Prompt

In-App Real-time Geolocation Tracking

Note: you can find the full source code for this section in Activity_List.java in the GitHub repo.

PubNub will allow both drivers and passengers to communicate their location with each other in real time.

The following packages are required:

import com.pubnub.api.PNConfiguration;
import com.pubnub.api.PubNub;
import com.pubnub.selfdrivingdemo.util.Constants;

Instantiate PubNub in the main activity:

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);
}

That’s it! With a few lines of code, PubNub is ready to power your app. We will use it to seamlessly subscribe to a channel and publish our location data to it.

Creating the Driver App

You can find the full source code for this section in DriverActivity.java file in the repository.

We will focus on the driver side first, as it is more straightforward. We will use an Android device as a proxy for the car, but any device could be used. The important functionality from the driver is to continuously publish location data so that the rideshare network is aware of all cars at all times.

We will use a LinkedHashMap data structure to store the location GPS coordinates in terms of latitude and longitude.

LinkedHashMap

To get an updated state of the device’s current location, we can use the mFusedLocationClient Android class.

mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
locationRequest = LocationRequest.create();

We can make this location request every few seconds and publish the data to a shared channel, car_location, creating a request every second through LocationRequest with PRIORITY_HIGH_ACCURACY. It is important to note that while this method returns the most accurate location, it may consume more battery.

The following simple structure is used to publish a PubNub message:

Activity_list.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());
            }
        });

By running this logic through a loop, we can get access to the location of the car in real-time on the car_location channel.

Creating the Passenger (Customer) App

Note: you can find the complete source code for this section in Home.java file in the repository.

Now that the infrastructure and driver side coded, let’s lay out the interface for the passenger app.

This map will serve as the activity in which the customers can call a phone number for a ride and see the location of nearby drivers. They will also be able to input the pickup location.

Add a new Google Maps Activity from New->Activity. After inputting your Google Maps API key, the next step is to generate a Google map instance within the onCreate() event. To create the UI for the map view, the Activity will call SupportMapFragment and initialize a map.

You should see these lines:

MapFragment mapFragment = (MapFragment) getFragmentManager()
        .findFragmentById(R.id.googleMap);
mapFragment.getMapAsync(this);
private GoogleMap mGoogleMap; // object that represents googleMap and allows us to use Google Maps API features

At this point, you will see an interface with a map built for you when you run your app. After the map is ready, we can begin adding functionality. This is done from the onMapReady() function. Here you can adjust the default values of the marker.

We will now subscribe to the PubNub channel we created and call a function whenever we receive a message. To subscribe to the channel, simply add the following lines:

Activity_list.pubnub.subscribe()
        .channels(Arrays.asList(Constants.PUBNUB_CHANNEL_NAME)) // subscribe to channels
        .execute();

Whenever we receive a message with a driver location update, we can update the UI of the new car location.

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();
            }
        }
    });
}

We have now set up real-time communication between drivers (publishers) and passengers (subscribers) in a geolocation-based app. After spinning up the app on two separate devices (one for the passenger and one for the driver), you will see the car's location on the passenger map.

With this data, you can fill several other use cases, such as drawing routes between the chosen driver, push notifications when a driver is nearby, or creating a self-driving network that functions independently (for the very ambitious).

Draw a Route Between Two GPS Locations on a Map

Note: you can find the complete source code for this section in Home.java in the repository.

To link the two members in the channel, we will draw routes between the driver and the passenger and a path to the final destination. As we are using PubNub, this will be updated in real-time, displaying the most optimal route.

We will use the Google Maps Directions API and the Google-Directions-Android-Library to draw routes. After including the library in your build.gradle, implement RoutingListener in the passenger activity class.

Note: you will also need to implement the methods of the RoutingListener class.

This API works by making a call for a walking or driving route and implementing a listener that waits for the route to be returned by Google Maps. After the call returns, we can use Polylines to draw lines on the streets as the final route.

You will want to make the routing call each time the UI is updated (i.e., whenever the driver updates its location). The function will look like this:

private void getRouteToMarker(LatLng newLocation) {
    Location myLocation = mMap.getMyLocation();
    LatLng myLatLng = new LatLng(myLocation.getLatitude(),
            myLocation.getLongitude());
    Routing routing = new Routing.Builder()
            .key(Constants.GOOGLE_API_KEY)
            .travelMode(AbstractRouting.TravelMode.DRIVING)
            .withListener(this)
            .alternativeRoutes(true)
            .waypoints(newLocation, myLatLng)
            .build();
    routing.execute();
}

In the onRoutingSuccess() method, include the following logic to draw the final route.

public void onRoutingSuccess(ArrayList<Route> route, int shortestRouteIndex) {
    if(polylines.size()>0) {
        for (Polyline poly : polylines) {
            poly.remove();
        }
    }
    polylines = new ArrayList<>();
    //add route(s) to the map.
    for (int i = 0; i <route.size(); i++) {
        //In case of more than 5 alternative routes
        int colorIndex = i % COLORS.length;
        PolylineOptions polyOptions = new PolylineOptions();
        polyOptions.color(getResources().getColor(COLORS[colorIndex]));
        polyOptions.width(10 + i * 3);
        polyOptions.addAll(route.get(i).getPoints());
        Polyline polyline = mMap.addPolyline(polyOptions);
        polylines.add(polyline);
        Toast.makeText(getApplicationContext(),"Route "+ (i+1) +": distance - "+ route.get(i).getDistanceValue()+": duration - "+ route.get(i).getDurationValue(),Toast.LENGTH_SHORT).show();
    }
}

Ensure the following variables are declared globally:

private List<Polyline> polylines;
private static final int[] COLORS = new int[]{R.color.primary_dark_material_light, R.color.colorAccent};

After testing the polylines, your app should look like this:

Route between driver and passenger

Send an Android Push Notification with PubNub and Firebase

Another use case PubNub fills is sending push notifications to the passenger when the driver is nearby. To get set up, follow thistutorial on Android push notifications to get Firebase and PubNub set up in your Android application. We can utilize the collected location data to compute the distance between the passenger and the closest driver.

Location loc1 = new Location(“”);
loc1.setLatitude(passengerLocation.latitude);
loc1.setLongitude(passengerLocation.longitude);
 
Location loc2 = new Location(“”);
loc2.setLatitude(driverLocation.latitude);
loc2.setLongitude(driverLocation.longitude);
float distance = loc1.distanceTo(loc2);

When the driver is close enough (say, within 200 meters) we can send a push notification to the passenger that the nearby driver.

if (distance < 200) {
    notificationMessage = {"pn_gcm":{"notification":{"body":"Your driver is nearby."}}}
} else {
    notificationMessage = {"pn_gcm":{"notification":{"body":"Driver found: ” + String.valueOf(distance)}}};
}

With the new Notifications channel, we can publish the message to the channel and send a push notification to the passenger.

Activity_List.pubnub.publish()
    .message(message)
    .channel(Notifications)
    .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());
    }
});

Create a Connected Experience with Chat for On-demand Apps

Chat between the matched users (in this case, drivers and passengers) is a core feature of on-demand service apps. To build an Android chat inside this app, you may start by following this tutorial on getting started with Android chat.

Create a button that opens the chat view.  Ensure the chat channel is different from the location and notifications channels.

A new channel will be needed for each passenger and driver pair.

Design

Note: you can find the complete source code for this section in the activity_home.xml file in the repository.

PubNub Ride Short Gif

Design is where you can get creative and tailor your app to your use case. To modify the UI, we will deal with the XML files in app/res/layout/. While the repository contains every page in the flow designed, we will focus on the UI for the passenger home view.

Use EditText fonts to display titles and content to the user cleanly. For the title, use Roboto Regular, and for any content text, use SF Pro Display Regular (these fonts are included in the GitHub repo). Here are the respective styles for the toolbar, the location card, and the destination card found in activity_home.xml.

<customfonts.MyTextView_Roboto_Regular
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="PubNub Ride"
    android:layout_gravity="center"
    android:textSize="17.3sp"
    android:textColor="#323643"/>
 
 
<customfonts.EditText__SF_Pro_Display_Regular
    android:layout_marginTop="15dp"
    android:layout_marginLeft="18dp"
    android:background="#00000000"
    android:textColorHint="#000000"
    android:inputType="text"
    android:textSize="13.4sp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="Enter Location"/>

Next, use the Toolbar widget to display the title, menu, and notification icons.

PubNub Ride Toolbar

To get this Toolbar, use the following XML layout:

<android.support.v7.widget.Toolbar
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/toolbar"
    app:contentInsetStart="0dp">
  <ImageView
      android:id="@+id/navigation_menu"
      android:layout_marginLeft="19.2dp"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_menu_"/>
  <customfonts.MyTextView_Roboto_Regular
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="PubNub Ride"
      android:layout_gravity="center"
      android:textSize="17.3sp"
      android:textColor="#323643"/>
  <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="right"
      android:src="@drawable/ic_notifications_none_black_24dp"
      android:layout_marginRight="10dp"/>
</android.support.v7.widget.Toolbar>

Next add the location and destination cards.

PubNub Ride CardView

This is accomplished using the CardView widget and ImageView for the respective icons.

<android.support.v7.widget.CardView
    android:layout_marginTop="87.9dp"
    android:layout_marginLeft="19.2dp"
    android:layout_marginRight="19.2dp"
    android:layout_width="match_parent"
    android:layout_height="46.1dp">
  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal">
    	<ImageView
            android:layout_marginTop="17dp"
            android:layout_marginLeft="10dp"
            android:layout_width="17dp"
            android:layout_height="17dp"
            android:src="@drawable/pin_black"/>
    	<customfonts.EditText__SF_Pro_Display_Regular
            android:layout_marginTop="15dp"
            android:layout_marginLeft="18dp"
            android:background="#00000000"
            android:textColorHint="#000000"
 	    android:inputType="text"
            android:textSize="13.4sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="Enter Location"/>
  </LinearLayout>
</android.support.v7.widget.CardView>

Building and Fixing Common Issues with Rideshare Android App

To run the taxi booking app, build the project and hit the run button in the Android Studio Toolbar. You may use a physically connected device with USB debugging enabled or an emulator. Using the emulator may result in issues sharing your location, so using physical devices is recommended. The current state supports passenger and driver geo-location tracking. To run the cloned repo, select Home from the passenger device first and Driver from the driver device.

There are a few valuable tools when testing and debugging your app and reducing development costs.

If you are unsure that your messages are being published to the PubNub channel, you can use the Debug Console in your admin panel.

You can also use the PubNub Developer Console, through which you can track presence and history events. Make sure to use the correct publish and subscribe keys for this mobile application, as well as the correct channel name.

Another source of error occurs if the app is not retrieving the location from Google Maps. Use the APIs Overview in the GCP console to view the respective APIs' number of requests, errors, and latency to check for any issues.

Expanding the App

After creating this base app, you can expand on this skeleton for your own use cases. You can adapt it as a food delivery app or a home rental service. If you’d like to continue building an Uber app for Android (Uber clone app), you can build out the other portions of the customer journey, as outlined above.

After cloning the repository, you can add in the functionality behind login/sign-up, additional drivers by adding members to the channel and selecting the closest driver, a payment system (PayPal, Credit card, or integrating your own payment gateway such as Stripe), and revenue model, as well as ride history, more complex ride requests and cancellation, all powered by PubNub. Of course, as a final step, you will want to publish your app to the app store and configure a backend for your app-specific functionality, e.g. using Node.js.

The same principles and tech stack discussed in this blog would apply equally to apple iOS and iOS app development with Swift replacing Java.

Have suggestions or questions about the content of this post? Reach out to devrel@pubnub.com.

More from PubNub

How to Add a Notification Badge to Icons in React Native
Real-time Chat BlogDec 19, 20226 min read

How to Add a Notification Badge to Icons in React Native

Display real-time notification badges with PubNub and React Native to display important information and bring users back to your...

Michael Carroll

Michael Carroll

Digital Twins and the Future of Real-Time Data
InsightsDec 6, 20224 min read

Digital Twins and the Future of Real-Time Data

The concept of Digital Twins has evolved over the last two decades, however, one thing remains the same: the need for real-time...

Michael Carroll

Michael Carroll

How Many Text Characters Fit in a 32KB PubNub Message?
Real-time Chat BlogNov 24, 20224 min read

How Many Text Characters Fit in a 32KB PubNub Message?

Learn the ins-and-outs of PubNub message size and get a better idea of how many text characters fit in a single message.

Michael Carroll

Michael Carroll