JBay Solutions Development Blog on Java, Android, Play2 and others
RSS RSS RSS RSS

How to Implement a Mechanism like QR code scanning of WhatsApp Web

This tutorial came to be due to a question I found on Stackoverflow titled Mechanism behind QR code scanning of whatsapp webapp and the investigation and answer that I gave .

Because the answer was given in real broad strokes, I decided to create a tutorial on how to implement something like what is being used by WhatsApp. This tutorial will cover the theory behind it, the implementation of an Android App to read QR codes and send and receive data to a Server, and also an implementation of the Server side using Play2 Framework. Another implementation using Glassfish 4 and Tyrus will follow at some point if there is interest.

It is assumed some degree of knowledge on Android development for the Android sections, some knowledge of Play2 Framework for the Play2 Framework server side implementation, and obviously Java. I'll try to cover all the basics and specifics, but it will be quite hard to explain everything is 100% detail.

  1. To Start
  2. What is the plan?
  3. Server and Web Implementation
  4. Android Client Implementation
  5. REFERENCES



To Start

So, first of all, all source code is available at these two locations:

One is the Web server side bit, which will display a QR Code and allow the auth and also do some chatting, and the other is the Android app that will read QR Codes and also work as a chat client.

These are not particularly pretty, they are a proof of concept and just work. Contributions, forks, etc, are welcome.

What is the plan?

For our small proof of concept, just like with WhatsApp, the chat bit of the Web App will work using WebSockets. When a user opens the URL for the Web App thing ( using http://localhost:9000/ ) the Web page gets a few Javascripts (we are also using JQuery here, just to simplify some of the work we need to be done, and bootstrap so that everything doesn't just look too horrible), some CSSs and then it opens a WebSocket, where it receives immediatly a UUID for it to use to download the QR Code image.

The WebSocket on the browser is listenning for several things, one of which is that UUID for the QR Code retrieval. When the UUID code for the QR Code arrives, the javascript updates the Web Page with that image URL and displays it.

The browser at this point:

  • has a WebSocket open
  • has a QR Code displayed on the screen

The server at this point:

  • Has a WebSocket open with that browser
  • It knows that the WebSocket is associated with a particular QR Code.



On the Android side of things, the Android App will do most of the communications (like authentication ... which is basically lacking, for simplicity sake, and sending messages to the server) using normal GET requests to the Play2 Application . To receive messages, we will be using Firebase Cloud Messaging, because it saves us time in implementation, because we won't need to keep sockets and whatnot open with the server, and monitor them for disconnects and other problems, and because it is just the best way for this Proof of Concept.

So, unlike the WhatsApp Android App , we require the user to set his username (we make no checks to see if one already exist, or anything like that... feel free to implement this if you like) and sign in. This just stores the Username on the App so that everytime we send a message using the App, it sends the username of the person who wrote that message. This is not at all secure, because it would be easy to impersonate another person, but then again, this is just a Proof of Concept.

Once the User selected a Username on the Android App, the can start chatting away, or can at any moment do the QR Code scanning. Doing the QR Code scanning, the Android App gets the UUID that is embedded on the QR Code and sends a message to the server saying : OK , this UUID that I just scanned, belongs to this Username. The server receives the message, checks the list of QR Code UUIDs that it has, and then uses the WebSocket associated with it to notify the browser of the username it should use, and refresh the interface with the chat windoe and hide the QR Code bit.

At this point both the Android and Web App use GET requests to send messages to the server, and receive new messages either through the WebSocket (in the case of the Web Client) or through Firebase Cloud Messaging (in case of the Android Client).

In both, when a message is received, the interface is updated to show that message and who wrote it.

This is it.



Server and Web Implementation

For the Web/Server implementation we use Play Framework 2.5.6 "Steamy", you can get it at the official Play Framework site , or just get the complete source code of the WhatsApp Clone Play Project Here .

Lets break it down in smaller bits

Generating QR Codes

To generate QR codes we'll use ZXing lib from google version 3.2.1 , which you can get here https://mvnrepository.com/artifact/com.google.zxing/core/3.2.1 . All code made here was base on this Tutorial with some modifications to serve our purpose.

First we add the ZXing dependency to the Play project, by editing the build.sbt file and adding the following line, to the end of it:

libraryDependencies += "com.google.zxing" % "core" % "3.2.1"

We created a class called QRCodeUtil in the package com.jbaysolutions.whatsappclone.util with a single method called generateQRCode that generates a QR Code BufferedImage for a given UUID:

public static BufferedImage generateQRCode(UUID uuid) {

    int size = 250;
    try {

        Map<EncodeHintType, Object> hintMap = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
        hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);

        QRCodeWriter qrCodeWriter = new QRCodeWriter();

        BitMatrix byteMatrix = qrCodeWriter.encode(
                uuid.toString(),
                BarcodeFormat.QR_CODE,
                size,
                size,
                hintMap
        );

        int CrunchifyWidth = byteMatrix.getWidth();
        BufferedImage image = new BufferedImage(CrunchifyWidth, CrunchifyWidth,
                BufferedImage.TYPE_INT_RGB);
        image.createGraphics();

        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, CrunchifyWidth, CrunchifyWidth);
        graphics.setColor(Color.BLACK);

        for (int i = 0; i < CrunchifyWidth; i++) {
            for (int j = 0; j < CrunchifyWidth; j++) {
                if (byteMatrix.get(i, j)) {
                    graphics.fillRect(i, j, 1, 1);
                }
            }
        }

        logger.debug("You have successfully created QR Code.");

        return image;

    } catch (WriterException e) {
        logger.error("Problem generating QR Code" , e);
        return null;
    }
}

