Handwritten Text Recognition for manuscripts and early printed texts
Creating an Uber Clone - Part XIII: Twilio & WebSocket Setup
1. Creating an Uber Clone - Part XIII
Now that we have a server and a mock client we need to connect them together so we’ll have a working prototype. We also need to implement some core functionality
such as SMS Activation
3. public class Globals {
public static final String SERVER_URL = "http://localhost:8080/";
public static final String SERVER_SOCKET_URL = "ws://localhost:8080/wsMsg";
public static final String TWILIO_ACCOUNT_SID = "AC....";
public static final String TWILIO_AUTH_TOKEN = "1d....";
public static final String TWILIO_FROM_PHONE = "+14.....";
}
Globals
Once you have those values you can create a new Globals class which we will use for the global application data.
Notice that you might want to replace localhost with your IP during development so you can test a device against a server running on your machine. The device would
obviously need to be connected to the same wifi
4. public class Globals {
public static final String SERVER_URL = "http://localhost:8080/";
public static final String SERVER_SOCKET_URL = "ws://localhost:8080/wsMsg";
public static final String TWILIO_ACCOUNT_SID = "AC....";
public static final String TWILIO_AUTH_TOKEN = "1d....";
public static final String TWILIO_FROM_PHONE = "+14.....";
}
Globals
The following values are the values we have from Twilio. For convenience I use static import for these constants within the code
5. public class User implements PropertyBusinessObject {
public final LongProperty<User> id = new LongProperty<>("id");
public final Property<String, User> givenName = new Property<>("givenName");
public final Property<String, User> surname = new Property<>("surname");
public final Property<String, User> phone = new Property<>("phone");
public final Property<String, User> email = new Property<>("email");
public final Property<String, User> facebookId = new Property<>("facebookId");
public final Property<String, User> googleId = new Property<>("googleId");
public final BooleanProperty<User> driver = new BooleanProperty<>("driver");
public final Property<String, User> car = new Property<>("givenName");
public final FloatProperty<User> currentRating = new FloatProperty<>("currentRating");
public final DoubleProperty<User> latitude = new DoubleProperty<>("latitude");
public final DoubleProperty<User> longitude = new DoubleProperty<>("longitude");
public final FloatProperty<User> direction = new FloatProperty<>("direction");
public final Property<String, User> authToken = new Property<>("authToken");
public final Property<String, User> password = new Property<>("password");
private final PropertyIndex idx = new PropertyIndex(this, "User", id, givenName,
surname, phone, email, facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, authToken, password);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
}
User (Client Side)
The User class on the client side mirrors the UserDAO but uses the Properties syntax so we can leverage observability, JSON, persistence and other cool capabilities. If
you are familiar with properties already you won’t notice anything special about this class it’s just a standard property object. If you aren’t familiar with properties please
check out the video covering them as it would be helpful moving forward.
6. public class UserService {
private static User me;
public static void loadUser() {
me = new User();
PreferencesObject.create(me).bind();
}
public static void logout() {
Preferences.set("token", null);
}
public static boolean isLoggedIn() {
return Preferences.get("token", null) != null;
}
public static void sendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
Random r = new Random();
String val = "";
for(int iter = 0 ; iter < 4 ; iter++) {
val += r.nextInt(10);
}
Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
UserService (Client Side)
We need to define a connection layer that will abstract the server access code. This will allow us flexibility as we modify the server implementation and the client
implementation. It will also make testing far easier by separating the different pieces into tiers.
The abstraction is similar to the one we have in the server. I chose to go with a mostly static class implementation for the user service as it's inherently a static web
service. It makes no sense to have more than one UserService.
Once logged in we will cache the current user object here so we have all the data locally and don't need server communication for every query
7. public class UserService {
private static User me;
public static void loadUser() {
me = new User();
PreferencesObject.create(me).bind();
}
public static void logout() {
Preferences.set("token", null);
}
public static boolean isLoggedIn() {
return Preferences.get("token", null) != null;
}
public static void sendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
Random r = new Random();
String val = "";
for(int iter = 0 ; iter < 4 ; iter++) {
val += r.nextInt(10);
}
Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
UserService (Client Side)
We bind the user object to preferences so changes to the user object implicitly connect to the Preferences storage API and visa versa. Preferences allow us to store keys
and values in storage which maps every entry to a similar key/value pair
8. public class UserService {
private static User me;
public static void loadUser() {
me = new User();
PreferencesObject.create(me).bind();
}
public static void logout() {
Preferences.set("token", null);
}
public static boolean isLoggedIn() {
return Preferences.get("token", null) != null;
}
public static void sendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
Random r = new Random();
String val = "";
for(int iter = 0 ; iter < 4 ; iter++) {
val += r.nextInt(10);
}
Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
UserService (Client Side)
Whether we are logged in or out is determined by the token value. We need that to send updates to the server side
9. public class UserService {
private static User me;
public static void loadUser() {
me = new User();
PreferencesObject.create(me).bind();
}
public static void logout() {
Preferences.set("token", null);
}
public static boolean isLoggedIn() {
return Preferences.get("token", null) != null;
}
public static void sendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
Random r = new Random();
String val = "";
for(int iter = 0 ; iter < 4 ; iter++) {
val += r.nextInt(10);
}
Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
UserService (Client Side)
I'm creating the 4 digit verification code and sending it via the Twilio SMS webservice API. I'm also storing the value in Preferences so I can check against it when it's
received even if the app dies for some reason
10. Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
public static void resendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
tw.sendSmsAsync(phoneNumber,
Preferences.get("phoneVerification", null));
}
public static boolean validateSMSActivationCode(String code) {
String val = Preferences.get("phoneVerification", null);
return code.indexOf(val) > -1 && code.length() < 80;
}
public static boolean userExists(String phoneNumber) {
Response<byte[]> b = Rest.get(SERVER_URL + "user/exists").
acceptJson().
queryParam("phone", phoneNumber).getAsBytes();
if(b.getResponseCode() == 200) {
// the t from true
return b.getResponseData()[0] == (byte)'t';
}
return false;
}
UserService (Client Side)
This method is invoked to validate the received SMS code, notice I don't just use equals since the validation string might include the full SMS text. This can happen on
Android where we can automatically validate. Notice I still limit the length of the string to prevent an attack where a user can inject all the possible 4 code combinations
into this method
11. Preferences.set("phoneVerification", val);
tw.sendSmsAsync(phoneNumber, val);
}
public static void resendSMSActivationCode(String phoneNumber) {
TwilioSMS tw = TwilioSMS.create(TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE);
tw.sendSmsAsync(phoneNumber,
Preferences.get("phoneVerification", null));
}
public static boolean validateSMSActivationCode(String code) {
String val = Preferences.get("phoneVerification", null);
return code.indexOf(val) > -1 && code.length() < 80;
}
public static boolean userExists(String phoneNumber) {
Response<byte[]> b = Rest.get(SERVER_URL + "user/exists").
acceptJson().
queryParam("phone", phoneNumber).getAsBytes();
if(b.getResponseCode() == 200) {
// the t from true
return b.getResponseData()[0] == (byte)'t';
}
return false;
}
UserService (Client Side)
Maps to the user exists method in the server which we use to determine add/login flows. I use the Rest API to make a single connection with the get method. In this case
the response is the string true or the string false so I can just check against the letter t
12. // the t from true
return b.getResponseData()[0] == (byte)'t';
}
return false;
}
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;
}
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).
getAsJsonMapAsync(new Callback<Response<Map>>() {
UserService (Client Side)
When adding a user I use the Rest API's post method. Here I can set the body to the JSON content of the User object. The response is a String token representing the
user which we can now store into `Preferences
13. 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).
getAsJsonMapAsync(new Callback<Response<Map>>() {
@Override
public void onSucess(Response<Map> value) {
me = new User();
me.getPropertyIndex().populateFromMap(
value.getResponseData());
Preferences.set("token", me.authToken.get());
PreferencesObject.create(me).bind();
onSuccess.onSucess(me);
}
@Override
public void onError(Object sender, Throwable err,
int errorCode, String errorMessage) {
onError.onError(null, err, errorCode, errorMessage);
}
});
UserService (Client Side)
The login method accepts a phone and password and is invoked after we validated the phone. It can succeed or fail.
14. 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).
getAsJsonMapAsync(new Callback<Response<Map>>() {
@Override
public void onSucess(Response<Map> value) {
me = new User();
me.getPropertyIndex().populateFromMap(
value.getResponseData());
Preferences.set("token", me.authToken.get());
PreferencesObject.create(me).bind();
onSuccess.onSucess(me);
}
@Override
public void onError(Object sender, Throwable err,
int errorCode, String errorMessage) {
onError.onError(null, err, errorCode, errorMessage);
}
});
UserService (Client Side)
If we get back a token that means the UserAuthenticationException wasn't thrown in the server and we can set it into the Preferences otherwise we need to send a failure
callback