Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
ChrisPaine
Active Contributor

Been meaning to do this post for ages, and with a few free seconds (and because it seems I've almost forgotten how to relax) I thought I'd throw something together.

A few months ago I was given the challenge of facilitating an open discussion on Fiori, UI5 and the future of mobile in the SAP HR enterprise at the Australian "Mastering SAP HR, Payroll and SuccessFactors" conference.  It certainly was a challenge and I'm not sure one I'd put my hand up for again, but as Robbo says, you only learn by trying.

The Plan

To make it a bit more fun, I thought I'd build a mobile UI5 app to go with the session. To make it even more fun, I gave myself 16 hours the week before the session started to do the work. Oh and Cloud. Because Cloud.

The "plan" was to have a web site (an HTML "app") that connected the device to the cloud (SAP HANA Cloud Platform) via simple RESTful interface to send results of votes of all the attendees to a chart which would update in realtime as people voted. The chart would update using WebSockets so that any info sent to HCP by the apps would automatically get updated. Oh, and to make it fun, in order to retrieve the questions, the users would need to shake their mobile device. :smile:

16 hours, I was mad!

OpenUI5 vs SAPUI5.

One of the biggest differences between SAPUI5 and OpenUI5 (other than Open tends to be a release ahead) is the lack of charting libs in OpenUI5. Since this wasn't going to be a SAP product using any SAP tools (other than the HCP) I thought I'd better use OpenUI5. So that left me hunting (for approximately a minute) for an alternative way to draw graphs. After typing "open source javascript chart websockets" into Google the first result was a paper from a scientific journal, the next two had to do with D3.js. So I had a look at that.

D3.js

Having played a bit with D3 now, I'm so impressed with what it can do, but even more impressed with the amount of documentation and example out there. And for me this was the clincher. The more examples I could copy, the less work I needed to do, and those 16 hours were looking mighty short. Plus it seemed that people had got D3 and websockets to play nicely previously, so surely I could do the same.

HCP for persistence and somewhere to run a WebSocket endpoint

By now HCP is my choice of development platform, I'm really getting the hang of JPA (especially with Spring) and it's just easy. And - because cloud.

WebSockets

Hmm I was sure I'd seen a blog on SCN... oh yes - http://scn.sap.com/community/developer-center/cloud-platform/blog/2013/12/19/websocket-on-sap-hana-c... there it was. Seems simple enough... (oh how foolish I am). The big thing to note is the JSR 356 definition and support. This means in simple terms that a WebSocket endpoint can be defined on the SAP HANA Cloud Platform just using some very simple notation of a class. Notation is awesome, XML sucks, I've learnt this through my learnings with Spring. So I was very happy to continue with WebSockets.

Shaking detection

About a month before I'd seen a cool feature that john.astill had demo'd on some of the SAP internal tools, where shaking the device entered "feedback mode" for sending error reports and other feedback about the app. He had told me that it was just a simple bit of code. So I went looking. I ended up finding a lib called shake.js by a chap called Alex Gibson who lives in the UK. http://alxgbsn.co.uk/ Very nice of him to share, and especially to make it clear what license. I'm not sure my usage of it is 100% right as I seem to occasionally have to restart my phone to make it pick up new shake events (think there are a limited number of listeners available for orientation events in the browser and sometimes (especially when debugging) I don't clean up the ones I have used. Bad me.

Putting it all together

So I had all the bits - what now? Well it was time to start coding.

All the code is available on GitHub - https://github.com/wombling/mobilequiz

I'll go through some of the more interesting bit (in my view) and also the results:

Playing with WebSockets

I'll include the entire code for the WebSockets class as I found very few full examples of how to build such a service. I was disappointed that I had to use a static method to send the updates out, but WebSocket support only arrived with Spring 4 and I haven't had much experience with that yet. Not to mention it doesn't look nearly as simple as the JSR-356 standard I've used below. So integration with the Spring dependency injection autowiring/services is something that will have to wait. NB the question service does have an interface and uses @Autowired when referenced, so I didn't complete give up on the idea.  I know I should have implemented an interface and had tests for this too and perhaps handled the exceptions, but well, 16 hours dudes!


package com.wombling.mobilequiz.admin;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.wombling.mobilequiz.api.ApiValues;
import com.wombling.mobilequiz.pojo.QuestionList;
import com.wombling.mobilequiz.user.QuestionService;
@ServerEndpoint(ApiValues.QUESTIONS_WEBSOCKET)
public class QuestionsWebSocket {
        Logger logger = LoggerFactory.getLogger(QuestionsWebSocket.class);
        private static Set<Session> clients = Collections
                        .synchronizedSet(new HashSet<Session>());
        private static void sendCurrentValues(Session session, QuestionService qs)
                        throws IOException {
                GsonBuilder builder = new GsonBuilder();
                Gson gson = builder.create();
                QuestionList questionList = qs.getAllQuestions("");
                String questionsJSON = gson.toJson(questionList);
                session.getBasicRemote().sendText(questionsJSON);
        }
        public static void sendUpdate(QuestionService qs) {
                for (Session client : clients) {
                        try {
                                sendCurrentValues(client, qs);
                        } catch (IOException e) {
                                // ignore for now
                        }
                }
        }
        @OnClose
        public void onClose(Session session) {
                // Remove session from the connected sessions set
                clients.remove(session);
        }
        @OnOpen
        public void onOpen(Session session) {
                // Add session to the connected sessions set
                clients.add(session);
                try {
                        QuestionService qs = WebSocketSupportBean.getInstance().getQs();
                        if (qs != null) {
                                sendCurrentValues(session, qs);
                        }
                } catch (IOException e) {
                        // ignore for now
                }
        }
        @OnMessage
        public void processGreeting(String message, Session session) {
                logger.debug(message);
        }
}

Worth noting the use of Google Gson which is the most awesome lib for converting pojos to JSON and JSON to pojos. It makes my life so much easier and is highly recommended. Also, who'd want to send data in any other format. (Unless of course you're feeling particularly enterprisy in which case there's a cure for that and it's called Apache Olingo.)

