Skip to Content

Part 1: Motivation, Idea and first Prototype

Motivation

SAPUI5 business applications are mostly connected to an on premise or cloud based ERP system. Needed data is provided via oData services. The client side pulls the data from the server. Changes on the server, while working on the data in the frontend, are not visible in realtime and will only be available in the frontend when a refresh is triggered. In order to get a more realtime like update of the frontend data, websockets can be used. In an on premise ABAP systems an ABAP Push Channel could be used for this. The overhead for realizing such a solution is very high. Also the downside is, that we have two different channels with different technologiesa and with probably different data structures: The oData Service and the websocket channel. So what would be an easier, more convenient way, if a data connection with real time updates of the frontend is needed? Google Firebase is a possible solution. But what is it?

Google Firebase

As Google says: “Firebase gives you the tools to develop high-quality apps, grow your user base, and earn more money”.
Firebase can be used as a complete backend service for web applications. It provides services for user authentication, a realtime database, cloud storage, cloud functions, runtime analytics and more. What we will be using for our SAPUI5 demo app is the user authentication, the realtime database and (later) the cloud functions.

Prototyping – Data Structures

To demonstrate the realtime capabilities the chosen scenario is a chat app. A user will logon to the firebase backend, can choose one of the other users and send and receive messages in realtime.
The data for the chat will be stored in the firebase realtime database. This is a NoSQL database. Data is stored as a JSON tree with key / value associations.
We will only have 1 to 1 chats between the users. So for every combination of two users there could be one chat.
We will use the following structure for the users:
A user has a GUID as primary key and the two properties email and name. All the chats will have a unique key in the user tree and a guid that can be found at the corresponding user and in the chats tree. The email of the chat partner seems to be redundant but will it make more easy to find the correct chat after selecting the user in the user interface. It makes it also possible not to have to read all chats of the user from the database. If a new chat is created for two users it will be added at both “users” und the “chats” path.
The chat tree contains all chats. Every chat consists of messages. A message does have a guid and provides the sender’s email, the text and a timestamp for sorting.

Prototyping – Creating the Project in Firebase

For our prototpy we will first make the necessary settings in Google Firebase and create a new project.
If you already have a Google account this can be used to login to Google Firebase. If not this has to be done first.
After logging in on https://firebase.google.com/ go to the “console”.
In the console a new project can be added.
Enter the project name and the country and select “Create Project”.

Prototyping – User Authentication

In the console on the left menu select “Authentication”.
The authentication method we will use at first for the prototype is “Email / Password”. This has to be activated.
After the activation new users can be added manually. Later we will do this automatially and try to authenticate via an SAP ERP backend system. For the tests create at least two users.

Prototyping – Database Setup

At the beginning the firebase realtime database is completly empty. Not structures have to be defined before using it. An app can write data and at the same time deliver the needed structure. For storing the chats in the database the user data is also needed in the database. The idea is to use a firebase server function that could be triggered when a user is created, changed or deleted. This function could update the user structure in the database. This will be done later in this blog series. For the first prototype we will do this manually.
For my users the following database items have to be created:
For the creation of the GUIDs I use the following javascrtipt function, which will also later be used in our SAPUI5 coding:
function() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8);
    return v.toString(16);
    });
}

Prototyping – Create the SAPUI5 Application

For developing the SAPUI5 application I used the Full Stack WEB IDE and created a new “Project from Template”.
Select the “SAPUI5 Application” template.
Enter a project name and a namspace.
Change the view name to MainViews.
And we are ready to code!
The app will be startet in the Web IDE with the index.html from the template. So we use the index.html to load the necessary firebase library.
The index.html should look like this:
<!DOCTYPE HTML>
<html>

    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta charset="UTF-8">

        <title>FirebaseChat</title>

        <script id="sap-ui-bootstrap"
            src="../../resources/sap-ui-core.js"
            data-sap-ui-libs="sap.m"
            data-sap-ui-theme="sap_belize"
            data-sap-ui-compatVersion="edge"
            data-sap-ui-resourceroots='{"com.demo.FirebaseChat": "./"}'>
        </script>

        <script src="https://www.gstatic.com/firebasejs/4.11.0/firebase.js"></script>

        <link rel="stylesheet" type="text/css" href="css/style.css">

        <script>
            sap.ui.getCore().attachInit(function() {
                new sap.m.Shell({
                    app: new sap.ui.core.ComponentContainer({
                        height : "100%",
                        name : "com.demo.FirebaseChat"
                    })
                }).placeAt("content");
            });
        </script>
    </head>

    <body class="sapUiBody" id="content">
    </body>