On the HomeController we have a method for returning that QR Code to the browser, for a given UUID String:

public Result getQRCode(String inputUUID) throws IOException {

    UUID uuid = UUID.fromString(inputUUID);

    BufferedImage image = QRCodeUtil.generateQRCode(uuid);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(image, "png", baos);
    byte[] bytes = baos.toByteArray();

    ByteArrayInputStream stream = new ByteArrayInputStream(bytes);

    return ok(stream)
            .as("image/png");

}

On the Routes file the Play Project we have the following route for this particular method on the Controller:

GET        /uuid/:uuid                   controllers.HomeController.getQRCode(uuid: String)

That is it. To test it out, just go to a browser on the same machine where you ahve the Play application running and go, for example, to the following URL: http://localhost:9000/uuid/0e2161d7-25e7-4846-84c8-faaa87a81313 . A QR Code should be displayed on the browser. Try it out with any other valid UUID aswell.



Where will this be used?

This QR Code generation code will be used when we implement the WebSocket bit, and on the Browser side, the listenner receives a UUID. When the WebSocket listener receives a specific command with a UUID, it will update the interface with the UUID QR Code image. We'll see more about that in a minute.

WebSockets - The Server Side

A good place to start if the Play Framework documentation on WebSockets , which... is confusing, but still, it give you examples of stuff working. On the next version of Play, a new sort of implementation for WebSockets oin Java is available, and that is why you'll see a lot of uses of the class LegacyWebSocket, which is the old implementation, that will still be made available in the next releases of Play. We'll use that.

On the HomeController we have the following method:

public LegacyWebSocket<String> socket() {

    return new LegacyWebSocket<String>() {
        @Override
        public void onReady(WebSocket.In<String> in, WebSocket.Out<String> out) {

            in.onMessage(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    // Never Used
                }
            });

            in.onClose(new Runnable() {
                @Override
                public void run() {
                    logger.info("WebSocket Disconnected");
                }
            });

            // Now we do some of or logic 
            // 1- We generate a UUID that we'll associate with this WebSocket 
            String uuid = UUID.randomUUID().toString();

            // 2- Now we store the UUID that we just created, in some fashion
            // to associate it with the IN and OUT streams of the Web Socket
            WebSessionsHandler.getInstance().createNewQRSession(
                    uuid,
                    in,out
            );

            // 3- We send the UUID to the browser, through the WebSocket 
            // we just opened with the browser 
            out.write("wsready###" + uuid);

        }
    };

}

Real simple, instead of returning a Result, like we always (almost always) do in the Controllers, we now return a WebSocket ( ok ok , LegacyWebSocket ) to the Browser. In our implementation of the WebSocket we simply create the Socket and make it so that when it is closed, we log that :

in.onClose(new Runnable() {
    @Override
    public void run() {
        logger.info("WebSocket Disconnected");
    }
});

When we receive a message on the Server, through the WebSocket, we do nothing. The reason is : we will not be send messages from the browser to the Server through the WebSocket. For no particular reason, just because we'll use the same method we use for the Android App :

in.onMessage(new Consumer<String>() {
    @Override
    public void accept(String s) {
        // Never Used
    }
});

Then we do some logic that is associated with our WhatsApp Clone thing. First we generate an UUID to associate with the WebSocket on the server side, and later to generate the QR Code on the Browser:

String uuid = UUID.randomUUID().toString();

We then store the IN and OUT "streams" of the WebSocket in association with the UUID :

WebSessionsHandler.getInstance().createNewQRSession(
        uuid,
        in,out
);

Our implementation of WebSessionsHandler isn't particularly nice, but it does the job. We store the In and OUT associated with an UUID, which is just what wew need at this stage.

Then, we use the OUT stream to send the UUID to the browser. We are sending a String that starts with "wsready###" followed by the UUID. This just makes it easier to identify the type of message, and to split the string on the Client side and extract the UUID later:

out.write("wsready###" + uuid);

That is it for the WebSocket code on the Server side. We just need to add a route to the Routes file and give it a test run. The route should look like this:

GET        /ws                           controllers.HomeController.socket()

To test it out, use the following link : http://www.websocket.org/echo.html and use ws://localhost:9000/ws as the test URL. Connect and see the test benchmark receive an UUID!



WebSockets and Interface stuff - The Web Side

Interface Stuff

First things first, on the main.scala.html template, on the Head of the HTML we import JQuery and Bootstrap:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>

<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

And inside the body, but right at the end:

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

But that is not all, we also import a Javascript that we will write called whatsappclone.js` :

<script src="@routes.Assets.versioned("javascripts/whatsappclone.js")" type="text/javascript"></script>

On this script we will write all of the logic we will describe next.

Also, on the index.scala.html template file we write it like this:

@(title: String)

@main(title) {

    <div class="page-header">
      <h1>WhatsApp Web App Clone Tutorial</h1>
    </div>


    <div class="container" id="chatarea" style="display: none">
        <div class="row">
            <div class="col-md-12">
                <div class="list-group" id="product-filter-data">
                    <div id="chatlist" style="height: 300px; max-height: 300px; overflow-y: scroll">

                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-md-12">
                <div class="input-group">
                    <input id="messageInput" type="text" class="form-control" placeholder="Say something...">
                    <span class="input-group-btn">
                        <button class="btn btn-default" type="button" onclick="sendMessage();">Send</button>
                    </span>
                </div><!-- /input-group -->
            </div>
        </div>
    </div>


    <div id="qrcodearea">
        <img id="qrcode" src="">
    </div>

}

The important bits to pay attention are :

  • There is a DIV with id qrcodearea with an img inside. This is where the QR Code will be displayed

  • There is a DIV with if chatarea . This is the Chat bit. It has a List , where the messages will be displayed, and also an input with id messageInput which is where the user will write messages he wants to send.

    Notice that the chatarea div is not being displayed. This is the default startup mode we want: not showing chat area until the user is authenticated with the QR Code.



WebSockets Javascript Stuff

On the main.scala.html file we are importing a javascript called whatsappclone.js, just like we said on the previous section. This is the javascript that contains all the logic. When the javascript is loaded and the document is ready we do a couple of things:

  • We find the divs that we want to hide and show and assign them to some variables, just for confort.
  • We initialize the WebSocket
  • We also add a Listener on the messageInput field, for the return key, and call send message when the return key is pressed while on the input field.

This is the code for that:

$(document).ready(function(){
    console.log("Starting WhatsApp Clone");
    qrcodeareaDiv = $("#qrcodearea");
    chatareaDiv = $("#chatarea");
    chatareaDiv.css('display','none')
    chatList = $("#chatlist");
    initWebSocket();

    $("#messageInput").on('keypress', function (e) {
        if (e.which === 13) {
            sendMessage();
        }
    });

})

The initWebSocket method, that is called on the document ready methods does the following:

function initWebSocket() {

    websocket = new WebSocket("ws://localhost:9000/ws");

    websocket.onopen = function(evt) {
        console.log("WebSocket Opened");
    };

    websocket.onclose = function(evt) {
        console.log("WebSocket Closed");
        websocket = null;
        username = null;
    };

    websocket.onmessage = function(evt) {

        var rawdata = evt.data;

        if (rawdata.startsWith("wsready###")) {

            var qrUuid = rawdata.split("###")[1];
            console.log("UUID for qr received: " + qrUuid);
            $("#qrcode").attr("src", "http://localhost:9000/uuid/" + qrUuid);

        } else if (rawdata.startsWith("authed###")) {

            username = rawdata.split("###")[1];
            console.log("Auth done using mobile for user: " + username);
            qrcodeareaDiv.hide();
            chatareaDiv.css('display','block');

        } else if (rawdata.startsWith("msg###")) {

            var user = rawdata.split("###")[1];
            var msg = rawdata.split("###")[2];
            console.log("message from : " + user + " with data: " + msg + " \n");

            if ( username === user ) {
                // FROM ME
                chatList.append("<div class=\"list-group-item list-group-item-success\">I said:"+msg+"</div>");
            } else {
                // FROM OTHER PEOPLE
                chatList.append("<div class=\"list-group-item list-group-item-warning\" style=\"text-align: right\">"+user+" said:"+msg+"</div>");
            }

        } else {
            console.error("Unknown Stuff: " + rawdata) ;
        }

    };


    websocket.onerror = function(evt) { 
        console.error("Error: " + evt) ;
    };

}

So, we open a WebSocket to the url ws://localhost:9000/ws , which is where we are making the socket available:

websocket = new WebSocket("ws://localhost:9000/ws");

Then we say that when the WebSocket is opened, we log some stuff to the javascript console, just for debugging purposes:

websocket.onopen = function(evt) {
    console.log("WebSocket Opened");
};

When the WebSocket is closed, we also print some debugging to the javascript console and we set the WebSocket to null and also a variable called username:

websocket.onclose = function(evt) {
    console.log("WebSocket Closed");
    websocket = null;
    username = null;
};

If there is an error with the WebSocket we also print some debugging to the javascript console:

websocket.onerror = function(evt) { 
    console.error("Error: " + evt) ;
};

Now for the interesting bit, when a message is received on the WebSocket we do a few things:

First we get the message that was sent from the server into a variable:

var rawdata = evt.data;    

Then, we try to understand what type of message was sent, and we do that by checking how to message starts. Remember that in the previous section, when a WebSocket was open, the server would send straight away a message with the UUID of the QR code to be displayed. That message starts with wsready followed by ### and the the UUID. So :

if (rawdata.startsWith("wsready###")) {

    var qrUuid = rawdata.split("###")[1];
    console.log("UUID for qr received: " + qrUuid);
    $("#qrcode").attr("src", "http://localhost:9000/uuid/" + qrUuid);

} 

What this code does is : when the WebSocket on the browser side receives a message that starts with wsready### , it first splits the message at the ### marker and stores the UUID that comes after the ###:

var qrUuid = rawdata.split("###")[1];

We then log that UUID to the javascript console, just so we can see what is going on, and then, we update an <img> element with id qrcode that exists on the index.scala.html with an url for the QR Code image:

$("#qrcode").attr("src", "http://localhost:9000/uuid/" + qrUuid);

Remember, the url http://localhost:9000/uuid/ was explained in the previous sections, where we show how we generate a QR Code image and how we make it return on a specific route.

The browser and server, if a message starting with wsready### arrives, have a WebSocket open between each other, that specific WebSocket can be identified by an UUID, and the browser is now displaying a QR Code of that UUID.



But that is not all that happens when a message is received on the WebSocket. We are also expecting two other types of messages, which we haven't talked about yeat, but I'll introduce now:

  • authed### : A message starting with authed### is sent to the WebSocket by the server, once a Mobile user authenticates using the QR Code. This message has the usernameof the user that just authenticated with the Mobile Device, and is used to signal the browser to get ready with the Chat window. so :

    1- Store Username:

    username = rawdata.split("###")[1];
    

    2- Hide QR Code bit of the interface, which is not needed anymore, because a user already authenticated with that QR Code:

    qrcodeareaDiv.hide();
    

    3- Display the Chat section of the interface :

    chatareaDiv.css('display','block');
    
  • msg###: A message starting with msg### is sent to the WebSocket by the server when some user sends a chat message to the server, be it from a Web Client or a Mobile Client. This message is a two part message with this format : msg###USERNAME###MESSAGE . The first thing we do is split the rawmessage on the ### marks, and extract the username of the person sending the message, and the message itself:

    var user = rawdata.split("###")[1];
    var msg = rawdata.split("###")[2];
    

We then update the Chat interface in one of two ways, depending if the user that sent the message was the same user that is authenticated on the Web Client, or if it was another user:

if ( username === user ) {
    // FROM ME
    chatList.append("<div class=\"list-group-item list-group-item-success\">I said:"+msg+"</div>");
} else {
    // FROM OTHER PEOPLE
    chatList.append("<div class=\"list-group-item list-group-item-warning\" style=\"text-align: right\">"+user+" said:"+msg+"</div>");
}

That is it for the WebSocket.

There is also another method, that we have introduced before, when talking about the ready method of the document, with regards to the return key of the messageInputtext field. That is the sendMessage function, which looks like this:

function sendMessage() {
    var message = $("#messageInput").val();

    $.ajax({
        'url': '/session/msg',
        'type': 'GET',
        'data': {
            'user': username,
            'message': message
        },
        'success': function (data) {
            //clear input field value
            $("#messageInput").val("");
        },
        'error' : function (data) {
            alert(data);
        }
    });
}

So, when the Web Client user wants to send a message, he writes it down on the messageInput field, and when he is done, he presses the return key. This method is called, which uses JQuery to send an ajax request to the server, where it send the username of the user sending the message, and the message the user wrote.

Notice that we do no update the Chat sections of the interface. We leave that to the WebSocket. We just send the message to the server, if the server receives it and everything is fine, the server will send the message to every Web Client, through the WebSockets that are open, and the interfaces get refreshed when that message arrives.

This is it for the Web Client side of things.



Send and Receiving Messages - Server Side

The way we went about this is, everytime a client wants to send a message to the Chat, the client must make a GET request to the server with the its Username and Message. The Client shouldn't make any updates to the Chat interface on sending the message. The server receives the message and then goes to all Web Session that are authenticated and to all Mobile Sessions and sends that same message to the clients, using the WebSockets, or the Firebase cloud Messaging, depending on the type of Client. Only then, when the Client receives the message from the server, will it update the Chat interface.

On the Server side, we have the following method on the HomeController :

public Result broadcastMessage(String user, String message) {

    // Send for all WebSessions
    for ( WebSession session : WebSessionsHandler.getInstance().getAllAuthSessions()) {
        try {
            session.getOut().write(
                    "msg###"+user+"###"+message
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Send for all Mobile Sessions
    for (MobileSession session : MobileSessionsHandler.getInstance().getAllSessions()) {
        FirebaseUtil.sendPushMessage(session.getFirebaseToken(), user + " : " +message);
    }

    return ok();

}

What it does is, first iterate all the WebSessions it has, that are authenticated, and send a message through their WebSocket in the format previously describe while writing the Javascript bit of the WebSocket.

Then it iterates all the MobileSessions and using Firebase Cloud Messaging, send a message to each of the Mobile Devices. For doing this we are using the Pushraven lib , in the following way (FirebaseUtil class) :

 public synchronized static void sendPushMessage(String client_key, String message) {
    raven
            .text(message)
            .to(client_key);
    raven.push();

    raven.clear();
    raven.clearAttributes();
    raven.clearTargets();
}

Please check the official web page for Pushraven for more information on what is going on here.

And this is basically it. Now on to the Android Stuff.



Android Client Implementation

For the Android implementation we have a really simple ìnterface for login in, make the QR Code Scan and do some chatting. It uses Volley for communications with the Chat Server, which we just explained in the previous section, and also uses Firebase Cloud Messaging for receiving new messages from the Server.

It also works a bit different than the WhatsApp app, in that we don't use the phonenumber as the ID for the user, but instead we prompt the user for a Username. We do no validation, nor authentication, because that is not the point of this tutorial.

Lets start!

Interface Bit

For our Android client we just use an Activity with just one interface. That interface is divided in two main sections, one for the Login in, and another for the Chat. The section for the chat also has the QR Code bit. Our activity_main.xml layout looks like this:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
    android:orientation="vertical"
    android:id="@+id/mainLayout">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:id="@+id/signinLayout">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/usernameET"
            android:hint="@string/username_et_hint" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/signin_button"
            android:id="@+id/signinButton"
            android:onClick="signIn" />

    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/qrScannerLayout"
        android:visibility="gone"></LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/chatLayout"
        android:visibility="gone">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <Button android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:text="@string/qr_code_button"
            android:onClick="performQrScanner"
                android:id="@+id/scanQrButton"
                android:layout_alignParentTop="true"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true" />

            <ListView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/chatLListView"
                android:layout_gravity="center_horizontal"
                android:layout_below="@+id/scanQrButton"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:layout_above="@+id/linearLayout" />

            <RelativeLayout
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:id="@+id/linearLayout">

                <EditText
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/messageET"
                    android:layout_toLeftOf="@+id/sendButton"
                    android:layout_alignParentLeft="true"
                    android:layout_alignParentStart="true" />

                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/send_button"
                    android:id="@+id/sendButton"
                    android:onClick="sendMessage"
                    android:layout_alignParentTop="true"
                    android:layout_alignParentRight="true"
                    android:layout_alignParentEnd="true" />
            </RelativeLayout>
        </RelativeLayout>

    </LinearLayout>

</LinearLayout>

We have the signinLayout LinearLayout, which is the section where the user specifies the Username he wants to use:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:id="@+id/signinLayout">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/usernameET"
        android:hint="@string/username_et_hint" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/signin_button"
        android:id="@+id/signinButton"
        android:onClick="signIn" />

</LinearLayout>

We have and empty LinearLayout called qrScannerLayout which is where we'll display the Camera bit , when we perform QR Code Scanning. It is empty by default but will be filled when the user selects the QR Code Scanning button:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/qrScannerLayout"
    android:visibility="gone"></LinearLayout>

And then, we have the Chat section bit, which is again another LinearLayout called chatLayout that contains:

  • Button for QR Code Scanning
  • ListView for the Chat Messages
  • EditText for writting Messages to send

One thing to take notice is that the chatLayout has the visibility property as gone, which is the default, because we don't want to show this interface until the user defines its Username. This section looks like this:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/chatLayout"
    android:visibility="gone">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:text="@string/qr_code_button"
        android:onClick="performQrScanner"
            android:id="@+id/scanQrButton"
            android:layout_alignParentTop="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true" />

        <ListView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/chatLListView"
            android:layout_gravity="center_horizontal"
            android:layout_below="@+id/scanQrButton"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_above="@+id/linearLayout" />

        <RelativeLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:id="@+id/linearLayout">

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/messageET"
                android:layout_toLeftOf="@+id/sendButton"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/send_button"
                android:id="@+id/sendButton"
                android:onClick="sendMessage"
                android:layout_alignParentTop="true"
                android:layout_alignParentRight="true"
                android:layout_alignParentEnd="true" />
        </RelativeLayout>
    </RelativeLayout>
</LinearLayout>

On the Activity side it is very simple. For the login bit, the user writes down the Username on the EditTextfield and presses the Login Button, which does nothing except locking the Username EditText field and Login Button, and show the Chat session of the interface, like so:

public void signIn(View view) {
    if (usernameET.getText().toString().isEmpty()) {
        Toast.makeText(this, "Must have username defined", Toast.LENGTH_SHORT).show();
        return ;
    }

    // If a username is defined, then lock interface and update
    usernameET.setEnabled(false);
    signInButton.setEnabled(false);
    chatLayout.setVisibility(View.VISIBLE);
}

But lets now talk about Firebase Cloud Messaging.



Firebase Cloud Messaging Intro and Dependencies

For sending the messages of the Chat from the Server to the Android Clients, we use Firebase Cloud Messaging. Give a read at the Official Site for more information regarding Firebase, but for the purpose of this tutorial you just need to know that it allows sending push messages to Android devices (to and from, and other devices, and browsers and stuff), and that is what we will use it for. We'll use Firebase to send push messages from the Chat server to the Android devices.

So, to start using Firebase, you need to go to the Firebase console at https://console.firebase.google.com and register the app you are creating. For this tutorial, the package to use during registration is : com.jbaysolutions.tutorial.whatsappclone .

Once the app is registered on the Firebase Console, you must download the google-services.json file that is provided and replace it on the App. This is very important, or else it just won't work.

If you are making an app from scratch, then you need to :

  • Add dependency of com.google.gms:google-services on the build.gradle file on the root of the project ( the one that has the gradle build tools dependency) :

    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
    
    
       // NOTE: Do not place your application dependencies here; they belong
       // in the individual module build.gradle files
    
    
       classpath 'com.google.gms:google-services:3.0.0'
    
    
    }
    
  • Add dependencies of firebase to the build.gradle file that exists at the base of the module:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
         compile 'com.android.support:appcompat-v7:24.0.0'
         compile 'me.dm7.barcodescanner:zxing:1.8.4'
         compile 'com.google.firebase:firebase-core:9.4.0'
         compile 'com.google.firebase:firebase-messaging:9.4.0'
         compile 'com.android.volley:volley:1.0.0'
    }
    
  • Add the apply plugin to the end of the of the build.gradle file at the base of the module:

    apply plugin: 'com.google.gms.google-services'
    
  • Save the google-services.json you just downloaded from the Firebase Console for this app right next to the build.gradle file at the base of the module.

This is it. You are now ready to use the Firebase Cloud Messaging.



Firebase Android implementation

Most of the code used on the WhatsApp Clone Android App is base on this Tutorial so give it a quick read if you want.

So, when we start the WhatsApp Clone App and the first thing we need to do is send a registration of the Device to the server, with the Firebase token of the device, so that the Server can now contact the Device back at any time. We also need to notify the Server everytime the Firebase token changes.

Remember, the Firebase Token is the one thing that allows the Server to reach the Client Device at any time using push messaging.

Updating Firebase Token

For that we use a service which we define on the Manifest like this :

<service
    android:name=".firebase.MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

The code for that Service looks like this :

public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {

    private static final String TAG = "MyFirebaseIIDService";

    @Override
    public void onTokenRefresh() {

        //Getting registration token
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();

        //Displaying token on logcat
        Log.d(TAG, "Refreshed token: " + refreshedToken);

        try {
            ServerFacade.sendRegistrationToServer(this, refreshedToken);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            Log.e(TAG, e.getMessage(), e);
        }
    }
}

What this does is, everytime the Firebase token for the device changes, the onTokenRefresh method is called, in which we get the token, and then we use the sendRegistrationToServer method of the ServerFacade class to send it to the Server.

The other thing we need to do is do something when a message arives through Firebase.

Handling arriving messages

To handle arriving messages from Firebase we use another service which we have defined on the Manifest like so:

<service
    android:name=".firebase.MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

The code for that Service looks like this:

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMsgService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        //Displaying data in log
        //It is optional
        Log.d(TAG, "From: " + remoteMessage.getFrom());
        Log.d(TAG, "Notification Message Body: " + remoteMessage.getNotification().getBody());

        //Calling method to generate notification
        //sendNotification(remoteMessage.getNotification().getBody());

        Intent i = new Intent();
        i.setAction("RECEIVEMESSAGE");
        i.putExtra("rawdata", remoteMessage.getNotification().getBody());
        sendBroadcast(i);
    }
}

What this does is, everytime a message is sent to this device, the onMessageReceived method is called. This method retrieves the name of the user who sent the message and the message itself and sends it to the MainActivity through broadcasting. On the Activity we have a BroadcastReceiver implemented and registered to receive these notifications. Lets see that now.



BroadcastReceiver to Receive Messages

On the MainActivity we have a inner class that extends BroadcastReceiver to receive the messages from the MyFirebaseMessagingService . The BroadcastReceiver is implemented like this :

class MessageBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String rawData = intent.getStringExtra("rawdata");
        String user = rawData.split(":")[0].trim();
        String message = rawData.split(":")[1].trim();
        adapter.addMessage(
                user, message,
                (user.trim().equals(usernameET.getText().toString()))
        );
        Log.d("RAWDATA", "Received RawData : " + rawData);
    }
}

What it does is receive messages being sent from the MyFirebaseMessagingService service, extracts the User and Message from the raw message , and updates an Adapter that is set for the ListView of the interface. We'll see more about that in a minute.

For this BroadcastReceiver to actually receive stuff, it needs to be registered on the Activity, which is done like this:

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mReceiver, new IntentFilter("RECEIVEMESSAGE"));
}

We also unregister is when we have the Activity go on Pause:

@Override
public void onPause() {
    super.onPause();
    if (mScannerView != null) {
        mScannerView.stopCamera(); 
    }
    unregisterReceiver(mReceiver);
}

Now that we have the BroadcastReceiver out of the way, lets look at the ListView and the Adapter.

Chat Message List and Adapter

On the interface layout, we have a ListView called chatLListView which we assign to a variable on the onCreate method, and set an Adapter to it:

chatList = (ListView) findViewById(R.id.chatLListView);
chatList.setAdapter(adapter);

That adapter is an instance of ChatListAdapter which is a BaseAdapter that draws one of two layouts depending on whether the message is from the same user as the user registered on the Android App, or not. The relevant method to take notice is the getView method, that looks like this:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    View v;
    LayoutInflater vi = LayoutInflater.from(context);

    ChatMessage p = getItem(position);

    if (p.isMe) {
        v = vi.inflate(R.layout.chatlayout_me, null);
    } else {
        v = vi.inflate(R.layout.chatlayout_others, null);
        ((TextView)v.findViewById(R.id.whoTV)).setText(p.user);
    }

    ((TextView)v.findViewById(R.id.messageTV)).setText(p.message);

    return v;
}

The two layouts that exist to render a message are chatlayout_me and chatlayout_others, and they look like this :

chatlayout_me :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#d9fcd1">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="me:"
        android:id="@+id/whoTV"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:textColor="@android:color/black" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Medium Text"
        android:id="@+id/messageTV"
        android:layout_below="@+id/whoTV"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

chatlayout_others :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#fcecbf">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="me:"
        android:id="@+id/whoTV"
        android:textColor="@android:color/black"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Medium Text"
        android:id="@+id/messageTV"
        android:layout_below="@+id/whoTV"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:gravity="right" />
</RelativeLayout>

They are basically the same, with just a different background color and gravity.

Now for the QR Code Scanning!



QR Code Scanning

The QR Code Scanning was based on this Tutorial , so, again, give it a read if you want to. We are using the ZXing library from google to do the scanning.

The overall idea is : When you press the QR Code Scan button, that is on the activity_main.xml layout file , we initialize a scanning view from the ZXing library and we add it to the qrScannerLayout, which is a LinearLayout we have defined also on the activity_main.xml layout file. This was explained previously. We set the result listener of the scanner to the activity itself (because the MainActivity implements ZXingScannerView.ResultHandler ) and we start the Camera. Like this:

public void performQrScanner(View view) {

    qrScanneLayout.setVisibility(View.VISIBLE);
    mScannerView = new ZXingScannerView(this);   // Programmatically initialize the scanner view

    qrScanneLayout.addView(
            mScannerView
    );

    mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
    mScannerView.startCamera();         // Start camera
}

Because our MainActivity implements ZXingScannerView.ResultHandler we have to implement the handleResult method, which we do, just like this:

 public void handleResult(Result rawResult) {
    // Do something with the result here
    Log.e("handler", rawResult.getText()); // Prints scan results
    Log.e("handler", rawResult.getBarcodeFormat().toString()); // Prints the scan format (qrcode)
    // show the scanner result into dialog box.

    mScannerView.stopCamera();

    try {
        ServerFacade.sendRegisterUserToQRCode(
                this,
                usernameET.getText().toString(),
                rawResult.getText()
        );

        qrScanneLayout.removeView(
                mScannerView
        );
        qrScanneLayout.setVisibility(View.GONE);

    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        Log.e("Main Activity", e.getMessage(), e);
        qrScanneLayout.removeView(
                mScannerView
        );
        qrScanneLayout.setVisibility(View.GONE);
    }

}

On this method we extract the QR Code, which is the UUID provided by the server to the browser through the WebSocket, and we use the ServerFacade class to register the User that is using this device with that UUID. This will trigger the Server to send a command to the Browser that belongs to that UUID notifying that it now belongs to the specific User, and the browser gets it interface updated with the Chat bit.

Now all that we are missing is the ServerFacade class that is used for communication with the Server.



Communication with Server

All communications with the Server are made through the use of a class called ServerFacade. It uses Volley to do all HTTP communications. Check this Tutorial for more information on Volley and how to use it.

There are three methods here:

  • sendRegistrationToServer : Used to send the deviceId of the android along with the Firebase Token. These are stored on the server for a way for the Server to know how to send message notifications back to the Device.

  • sendRegisterUserToQRCode : Used to associate a User with the UUID of a QR Code. This is the method that lets the Server know that a WebSocket belongs to a user.

  • sendMessage : Used to send messages all the registered users .

    Give a look at them. There is nothing much to it really.

    We have a RequestQueue wrapped in a Singleton, like described on this section of the Tutorial here , which we call RequestQueueSingleton, so all http requests are placed on that queue.

    On the ServerFacade, we build the requests using another Singleton called Configuration , which is where we have defined the Chat Server IP and Chat Server Port.

    We place the Requests on the queue and that is it.

Configurations that you need to Change

Before you can you this application, you need to go to the Configuration class and modify the IP and Port of the server you are running, so that the App knows where to reach it. And that is it. Compile and deploy it to your mobile phone!

REFERENCES

https://mvnrepository.com/artifact/com.google.zxing/core/3.2.1

http://www.websocket.org/echo.html

https://www.numetriclabz.com/android-qr-code-scanner-using-zxingscanner-library-tutorial/

https://www.simplifiedcoding.net/firebase-cloud-messaging-tutorial-android/

https://www.playframework.com/documentation/2.5.x/JavaWebSockets#handling-websockets

https://blog.openshift.com/how-to-build-java-websocket-applications-using-the-jsr-356-api/

http://crunchify.com/java-simple-qr-code-generator-example/

https://developer.android.com/training/volley/index.html





comments powered by Disqus