With the broadcasting of websockets underway and tested ( I also used Dark WebSocket Terminal to test) there needed to be something to subscribe to and react to my messages.

This was actually relatively easy - the MV* build of UI5 lends itself nicely to just updating the model from anything (in this case a websockets update) and the framework taking care of the rest.

Here's a code snippet taken from the onInit method of my admin view.


                var thisView = this;
                function url(s) {
                        var l = window.location;
                        return ((l.protocol === "https:") ? "wss://" : "ws://") + l.hostname + (((l.port != 80) && (l.port != 443)) ? ":" + l.port : "")
                                        + "/mobilequiz/" + s;
                }
                var socket = new WebSocket(url("questionWebSocket"));
                socket.onopen = function() {
                        console.log('WebSocket connection is established');
                };
                socket.onmessage = function(messageEvent) {
                        thisView._questionData = JSON.parse(messageEvent.data);
                        thisView.fnLoadQuestionList(thisView._questionData);
                        thisView.fnRenderGraph();
                };

Being able to change the protocol and port depending on whether I was running locally over http and ws rather than https and wss  when running on the HCP was an important consideration. It is actually trivially simple code - in fact in many ways easier than making an AJAX call.

Shake that phone!

The other code snippet I'd like to discuss is the logic to capture a shake.


onInit : function() {
                var config = {
                        "showLoadingThingy" : true,
                        "shakeSupported" : false,
                        "shakeNotSupported" : false,
                        "showQuestion" : false
                };
                var configModel = new sap.ui.model.json.JSONModel(config);
                this.getView().setModel(configModel, "cfg");
                this.getView().bindElement("cfg>/");
                var _e = null;
                var _i = null;
                var _c = null;
                var updateOrientation = function(e) {
                        _e = e;
                        window.removeEventListener("deviceorientation", updateOrientation, false);
                };
                var thisView = this;
                window.addEventListener("deviceorientation", updateOrientation, false);
                _i = window.setInterval(function() {
                        if (_e !== null && _e.alpha !== null) {
                                // Clear interval
                                clearInterval(_i);
                                thisView.setConfig(true);
                        } else {
                                _c++;
                                if (_c === 10) {
                                        // Clear interval
                                        clearInterval(_i);
                                        // > Redirect
                                        thisView.setConfig(false);
                                }
                        }
                }, 200);
        },
        setConfig : function(shakeAllowed) {
                var thisView = this;
                if (shakeAllowed) {
                        window.addEventListener("shake", function shakeEventOccured() {
                                thisView.fnGetNextQuestion();
                        }, false);
                } else {
                        sap.m.MessageToast.show("Your browser does not support shake detection");
                }
                var configModel = this.getView().getModel("cfg");
                configModel.setProperty("/shakeSupported", shakeAllowed);
                configModel.setProperty("/shakeNotSupported", !shakeAllowed);
                configModel.setProperty("/showLoadingThingy", false);
                this.getView().byId("busyInd").destroy();
        },