</html>

The MainView.controller.js should look like this:

/* global firebase*/
sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/m/MessageToast"
], function(Controller, MessageToast) {
    "use strict";

    return Controller.extend("com.demo.FirebaseChat.controller.MainView", {

        onInit: function() {
            var config = {
                apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                authDomain: "ui5chat-xxxxx.firebaseapp.com",
                databaseURL: "https://ui5chat-xxxxx.firebaseio.com",
                projectId: "ui5chat-xxxxx",
                storageBucket: "ui5chat-xxxxx.appspot.com",
                messagingSenderId: "84181818181"
            };
            firebase.initializeApp(config);
        },

        onLogin: function() {
            var sUser = this.getView().byId("email").getValue();
            var sPassword = this.getView().byId("password").getValue();
            var that = this;
            firebase.auth().onAuthStateChanged(function(user) {
                if (user) {
                    MessageToast.show("Login was successfully!");
                    that._getUsers();
                }
            });
            firebase.auth().signInWithEmailAndPassword(sUser, sPassword).catch(function() {
                MessageToast.show("Login not possible!");
            });
        },

        onSelectUser: function(oEvent) {
            var oSource = oEvent.getSource();
            var sSelectedEmail = oSource.getSelectedKey();
            var sChat = "";
            var sEmail = firebase.auth().currentUser.email;
            var oUserModel = this.getView().getModel("userModel");
            var aUsers = oUserModel.getProperty("/users");
            var that = this;
            aUsers.forEach(function(oUser) {
                if (oUser.email === sEmail) {
                    var oChats = oUser.chats;
                    if (oChats) {
                        Object.keys(oChats).forEach(function(sKey) {
                            if (oChats[sKey].email === sSelectedEmail) {
                                sChat = oChats[sKey].chat;
                            }
                        });
                    }
                }
            });
            if (!sChat) {
                var sCurrentUserGuid = "";
                var sSelectedUserGuid = "";
                aUsers.forEach(function(oUser) {
                    if (oUser.email === sEmail) {
                        sCurrentUserGuid = oUser.guid;
                    }
                    if (oUser.email === sSelectedEmail) {
                        sSelectedUserGuid = oUser.guid;
                    }
                });
                var sChatGuid = this._createGUID();
                var sCurrentUserChatKey = this._createGUID();
                var sSelectedUserChatKey = this._createGUID();
                firebase.database().ref("/users/" + sCurrentUserGuid + "/chats/" + sCurrentUserChatKey).set({
                    chat: sChatGuid,
                    email: sSelectedEmail
                });
                firebase.database().ref("/users/" + sSelectedUserGuid + "/chats/" + sSelectedUserChatKey).set({
                    chat: sChatGuid,
                    email: sEmail
                });
                var sFirstMessageGuid = this._createGUID();
                sChat = sChatGuid;
                var newMessageTimestamp = Date.now();
                firebase.database().ref("/chats/" + sChatGuid + "/" + sFirstMessageGuid).set({
                    message: {
                        email: "admin",
                        text: "Welcome!",
                        timestamp: newMessageTimestamp
                    }
                });
            }
            var oRefToChatData = firebase.database().ref("/chats/" + sChat);
            oRefToChatData.on("value", function(oSnapshot) {
                var mChatData = oSnapshot.toJSON();
                var aChatData = $.map(mChatData, function(oElement, sGuid) {
                    oElement.guid = sGuid;
                    return oElement;
                });
                var oChatModel = new sap.ui.model.json.JSONModel({});
                oChatModel.setProperty("/guid", sChat);
                oChatModel.setProperty("/chats", aChatData);
                that.getView().setModel(oChatModel, "chatModel");
                that.getView().byId("chatTable").getBinding("items").sort(new sap.ui.model.Sorter("message/timestamp", false));
            });
        },

        onSendNewMessage: function() {
            if (this.getView().getModel("chatModel")) {
                var sChatGuid = this.getView().getModel("chatModel").getProperty("/guid");
                var sNewMessageText = this.getView().byId("newMessage").getValue();
                var sNewMessageGuid = this._createGUID();
                var newMessageTimestamp = Date.now();
                var newMessageEmail = firebase.auth().currentUser.email;
                firebase.database().ref("/chats/" + sChatGuid + "/" + sNewMessageGuid).set({
                    message: {
                        email: newMessageEmail,
                        text: sNewMessageText,
                        timestamp: newMessageTimestamp
                    }
                });
                this.getView().byId("newMessage").setValue("");
            }
        },

        _getUsers: function() {
            var that = this;
            var oRefToUserData = firebase.database().ref("/users");
            oRefToUserData.on("value", function(oSnapshot) {
                var mUserData = oSnapshot.toJSON();
                var aUserData = $.map(mUserData, function(oElement, sGuid) {
                    oElement.guid = sGuid;
                    return oElement;
                });
                var oUserModel = new sap.ui.model.json.JSONModel({});
                oUserModel.setProperty("/users", aUserData);
                oUserModel.setProperty("/currentUser", firebase.auth().currentUser.email);
                that.getView().setModel(oUserModel, "userModel");
            });
        },

        _createGUID: function() {
            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
                var r = Math.random() * 16 | 0,
                    v = c === "x" ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

    });
});
The configuration (line 11 in the coding) must be set up according to your firebase project.
In the firebase project overview all the necessary keys can be copied.
Select “Add Firebase to your web app”:
The MainView.view.xml shoul look like this. Change the “controllerName” in the first line according to your namespace and ui5 project name.
<mvc:View controllerName="com.demo.FirebaseChat.controller.MainView" xmlns:html="http://www.w3.org/1999/xhtml"
    xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:core="sap.ui.core">
    <App id="idAppControl">
        <pages>
            <Page title="{i18n>title}">
                <content>
                    <InputListItem label="Email">
                        <Input id="email" type="Email" width="250px"/>
                    </InputListItem>
                    <InputListItem label="Password">
                        <Input id="password" type="Password" width="250px"/>
                    </InputListItem>
                    <InputListItem type="Active" press="onLogin">
                        <FlexBox alignItems="Center" justifyContent="Center">
                            <items>
                                <Text text="Login"/>
                            </items>
                        </FlexBox>
                    </InputListItem>
                    <InputListItem>
                        <FlexBox alignItems="Center" justifyContent="Center">
                            <items>
                                <Text text="Logged in as {userModel>/currentUser}"/>
                            </items>
                        </FlexBox>
                    </InputListItem>
                    <InputListItem label="Select User">
                        <ComboBox items="{userModel>/users}" showSecondaryValues="true" width="250px" selectionChange="onSelectUser">
                            <core:ListItem key="{userModel>email}" text="{userModel>name}" additionalText="{userModel>email}"/>
                        </ComboBox>
                    </InputListItem>
                    <Table id="chatTable" items="{chatModel>/chats}">
                        <columns>
                            <Column>
                                <Text text="Timestamp"/>
                            </Column>
                            <Column>
                                <Text text="Email"/>
                            </Column>
                            <Column>
                                <Text text="Text"/>
                            </Column>
                            <Column hAlign="Right"></Column>
                        </columns>
                        <items>
                            <ColumnListItem>
                                <cells>
                                    <Text text="{chatModel>message/timestamp}"/>
                                    <Text text="{chatModel>message/email}"/>
                                    <Text text="{chatModel>message/text}"/>
                                </cells>
                            </ColumnListItem>
                        </items>
                    </Table>
                    <InputListItem label="New Message">
                        <Input id="newMessage" width="250px"/>
                    </InputListItem>
                    <InputListItem type="Active" press="onSendNewMessage">
                        <FlexBox alignItems="Center" justifyContent="Center">
                            <items>
                                <Text text="Send Message"/>
                            </items>
                        </FlexBox>
                    </InputListItem>
                </content>
            </Page>
        </pages>
    </App>
