SlideShare a Scribd company logo
Creating an Uber Clone - Part XXV
Next we’ll go into the underlying business logic portion of the hailing process on the client
LocationService
✦Mark hailing in websocket code
✦Server checks available area
✦Send push notifications to available drivers
✦Expand the circle of search
© Codename One 2017 all rights reserved
Up until now I kept hailing as a vague process. I think Uber probably has a far more elaborate system than the one I developed in an hour but this should work fine for
most cases. Hailing includes the following phases:

We mark that we are interested in hailing in the location service WebSocket code.

The server checks which drivers are available in the area and returns to us their push keys

We send push notifications to available drivers

As time moves on we expand the circle of search for drivers
public static final String CODENAME_ONE_PUSH_KEY = "----";
public static final String GOOGLE_PUSH_AUTH_KEY = "----";
public static final String APNS_DEV_PUSH_CERT = "----";
public static final String APNS_PROD_PUSH_CERT = "----";
public static final String APNS_DEV_PUSH_PASS = "----";
public static final String APNS_PROD_PUSH_PASS = "----";
public static final boolean APNS_PRODUCTION = false;
Globals
In order to do this I had to make some changes to the Websocket protocol in the LocationService class. But first we need some additional variables in the Globals class.
I'll go into more details on those values in the next chapter but for now we need the variables only. All of these API's require developer keys which you can obtain from
their respective websites. I've edited the Globals class to include these new keys required by the 3 API's

Right now we can leave these all blank and get to them in the next chapter.
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Let's move to the LocationService class and the variables I had to add

When a driver accepts our hail the server sends a special message indicating that the hail was accepted
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Previously we had 2 modes for polling the server for searching and not searching. I added a 3rd mode that allows us to disable hailing
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
I've made the LocationService into a singleton
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
These represent whether we are hailing and if so to what radius? We use a Motion object to get a growing radius that will stretch over time to encapsulate a wider region
for hailing
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Our source and destination values which we need to broadcast a hail
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
When we send a push notification to a car we need to make sure we didn't already notify it
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
The unique id of the driver we've found
private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
This is the callback we invoke when a driver is found
if(ll == lon && lt == lat && dir == direction && hailing == HAILING_OFF) {
// no need to do an update
return;
}
sendLocationUpdate
Now that these are out of the way lets look at the other things that need doing. We changed the way we handle the communication protocol by and we added some
additional details.

First we need to ignore location changes when doing hailing which we can do by adding that condition.
dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
Next we need to change the protocol a little bit