Here I've had to deal with phones/browsers that don't actually support shake detection, but do implement the deviceorientation event subscription (honestly - what were they thinking?! Blinking iPad rubbish).  I poll 10 times for a value in the orientation and check to see if the data is changing. If it is, hey hey!, we have shake support. If not, either you ought to go into professional poker, you can hold that phone so steady, or you have it on the desk, in which case shaking probably not a good idea. I then update my config model with the results. This means I can then decide whether to show you a button to press for new questions, or make you shake it like a Polaroid picture. If shake is allowed, then I call the function to get the next question.

Putting buttons in a toolbar on the bottom of the screen is a Fiori induced anti-pattern and I don't like it. :mad:

<rant>

Seriously! Look at any modern UI, where are the buttons? At the TOP of the screen or right next to the thing you are working on! Check out the browser you are using right now, heck even MS office! Yes, there are items in the toolbar at the bottom, but user actions are placed where users can see them, not at the bottom of the screen in an unresponsive toolbar. New and better UI/UX is supposed to be about making things better for users. Consistent UI is fine, but consistently unintuitive UI does not make it intuitive because we get used to it. That just makes it obviously a SAP application.

</rant>

So in my UI for the app, I kept things simple, because simple is best!

and even in my admin interface:

Drop shadows make everything look sexy, so I used them to highlight the questions. I probably should have rounded off the corners too, then it would have been extra cool. A little bit of CSS on top of UI5 makes all the difference I am finding. Whilst the number on the right hand side of the question probably means nothing to you, when you can see it counting down, it becomes pretty obvious that it's a countdown.

Simple! And then click on the graph icon and get some of that websockets real time data vibe going:

The number of yes/no votes updates in real time and also updates the graph. Pretty cool, especially when you have a few people voting.

I did some fun stuff with cookies to ensure that people don't vote multiple times (unless they decide to clear their cookies between votes, and in that case, they deserve it for being clever clogs.) The system keeps track of which cookie ids voted for what... (very big brother, ha and you thought this was anonymous - haven't you learnt ANYTHING in the last few years about the internet?!)

Though to be fair, it's going to take me a bit of work to figure out what this means: (not as if I stored much in the way of identifying data.)

I was having so much fun building this app, that I even got my family into the act and my daughter decided that she would help me by making a how to use the app video.

After the good, The bad stuff

Not everything was so awesome. OpenUI5 is currently only hosted out of Waldorf and takes a blinking age to download onto an Australian mobile phone. SAP has limited desire/resources to push the use of a CDN to make this faster. Even SAPUI5 does not benefit from a CDN. The more community _ahem_ encouragement that they get, should help this be fixed. Please do throw your comments about this on the stackoverflow post about this. Or even here, the more volume this problem gets the faster a solution.

I tried hosting the OpenUI5 code on my own website which is based in Australia, it certainly improved things. But then was having problems because of the mis-match of protocols - with my website being HTTP but AJAX comms to the HCP being via HTTPS (using CORS). It shouldn't have caused an issue, but when we tried it with lots of people it did. I'm not sure why.

Still, even with an AU website hosting the UI5 it still took too long to download onto a phone. I'll have to check about customised versions of UI5 that only contain the bits I need, I hear that speeds things up. I'll have to try it out. I'd also like to try hosting/using the AU HCP site for an experiment and see how that works. (unfortunately the AU HCP data centre wasn't up and running at the point I wanted to use my app!)

Summary

In the end I probably did spend a little more than 16 hours, probably more like 32 hours in total building the application, but most of that was in trying out and learning new stuff - like D3.js and WebSockets, so the late nights were worth it :wink:

Please have a look at my GitHub repository, fork it if you like or just borrow bits of code as you like.

I'd love to hear your comments, I think this combo of mobile, websockets and cloud is just about to take off - real real-time dashboard based on what people are doing right now. Combine this with HANA and I guess we also could get real real-time analytics of that data. Exciting new world!

7 Comments
Labels in this area