</mvc:View>
Now you can start the app (via index.html).
Enter the user name and the password of one of your test users.
If the login was successfully or not is shown via a message toast. Under the login button there is also a text showing the logged in user’s email.
To test with two users in parallel, a second device (or different browser) has to be used, because a second login in the same browser takes over the first session. This can be checked with the “Logged in as” text.
After the login a user to chat with can be selected from the combo box.
If there is no chat for the two users in the database, a new chat is created and a first welcome message is saved in the database.
To create a new message just enter the text in the “New message” input field and hit “Send Message”.
Try it with two users on two devices or browsers and you will see that it updates all frontends in realtime.

Prototyping – How does it work … some explanations

The firebase javascript api is quite simple and intuitive.
In the index.html the firebase library is loaded.
<script src="https://www.gstatic.com/firebasejs/4.11.0/firebase.js"></script>
The firebase runtime needs some configuration for the connection. Thus is done at the initilization of the view controller. Insert your own configuration keys here from your firebase project.
var config = { 
   apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  
   authDomain: "ui5chat-xxxxx.firebaseapp.com",
   databaseURL: "https://ui5chat-xxxxx.firebaseio.com",
   projectId: "ui5chat-xxxxx",
   storageBucket: "ui5chat-xxxxx.appspot.com",
   messagingSenderId: "84181818181"
};
firebase.initializeApp(config);
On the view at first a user and a password must be entered. The login button press triggers the onLogin function. In this function two main things happen:
  1. An event listener is registered that reacts on authorization changes. It shows a message toast if login was successfully.
  2. After this the firebase signin method is called with user and password.