This was previously limited to 0 only and now we check if we are in hailing mode
dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
During hailing mode the radius of search grows over time
dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
We send the from/to values as UTF-8 encoded strings which allows us to communicate locale specific locations
dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
When we turn off hailing in the server it's a "one time thing". After it's off we can go back to the regular mode
dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
This isn't likely as this is a RAM based stream
@Override
protected void onMessage(byte[] bytes) {
try {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
short response = dis.readShort();
if(response == MESSAGE_TYPE_DRIVER_FOUND) {
driverId = dis.readLong();
User u = cars.get(driverId);
if(u == null) {
u = new User().id.set(driverId);
cars.put(driverId, u);
}
u.car.set(dis.readUTF()).
givenName.set(dis.readUTF()).
surname.set(dis.readUTF()).
currentRating.set(dis.readFloat());
final User finalUser = u;
callSerially(() -> driverFound.carAdded(finalUser));
return;
}
int size = dis.readInt();
List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
onMessage
We also need to handle message reception code.

This is a new special case that provides us with details on the driver that picked up the ride. We are provided with the driver id, car & name. Notice we need the finalUser
variable since car might change and a value that can change can't be passed to an inner class or lambda in Java.
@Override
protected void onMessage(byte[] bytes) {
try {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
short response = dis.readShort();
if(response == MESSAGE_TYPE_DRIVER_FOUND) {
driverId = dis.readLong();
User u = cars.get(driverId);
if(u == null) {
u = new User().id.set(driverId);
cars.put(driverId, u);
}
u.car.set(dis.readUTF()).
givenName.set(dis.readUTF()).
surname.set(dis.readUTF()).
currentRating.set(dis.readFloat());
final User finalUser = u;
callSerially(() -> driverFound.carAdded(finalUser));
return;
}
int size = dis.readInt();
List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
onMessage
This is a list of push keys who we should notify
List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
long id = dis.readLong();
User car = cars.get(id);
if(car == null) {
car = new User().
id.set(id).
latitude.set(dis.readDouble()).
longitude.set(dis.readDouble()).
direction.set(dis.readFloat()).
pushToken.set(dis.readUTF());
cars.put(id, car);
User finalCar = car;
callSerially(() -> carCallback.carAdded(finalCar));
} else {
car.latitude.set(dis.readDouble()).
longitude.set(dis.readDouble()).
direction.set(dis.readFloat()).
pushToken.set(dis.readUTF());
}
if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
onMessage
I added a push token to the driver details so we can send a push message to a specific driver
if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
if(sendPush == null) {
sendPush = new ArrayList<>();
}
sendPush.add(car.pushToken.get());
}
}
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
onMessage
If the car wasn't notified yet add it to the list of cars that we should notify
if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
if(sendPush == null) {
sendPush = new ArrayList<>();
}
sendPush.add(car.pushToken.get());
}
}
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
onMessage
We send the push message in a batch to speed this up
}
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
pushType(3).
apnsAuth(apnsCert, apnsPass, APNS_PRODUCTION).
gcmAuth(GOOGLE_PUSH_AUTH_KEY).sendAsync();
}
} catch(IOException err) {
Log.e(err);
}
}
onMessage
We send push type 3 which includes a data payload (the first section) and a visual payload which you see after the semicolon
public final Property<String, User> pushToken = new Property<>("pushToken");
private final PropertyIndex idx = new PropertyIndex(this, "User", id, givenName,
surname, phone, email, facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, authToken, password, pushToken);
User
Before we can compile that code we need to add a pushToken attribute to the User class
public static void hailRide(String from, String to, CarAdded callback) {
instance.driverFound = callback;
instance.from = from;
instance.to = to;
instance.notificationList = new ArrayList<>();
instance.halingRadius = Motion.createLinearMotion(500, 2000, 30000);
instance.halingRadius.start();
instance.hailing = HAILING_ON;
instance.server.sendLocationUpdate();
}
hailRide
Finally we have the hailRide method which is relatively simple. There isn't much we need to cover here. It just initializes the variables and starts the Motion object for the
expanding radius.

This should conclude the client side of the hailing process and now we need to address the server side…

More Related Content

PDF
Creating an Uber Clone - Part XXV.pdf
PDF
Creating an Uber Clone - Part XXVII - Transcript.pdf
PDF
Creating an Uber Clone - Part XV - Transcript.pdf
PDF
Creating an Uber Clone - Part XIII - Transcript.pdf
PDF
Creating an Uber Clone - Part XII - Transcript.pdf
PDF
Creating an Uber Clone - Part XV.pdf
PPTX
Beacons, Raspberry Pi & Node.js
PDF
Creating an Uber Clone - Part XIII.pdf
Creating an Uber Clone - Part XXV.pdf
Creating an Uber Clone - Part XXVII - Transcript.pdf
Creating an Uber Clone - Part XV - Transcript.pdf
Creating an Uber Clone - Part XIII - Transcript.pdf
Creating an Uber Clone - Part XII - Transcript.pdf
Creating an Uber Clone - Part XV.pdf
Beacons, Raspberry Pi & Node.js
Creating an Uber Clone - Part XIII.pdf

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf

Recently uploaded (20)

PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
NewMind AI Weekly Chronicles - August'25-Week II
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PPTX
sap open course for s4hana steps from ECC to s4
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Spectroscopy.pptx food analysis technology
PPTX
MYSQL Presentation for SQL database connectivity
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PPTX
Machine Learning_overview_presentation.pptx
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
NewMind AI Weekly Chronicles - August'25-Week II
Assigned Numbers - 2025 - Bluetooth® Document
Review of recent advances in non-invasive hemoglobin estimation
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
sap open course for s4hana steps from ECC to s4
Encapsulation_ Review paper, used for researhc scholars
Chapter 3 Spatial Domain Image Processing.pdf
The Rise and Fall of 3GPP – Time for a Sabbatical?
Digital-Transformation-Roadmap-for-Companies.pptx
Spectroscopy.pptx food analysis technology
MYSQL Presentation for SQL database connectivity
gpt5_lecture_notes_comprehensive_20250812015547.pdf
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Machine Learning_overview_presentation.pptx
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Advanced methodologies resolving dimensionality complications for autism neur...

