Automating Google Workspace (GWS) & more with Apps Script
Creating an Uber Clone - Part XXIX - Transcript.pdf
1. Creating an Uber Clone - Part XXIX
Before we go into the big UI changes lets go over some of the networking level changes in the code
2. public class Ride implements PropertyBusinessObject {
public final LongProperty<Ride> userId = new LongProperty<>("userId");
public final Property<String, Ride> name = new Property<>("name");
public final Property<String, Ride> from = new Property<>("from");
public final Property<String, Ride> destination = new Property<>("destination");
private final PropertyIndex idx = new PropertyIndex(this, "Ride",
userId, name, from, destination);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
Ride (Client Side)
In order to encapsulate the new Ride JSON object from the server I added an equivalent properties object locally. The class is practically identical to the server side
RideDAO class but uses the properties syntax instead of the standard POJO syntax.
3. public class DriverService {
private static String currentRide;
public static void fetchRideDetails(long id,
SuccessCallback<Ride> rideDetails) {
Rest.get(SERVER_URL + "ride/get").
acceptJson().queryParam("id", "" + id).
getAsJsonMap(response -> {
Map data = response.getResponseData();
if(data != null) {
Ride r = new Ride();
r.getPropertyIndex().populateFromMap(data);
rideDetails.onSucess(r);
}
});
}
public static boolean acceptRide(long id) {
Response<String> response =
Rest.get(SERVER_URL + "ride/accept").
acceptJson().
queryParam("token", UserService.getToken()).
queryParam("userId", "" + id).
getAsString();
if(response.getResponseCode() == 200) {
currentRide = response.getResponseData();
return true;
}
return false;
DriverService
The driver service class is a static representation of the driver specific server API’s.
This field represents the id of the current ride from this driver
4. public class DriverService {
private static String currentRide;
public static void fetchRideDetails(long id,
SuccessCallback<Ride> rideDetails) {
Rest.get(SERVER_URL + "ride/get").
acceptJson().queryParam("id", "" + id).
getAsJsonMap(response -> {
Map data = response.getResponseData();
if(data != null) {
Ride r = new Ride();
r.getPropertyIndex().populateFromMap(data);
rideDetails.onSucess(r);
}
});
}
public static boolean acceptRide(long id) {
Response<String> response =
Rest.get(SERVER_URL + "ride/accept").
acceptJson().
queryParam("token", UserService.getToken()).
queryParam("userId", "" + id).
getAsString();
if(response.getResponseCode() == 200) {
currentRide = response.getResponseData();
return true;
}
return false;
DriverService
Here we have the standard get method I mentioned earlier to retrieve the ride details and return them via a callback
5. public class DriverService {
private static String currentRide;
public static void fetchRideDetails(long id,
SuccessCallback<Ride> rideDetails) {
Rest.get(SERVER_URL + "ride/get").
acceptJson().queryParam("id", "" + id).
getAsJsonMap(response -> {
Map data = response.getResponseData();
if(data != null) {
Ride r = new Ride();
r.getPropertyIndex().populateFromMap(data);
rideDetails.onSucess(r);
}
});
}
public static boolean acceptRide(long id) {
Response<String> response =
Rest.get(SERVER_URL + "ride/accept").
acceptJson().
queryParam("token", UserService.getToken()).
queryParam("userId", "" + id).
getAsString();
if(response.getResponseCode() == 200) {
currentRide = response.getResponseData();
return true;
}
return false;
DriverService
We use accept to indicate a driver is accepting a users hailing, if he doesn't we don't care... Once accepted he gets a reference to the id of the newly created Ride object
in the server. Notice that failure is indeed a possibility. For example if the user canceled the ride or a different driver accepted first
6. }
public static boolean acceptRide(long id) {
Response<String> response =
Rest.get(SERVER_URL + "ride/accept").
acceptJson().
queryParam("token", UserService.getToken()).
queryParam("userId", "" + id).
getAsString();
if(response.getResponseCode() == 200) {
currentRide = response.getResponseData();
return true;
}
return false;
}
public static void startRide() {
Rest.post(SERVER_URL + "ride/start").
acceptJson().
queryParam("id", currentRide).
getAsString();
}
public static void finishRide() {
Rest.post(SERVER_URL + "ride/finish").
acceptJson().
queryParam("id", currentRide).
getAsString();
}
}
DriverService
When we invoke startRide and finishRide we use the currentRide id unlike the userId which we used to create the ride
7. public static void findLocation(String name,
SuccessCallback<Coord> location) {
Rest.get("https://maps.googleapis.com/maps/api/geocode/json").
queryParam("address", name).
queryParam("key", Globals.GOOGLE_GEOCODING_KEY).
getAsJsonMap(callbackMap -> {
Map data = callbackMap.getResponseData();
if(data != null) {
List results = (List)data.get("results");
if(results != null && results.size() > 0) {
Map firstResult = (Map)results.get(0);
Map geometryMap = (Map)firstResult.get("geometry");
Map locationMap = (Map)geometryMap.get("location");
double lat = Util.toDoubleValue(locationMap.get("lat"));
double lon = Util.toDoubleValue(locationMap.get("lng"));
location.onSucess(new Coord(lat, lon));
}
}
});
}
SearchService
In SearchService we had to add support for geocoding. Before this we only had the reverse geocoding which we use to locate the from/to points on the map.
We need this API since the driver only gets the to/from location names and we want to plot them on the map.
There isn't all that much to say about this method. It just searches the Google geocode API for a location with the given name and returns the coordinate of that location.
8. public class UserService {
private static User me;
public static User getUser() {
return me;
}
public static String getToken() {
if(UberClone.isDriverMode()) {
return Preferences.get("driver-token", null);
} else {
return Preferences.get("token", null);
}
}
public static void loadUser() {
me = new User();
if(UberClone.isDriverMode()) {
PreferencesObject.create(me).setPrefix("driver").bind();
} else {
PreferencesObject.create(me).bind();
}
if(Display.getInstance().isSimulator()) {
Log.p("User details: " + me.getPropertyIndex().toString());
}
}
UserService
There were many small changes in the UserService class most of them relate to the way identity is managed in the app.
One of the big problems in having two applications with one project is that both projects share the same data in the simulator. So if I want to launch the project twice,
once to run the user version and once for the driver version. I will have a problem. Both will inspect the same storage information and use the same user identity. They
might collide...
Notice that this is purely a simulator problem. The simulator doesn't currently isolate separate applications. Ideally this is something to improve in the simulator and might
not be an issue in the future. The solution is simple though. We can just save the data to different "locations" or "keys" if we are in the driver app. Lets review the
changes…
This is illustrated perfectly in the first change in this class. We use a different token to determine if the user is logged in for the case of a driver. Notice we replaced the
invocations of Preferences.get("token", null) that were all over the code with this method call
9. public class UserService {
private static User me;
public static User getUser() {
return me;
}
public static String getToken() {
if(UberClone.isDriverMode()) {
return Preferences.get("driver-token", null);
} else {
return Preferences.get("token", null);
}
}
public static void loadUser() {
me = new User();
if(UberClone.isDriverMode()) {
PreferencesObject.create(me).setPrefix("driver").bind();
} else {
PreferencesObject.create(me).bind();
}
if(Display.getInstance().isSimulator()) {
Log.p("User details: " + me.getPropertyIndex().toString());
}
}
UserService
The Preferences bind API lets us set a different prefix for the driver object that will be prepended to the properties in the Preferences
10. public class UserService {
private static User me;
public static User getUser() {
return me;
}
public static String getToken() {
if(UberClone.isDriverMode()) {
return Preferences.get("driver-token", null);
} else {
return Preferences.get("token", null);
}
}
public static void loadUser() {
me = new User();
if(UberClone.isDriverMode()) {
PreferencesObject.create(me).setPrefix("driver").bind();
} else {
PreferencesObject.create(me).bind();
}
if(Display.getInstance().isSimulator()) {
Log.p("User details: " + me.getPropertyIndex().toString());
}
}
UserService
This is a cool little trick that allows me to debug with a fake number, I used the isSimulator method to log the verification code on the simulator and can just type it in
even if the twilio code failed to send a message
11. public static void registerPushToken(String pushToken) {
Rest.get(SERVER_URL + "user/setPushToken").
queryParam("token", getToken()).
queryParam("pushToken", pushToken).getAsStringAsync(
new Callback<Response<String>>() {
@Override
public void onSucess(Response<String> value) {
}
@Override
public void onError(Object sender, Throwable err, int errorCode,
String errorMessage) {
}
});
}
public static boolean addNewUser(User u) {
Response<String> token = Rest.post(SERVER_URL + "user/add").
jsonContent().
body(u.getPropertyIndex().toJSON()).getAsString();
if(token.getResponseCode() != 200) {
return false;
}
UserService
Sends the push token to the server. Right now we don't need to do anything in the event callback. This is invoked on registration success and allows the server to send
driver push keys to the client
12. public static boolean addNewUser(User u) {
Response<String> token = Rest.post(SERVER_URL + "user/add").
jsonContent().
body(u.getPropertyIndex().toJSON()).getAsString();
if(token.getResponseCode() != 200) {
return false;
}
if(UberClone.isDriverMode()) {
Preferences.set("driver-token", token.getResponseData());
registerPush();
} else {
Preferences.set("token", token.getResponseData());
}
return true;
}
public static void loginWithPhone(String phoneNumber,
String password, final SuccessCallback<User> onSuccess,
final FailureCallback<Object> onError) {
Rest.get(SERVER_URL + "user/login").
acceptJson().
queryParam("password", password).
queryParam("phone", phoneNumber).
UserService
After the first activation of the driver app we need to register for push. Notice I'm using the version of this method from the CN class with static import but the callback
will go as expected into the DriverApp class