firebase.auth().onAuthStateChanged(function(user) {
	if (user) {
		MessageToast.show("Login was successfully!");
		that._getUsers();
	}
});
firebase.auth().signInWithEmailAndPassword(sUser, sPassword).catch(function() {
	MessageToast.show("Login not possible!");
});
After being succesfully logged in, the email of the logged in user is also shown on the view under the login button. This can be useful for tests with different users.
After the login the list of available users to chat with is loaded. This is done in the function “_getUsers”. First a reference to the path in the database is set.
var oRefToUserData = firebase.database().ref("/users");

This reference can then be used for reading the data. To just read the data and keep this status of the data the method “once” has to be called. But to use the full power of the realtime database and to make the chat a realtime chat we use the method “on” instead. This establishes a listener that gets called on every update in the database.

oRefToUserData.on("value", function(oSnapshot) {
	var mUserData = oSnapshot.toJSON();
	var aUserData = $.map(mUserData, function(oElement, sGuid) {
		oElement.guid = sGuid;
		return oElement;
	});
	var oUserModel = new sap.ui.model.json.JSONModel({});
	oUserModel.setProperty("/users", aUserData);
	oUserModel.setProperty("/currentUser", firebase.auth().currentUser.email);
	that.getView().setModel(oUserModel, "userModel");
});

The data from the firebase database updates the userModel of our view, and this model is binded to the the combo box in our view.

<ComboBox items="{userModel>/users}" showSecondaryValues="true" 
    width="250px" selectionChange="onSelectUser">
            <core:ListItem key="{userModel>email}" text="{userModel>name}" 
                additionalText="{userModel>email}"/>
</ComboBox>
So what happens: If any user is added to the firebase database, the change will be shown in realtime in our app. No refresh is needed. For the users it is a nice but not necessarily needed feature. But as we will see later this will show its full power when reading the chat data.
When selecting a user from the combo box the “onSelectUser” function is triggered and first checksif there is already an existing chat between the logged in user and the selected user. If not a new chat is created. It is saved to the path of both users and also to the chats path. Furthermore a first “welcome” message is created.
Writing to the database is done with the set method:
firebase.database().ref("/users/" + sCurrentUserGuid + "/chats/" + sCurrentUserChatKey).set({
	chat: sChatGuid,
	email: sSelectedEmail
});
firebase.database().ref("/users/" + sSelectedUserGuid + "/chats/" + sSelectedUserChatKey).set({
	chat: sChatGuid,
	email: sEmail
});