Creating an Uber Clone - Part XXV - Transcript.pdf

  • 1. Creating an Uber Clone - Part XXV Next we’ll go into the underlying business logic portion of the hailing process on the client
  • 2. LocationService ✦Mark hailing in websocket code ✦Server checks available area ✦Send push notifications to available drivers ✦Expand the circle of search © Codename One 2017 all rights reserved Up until now I kept hailing as a vague process. I think Uber probably has a far more elaborate system than the one I developed in an hour but this should work fine for most cases. Hailing includes the following phases: We mark that we are interested in hailing in the location service WebSocket code. The server checks which drivers are available in the area and returns to us their push keys We send push notifications to available drivers As time moves on we expand the circle of search for drivers
  • 3. public static final String CODENAME_ONE_PUSH_KEY = "----"; public static final String GOOGLE_PUSH_AUTH_KEY = "----"; public static final String APNS_DEV_PUSH_CERT = "----"; public static final String APNS_PROD_PUSH_CERT = "----"; public static final String APNS_DEV_PUSH_PASS = "----"; public static final String APNS_PROD_PUSH_PASS = "----"; public static final boolean APNS_PRODUCTION = false; Globals In order to do this I had to make some changes to the Websocket protocol in the LocationService class. But first we need some additional variables in the Globals class. I'll go into more details on those values in the next chapter but for now we need the variables only. All of these API's require developer keys which you can obtain from their respective websites. I've edited the Globals class to include these new keys required by the 3 API's Right now we can leave these all blank and get to them in the next chapter.
  • 4. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService Let's move to the LocationService class and the variables I had to add When a driver accepts our hail the server sends a special message indicating that the hail was accepted
  • 5. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService Previously we had 2 modes for polling the server for searching and not searching. I added a 3rd mode that allows us to disable hailing
  • 6. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService I've made the LocationService into a singleton
  • 7. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService These represent whether we are hailing and if so to what radius? We use a Motion object to get a growing radius that will stretch over time to encapsulate a wider region for hailing
  • 8. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService Our source and destination values which we need to broadcast a hail
  • 9. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService When we send a push notification to a car we need to make sure we didn't already notify it
  • 10. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService The unique id of the driver we've found
  • 11. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4; private static final short HAILING_OFF = 0; private static final short HAILING_ON = 1; private static final short HAILING_TURN_OFF = 2; private static LocationService instance; private int hailing; private Motion halingRadius; private String from; private String to; private List<String> notificationList; private long driverId; private CarAdded driverFound; LocationService This is the callback we invoke when a driver is found
  • 12. if(ll == lon && lt == lat && dir == direction && hailing == HAILING_OFF) { // no need to do an update return; } sendLocationUpdate Now that these are out of the way lets look at the other things that need doing. We changed the way we handle the communication protocol by and we added some additional details. First we need to ignore location changes when doing hailing which we can do by adding that condition.
  • 13. dos.writeDouble(lat); dos.writeDouble(lon); dos.writeFloat(dir); if(hailing == HAILING_ON) { dos.writeDouble(((double)halingRadius.getValue()) / 1000.0); dos.writeByte(HAILING_ON); byte[] fromBytes = from.getBytes("UTF-8"); byte[] toBytes = to.getBytes("UTF-8"); dos.writeShort(fromBytes.length); dos.write(fromBytes); dos.writeShort(toBytes.length); dos.write(toBytes); } else { dos.writeDouble(1); dos.writeByte(hailing); if(hailing == HAILING_TURN_OFF) { hailing = HAILING_OFF; } } dos.flush(); send(bos.toByteArray()); } catch(IOException err) { Log.e(err); } sendLocationUpdate Next we need to change the protocol a little bit This was previously limited to 0 only and now we check if we are in hailing mode
  • 14. dos.writeDouble(lat); dos.writeDouble(lon); dos.writeFloat(dir); if(hailing == HAILING_ON) { dos.writeDouble(((double)halingRadius.getValue()) / 1000.0); dos.writeByte(HAILING_ON); byte[] fromBytes = from.getBytes("UTF-8"); byte[] toBytes = to.getBytes("UTF-8"); dos.writeShort(fromBytes.length); dos.write(fromBytes); dos.writeShort(toBytes.length); dos.write(toBytes); } else { dos.writeDouble(1); dos.writeByte(hailing); if(hailing == HAILING_TURN_OFF) { hailing = HAILING_OFF; } } dos.flush(); send(bos.toByteArray()); } catch(IOException err) { Log.e(err); } sendLocationUpdate During hailing mode the radius of search grows over time
  • 15. dos.writeDouble(lat); dos.writeDouble(lon); dos.writeFloat(dir); if(hailing == HAILING_ON) { dos.writeDouble(((double)halingRadius.getValue()) / 1000.0); dos.writeByte(HAILING_ON); byte[] fromBytes = from.getBytes("UTF-8"); byte[] toBytes = to.getBytes("UTF-8"); dos.writeShort(fromBytes.length); dos.write(fromBytes); dos.writeShort(toBytes.length); dos.write(toBytes); } else { dos.writeDouble(1); dos.writeByte(hailing); if(hailing == HAILING_TURN_OFF) { hailing = HAILING_OFF; } } dos.flush(); send(bos.toByteArray()); } catch(IOException err) { Log.e(err); } sendLocationUpdate We send the from/to values as UTF-8 encoded strings which allows us to communicate locale specific locations
  • 16. dos.writeDouble(lat); dos.writeDouble(lon); dos.writeFloat(dir); if(hailing == HAILING_ON) { dos.writeDouble(((double)halingRadius.getValue()) / 1000.0); dos.writeByte(HAILING_ON); byte[] fromBytes = from.getBytes("UTF-8"); byte[] toBytes = to.getBytes("UTF-8"); dos.writeShort(fromBytes.length); dos.write(fromBytes); dos.writeShort(toBytes.length); dos.write(toBytes); } else { dos.writeDouble(1); dos.writeByte(hailing); if(hailing == HAILING_TURN_OFF) { hailing = HAILING_OFF; } } dos.flush(); send(bos.toByteArray()); } catch(IOException err) { Log.e(err); } sendLocationUpdate When we turn off hailing in the server it's a "one time thing". After it's off we can go back to the regular mode
  • 17. dos.writeDouble(lat); dos.writeDouble(lon); dos.writeFloat(dir); if(hailing == HAILING_ON) { dos.writeDouble(((double)halingRadius.getValue()) / 1000.0); dos.writeByte(HAILING_ON); byte[] fromBytes = from.getBytes("UTF-8"); byte[] toBytes = to.getBytes("UTF-8"); dos.writeShort(fromBytes.length); dos.write(fromBytes); dos.writeShort(toBytes.length); dos.write(toBytes); } else { dos.writeDouble(1); dos.writeByte(hailing); if(hailing == HAILING_TURN_OFF) { hailing = HAILING_OFF; } } dos.flush(); send(bos.toByteArray()); } catch(IOException err) { Log.e(err); } sendLocationUpdate This isn't likely as this is a RAM based stream
  • 18. @Override protected void onMessage(byte[] bytes) { try { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); short response = dis.readShort(); if(response == MESSAGE_TYPE_DRIVER_FOUND) { driverId = dis.readLong(); User u = cars.get(driverId); if(u == null) { u = new User().id.set(driverId); cars.put(driverId, u); } u.car.set(dis.readUTF()). givenName.set(dis.readUTF()). surname.set(dis.readUTF()). currentRating.set(dis.readFloat()); final User finalUser = u; callSerially(() -> driverFound.carAdded(finalUser)); return; } int size = dis.readInt(); List<String> sendPush = null; for(int iter = 0 ; iter < size ; iter++) { onMessage We also need to handle message reception code. This is a new special case that provides us with details on the driver that picked up the ride. We are provided with the driver id, car & name. Notice we need the finalUser variable since car might change and a value that can change can't be passed to an inner class or lambda in Java.
  • 19. @Override protected void onMessage(byte[] bytes) { try { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); short response = dis.readShort(); if(response == MESSAGE_TYPE_DRIVER_FOUND) { driverId = dis.readLong(); User u = cars.get(driverId); if(u == null) { u = new User().id.set(driverId); cars.put(driverId, u); } u.car.set(dis.readUTF()). givenName.set(dis.readUTF()). surname.set(dis.readUTF()). currentRating.set(dis.readFloat()); final User finalUser = u; callSerially(() -> driverFound.carAdded(finalUser)); return; } int size = dis.readInt(); List<String> sendPush = null; for(int iter = 0 ; iter < size ; iter++) { onMessage This is a list of push keys who we should notify
  • 20. List<String> sendPush = null; for(int iter = 0 ; iter < size ; iter++) { long id = dis.readLong(); User car = cars.get(id); if(car == null) { car = new User(). id.set(id). latitude.set(dis.readDouble()). longitude.set(dis.readDouble()). direction.set(dis.readFloat()). pushToken.set(dis.readUTF()); cars.put(id, car); User finalCar = car; callSerially(() -> carCallback.carAdded(finalCar)); } else { car.latitude.set(dis.readDouble()). longitude.set(dis.readDouble()). direction.set(dis.readFloat()). pushToken.set(dis.readUTF()); } if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){ if(!notificationList.contains(car.pushToken.get())) { notificationList.add(car.pushToken.get()); onMessage I added a push token to the driver details so we can send a push message to a specific driver
  • 21. if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){ if(!notificationList.contains(car.pushToken.get())) { notificationList.add(car.pushToken.get()); if(sendPush == null) { sendPush = new ArrayList<>(); } sendPush.add(car.pushToken.get()); } } } if(sendPush != null) { String[] devices = new String[sendPush.size()]; sendPush.toArray(devices); String apnsCert = APNS_DEV_PUSH_CERT; String apnsPass = APNS_DEV_PUSH_PASS; if(APNS_PRODUCTION) { apnsCert = APNS_PROD_PUSH_CERT; apnsPass = APNS_PROD_PUSH_PASS; } new Push(CODENAME_ONE_PUSH_KEY, "#" + UserService.getUser().id.getLong() + ";Ride pending from: " + from + " to: " + to, devices). onMessage If the car wasn't notified yet add it to the list of cars that we should notify
  • 22. if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){ if(!notificationList.contains(car.pushToken.get())) { notificationList.add(car.pushToken.get()); if(sendPush == null) { sendPush = new ArrayList<>(); } sendPush.add(car.pushToken.get()); } } } if(sendPush != null) { String[] devices = new String[sendPush.size()]; sendPush.toArray(devices); String apnsCert = APNS_DEV_PUSH_CERT; String apnsPass = APNS_DEV_PUSH_PASS; if(APNS_PRODUCTION) { apnsCert = APNS_PROD_PUSH_CERT; apnsPass = APNS_PROD_PUSH_PASS; } new Push(CODENAME_ONE_PUSH_KEY, "#" + UserService.getUser().id.getLong() + ";Ride pending from: " + from + " to: " + to, devices). onMessage We send the push message in a batch to speed this up
  • 23. } } if(sendPush != null) { String[] devices = new String[sendPush.size()]; sendPush.toArray(devices); String apnsCert = APNS_DEV_PUSH_CERT; String apnsPass = APNS_DEV_PUSH_PASS; if(APNS_PRODUCTION) { apnsCert = APNS_PROD_PUSH_CERT; apnsPass = APNS_PROD_PUSH_PASS; } new Push(CODENAME_ONE_PUSH_KEY, "#" + UserService.getUser().id.getLong() + ";Ride pending from: " + from + " to: " + to, devices). pushType(3). apnsAuth(apnsCert, apnsPass, APNS_PRODUCTION). gcmAuth(GOOGLE_PUSH_AUTH_KEY).sendAsync(); } } catch(IOException err) { Log.e(err); } } onMessage We send push type 3 which includes a data payload (the first section) and a visual payload which you see after the semicolon
  • 24. public final Property<String, User> pushToken = new Property<>("pushToken"); private final PropertyIndex idx = new PropertyIndex(this, "User", id, givenName, surname, phone, email, facebookId, googleId, driver, car, currentRating, latitude, longitude, direction, authToken, password, pushToken); User Before we can compile that code we need to add a pushToken attribute to the User class
  • 25. public static void hailRide(String from, String to, CarAdded callback) { instance.driverFound = callback; instance.from = from; instance.to = to; instance.notificationList = new ArrayList<>(); instance.halingRadius = Motion.createLinearMotion(500, 2000, 30000); instance.halingRadius.start(); instance.hailing = HAILING_ON; instance.server.sendLocationUpdate(); } hailRide Finally we have the hailRide method which is relatively simple. There isn't much we need to cover here. It just initializes the variables and starts the Motion object for the expanding radius. This should conclude the client side of the hailing process and now we need to address the server side…