firebase.database().ref("/chats/" + sChatGuid + "/" + sFirstMessageGuid).set({
	message: {
		email: "admin",
		text: "Welcome!",
		timestamp: newMessageTimestamp
	}
});
When a chat between the two users already existed or has now been created, all the messages in the chat are read with the “on” method. Here again the data is written to a view model “chatModel” with bindings to ui elements. Because of the “on” method listenting to changes on the database, all new messages are transfered in realtime to all listening clients. For a real application you would probably listen to all messages for the logged in user, but just to show the real time capabilities and to make it not to complex in this prototype you have to select the chat partner first.
Sending of new messages is done on the “onSendMessage” function. Again the firebase “set” method is used. Here we are sure that a chat exists because of our creation at the user selection. So we have just to write the new message to the correct path.
firebase.database().ref("/chats/" + sChatGuid + "/" + sNewMessageGuid).set({
	message: {
		email: newMessageEmail,
		text: sNewMessageText,
		timestamp: newMessageTimestamp
	}
});

See it in action

The realtime capability cannot be tested with two tabs in one brwoser, but with tow different browsers in parallel (for example Chrome and Firefox) it works, or you can use different devices: Desktop, tablet, phone.
In the following video you see the functionality in action:

Outlook

So much for now. It is just a rough prototyp with very basic funtionality to demonstrate and test the realtime chat.
What will (probably) happen in the next parts of this blog series?
  • Move the loading of the firebase js library to the component.js
  • The SAPUI5 user should be used for authentication at firebase and be created if not already existing
  • New users should be created in the database’s “users” tree with a firebase function
  • User interface should be made more “safe” – using state management to activate / deactivate functionality
  • Make the user interface look “nicer”, make it more usable
  • Make the app more “secure” – make sure users are not able to read chats of other users
  • Listen to all chats of the user and make notifications
  • Try to wrap the functionality into a reusable SAPUI5 component
To report this post you need to login first.

11 Comments

You must be Logged on to comment or reply to a post.

  1. Tiago Almeida

    Hi Jens! Good stuff.

     

    A while back I’ve created a FirebaseModel prototype. It’s a drop-in JSONModel replacement that stores data to Firebase (thus allowing the kind the same synchronization you’re doing here).

    It solves some of the problems you mention in the Outlook:

    • Automatically loading firebase library files
    • Setting up listeners on the data.

    Feel free to use it and/or contribute to it. I’m keen to get it production ready but haven’t had the time yet to go past the “prototype” phase. Although it works fine in the demos.

    Details here:

    https://github.com/jumpifzero/ui5-firebase-model

    Regards,

    Tiago

     

    (2) 
    1. Michelle Crapo

      Tiago,

      Very cool comment – I’ve bookmarked it for when I have time to really look at it. You should write your own blog on it!

      Thank you,

      Michelle

      (0) 
  2. Meghal Shah

    Hi Jens,

    Nice Blog.

    I have tried to implement the steps but When I try to make a logon its not allowing me. Test User1 is not able to make the login. Could you please help me to identify the issue. ?

     

    Regards,

    Meghal Shah

    (0) 
    1. Jens Borau Post author

      Hi Meghal,

      did you copy the config from your own firebasebproject?

      Have you created your user in firebase under authentication? Users must be created with email, and login will also be done with email and password.

      Regards,

      Jens

       

      (0) 
      1. Meghal Shah

        Hi Jens,

        Thanks for your prompt support. The issue first user able to make the logon but after that user dropdown is blank. 

        Thanks again  🙂

         

         

        Regards,

        Meghal Shah

        (0) 
  3. Helmut Tammen

    Great blog, Jens. Thanks for this.

    Some time ago I also wrote a UI5 application that uses authentication and database from Firebase. I like the flexibility regarding scalabilty and price that Firebase offers.

    Somewhere else I read that there is a GraphQL API for Firebase databases. I think I will have a look at this soon cause I think GraphQL will get a boost this year and reach more people than OData does. I then plan to write a GraphQLModel for UI5.

    Don’t know if I will finish my plans. Maybe it’s easier if I find fellows who are also interested in these topics 😉

    Regards Helmut

    (0) 
    1. Tarun Jain

      As mentioned in the blog, You will have to create the database and add Users manually. It will work once you complete this step.

      (0) 

Leave a Reply