B4J Tutorial [BANanoFireStoreDB] Creating a Chat application

Ola

Download

We are attempting to create a chat application using BANanoFireStoreDB and BANanoVueMaterial.

What you will need:

1. BANanoFireStoreDB
2. BANanoVueMaterial

What will you learn?

1. Using BANanoPromise
2. Using Firebase FireStore (CRUD & Filtering records)
3. Using Firebase FireStore (Saving files and getting downloadURLs)
4. Firebase Realtime
5. Firebase Authentication - sign up / sign in.

First you need to set up firebase: Here is a very simplified process

  • Go to firebase console
  • click add project
  • specify project name > Continue
  • enable / disable google analytics > Continue / create project
  • Your project Is ready > Continue
  • Side Menu > Develop > Authentication
  • Set up sign-in method
  • Sign in method > Email/Password (click edit icon) > Enable > Save

  • Side Menu > Develop > Cloud Firestore
  • Create database
  • Start in test mode > Next
  • Select location > enable
  • Side Menu > Project Overview > Project Settings
  • Your apps > web
  • Enter App NickName > Register App
  • Add Firebase SDK = Connection settings (copy)
  • Continue To console

Part 1 - Reproduction

1. Create a collection on your firestore called messages. It will have these field names, (a) name, (b) text, (c) profilepicurl and (d) timestamp.
2. Get your firestore connection settings which will be used when connecting to your firestore.
3. A navbar is created with an avatar, a title and a sign in/sign out buttons. These two buttons show depending on the signed in state.
4. If the user is signed out, the message list is cleared and when signed in, all messages are displayed that exist in the database.
5. As we are using components and routers, we are using .SetDataGlobal & .GetDataGlobal, these ensure that our state is shared all over our applications.
6. We use .SetMethod to register our subs and .CallMethod / .RunMethod to call them to execute.
7. We use a filter to select the first 10 records from our collection ordered by the timestamp DESC.

 
Last edited:

Mashiane

Expert
Licensed User
Part 2 - Sign In

In part 1 of our chat app, users are allowed to log in using their gmail user names and passwords. No credentials are stored on the fire store for that. With this sign in, we want to enable users to first register using an email and a password and then be able to sign in using their own specified email addresses and passwords.

To create this sign in screen, we have followed the component approach to create our page.
Our login screen is sitting at the center of the screen and looks like this:

1599603538664.png


We initialize a VMComponent and specify its route path.

B4X:
signin.Initialize(vue, "signin", "/signin", Me)

If we want to navigate to it anywhere in the app, we execute this code.

B4X:
vue.NavigateTo("/signin")

We store a map in our state where the email and password will be in memory:

B4X:
signin.SetData("login", vue.NewMap)

To create the sign in screen.. we

1. Create a container and add some attributes and classes to it so that it gets centered in the middle of the screen.
2. We add a row to the container, i.e. r1 and then a column r1c1 to it.
3. We add a card to the r1c1 that has a card text and card actions.
4. On the card text we add the logo image and the heading, centered.
5. We then add a form, that contains the email and the password.
6. We add a divider, followed by card actions and inside the card actions add a button.
7. We then add the inject the card to the container and feed the container as a template to the component.

NB: We are using the base class here so that one is able to understand what happens underneath. This also allows us to be flexible with our designs so that they meet our needs.

B4X:
'create a container
    Dim vsignin As VMElement = vm.VContainer("vsignin")
    'let it sit in the center of the page
    vsignin.AddAttr("fluid", True).AddClass("fill-height")
    'create a row
    Dim r1 As VMElement = vm.VRow("").AddAttributes(CreateMap("align":"center","justify":"center"))
    'create a column
    Dim r1c1 As VMElement = vm.VCol("").AddSizes("8", "4", "4", "4")
    'create a card
    Dim scard As VMElement = vm.VCard("").AddClass("elevation-12")
    'create the card text
    Dim ctext As VMElement = vm.VCardText("")
    'add image
    Dim div As VMElement = vm.Div("").AddClass("layout column align-center")
    Dim img As VMElement = vm.Img("").AddAttributes(CreateMap("src":"./assets/vuejs.png", "alt":"VueJS Logo", "width":"180","height":"180"))
    Dim h1 As VMElement = vm.H1("").AddClass("flex my-4 primary--text").SetText("Sign In")
    div.AddStyle("margin-top", "-100px")
    div.AddElement(img)
    div.AddElement(h1)
    ctext.AddElement(div)
   
    'create a form
    Dim cform As VMElement = vm.Form("frmsignin")
    'create email input
    Dim txtEmail As VMElement = vm.VTextField("txtemail")
    txtEmail.AddAttributes(CreateMap("append-icon": "person", "name":"email", "label":"Email Address", "type":"text"))
    txtEmail.AddAttributes(CreateMap("v-model": "login.email"))
    'txtEmail.AddAttributes(CreateMap( ":error": "error", ":rules": "[emailrules]"))
    cform.AddElement(txtEmail)
    'create password input
    Dim txtPassword As VMElement = vm.VTextField("txtpassword")
    txtPassword.AddAttributes(CreateMap(":type": "hidepassword ? 'password' : 'text'"))
    txtPassword.AddAttributes(CreateMap(":append-icon": "hidepassword ? 'visibility_off' : 'visibility'"))
    txtPassword.AddAttributes(CreateMap("name":"password", "label":"Password", "v-model":"login.password"))
    txtPassword.AddAttributes(CreateMap("@click:append": "hidepassword = !hidepassword"))
    'txtPassword.AddAttributes(CreateMap(":error":"error", ":rules":"[passwordrules]"))
    cform.AddElement(txtPassword)
    'add form to the card text   
    ctext.AddElement(cform)
    'add card text to the card
    scard.AddElement(ctext)
   
    'card actions
    Dim cactions As VMElement = vm.VCardActions("")
    cactions.AddSpacer
    'add a button to the card actions
    Dim btnSignIn As VMElement = vm.VBtn("")
    btnSignIn.AddAttributes(CreateMap("color":"primary", "@click": "loginx", ":loading":"loading")).SetText("Login")
    cactions.AddElement(btnSignIn)
    'add a divider to the card
    scard.AddDivider
    'add card actions to the card
    scard.AddElement(cactions)
    'add the card tp r1c1
    r1c1.AddElement(scard)
    'add r1c1 to r1
    r1.AddElement(r1c1)
    'add r1 to the container
    vsignin.AddElement(r1)
   
    'add container to component template
    signin.AddElement(vsignin)

Ta!

PS: When a user logs, the details being confirmed are what the user used to register.

BVMFirebaseLogin.gif


B4X:
'define the callback
Sub loginx(e As BANanoEvent)
    'get the user details
    Dim userdata As Map = signin.getdata("login")
    Dim semail As String = userdata.get("email")
    Dim spassword As String = userdata.get("password")
    If semail = "" Or spassword = "" Then Return
    'show loading
    signin.SetData("loading", True)
    '
    Dim fb As BANanoFireStoreDB = pgIndex.fb
    'sign in using email and password
    Dim sres As Map
    Dim serr As Map
    Dim signinp As BANanoPromise = fb.signInWithEmailAndPassword(semail, spassword)
    signinp.Then(sres)
        signin.SetData("loading", False)
        vue.NavigateTo("/")
    signinp.Else(serr)
        Dim xerror As String = fb.getMessage(serr)
        signin.SetData("loading", False)
        pgIndex.ShowSnackBarError(xerror)
    signinp.end
End Sub
 
Last edited:

Mashiane

Expert
Licensed User
Part 3 - Register

The registration screen is an extension of the Login screen with an additional confirm password text field. This is the code that gets fired when Register is clicked:

B4X:
'define the callback
Sub loginy(e As BANanoEvent)
    'get the captured details 
    Dim userdata As Map = register.getdata("login")
    Dim semail As String = userdata.get("email")
    Dim spassword As String = userdata.get("password")
    
    If semail = "" Or spassword = "" Then Return 
    'show loading on the button
    register.SetData("loading", True)
    'get the firebase firestore reference
    Dim fb As BANanoFireStoreDB = pgIndex.fb
    'try and register the user using email and password
    Dim regResponse As Map
    Dim regError As Map
    Dim reguser As BANanoPromise = fb.CreateUserWithEmailAndPassword(semail, spassword)
    reguser.ThenWait(regResponse)
    'turn loading off
    register.SetData("loading", False)
    'sign out
    fb.signOut
    reguser.Else(regError)
    'there was an error registering users
    Dim msg As String = fb.getMessage(regError)
    'show error on snack bar
    pgIndex.ShowSnackBarError(msg)
    'turn loading off
    register.SetData("loading", False)
    reguser.End
End Sub

1599607767709.png


The aim here is that when a user registers, a record on the users collection in firebase is added.

Ta!

PS: In this example I register using my gmail account...

BVMFirebaseRegister.gif
 
Last edited:

Mashiane

Expert
Licensed User
Part 4: Sending Images & Storing these on the FireStore

This part involves making a file selector (images) for the end user. The selected image should be stored on the Firebase Storage drive.
To store each image we use the current user id, the message id and then the file name of the image.

Adding an image to a message

BVMFirebaseStoreDocuments1.gif



Storing the images

BVMFirebaseStoreDocuments.gif


NB: All projects updated on first post!
 
Last edited:

Mashiane

Expert
Licensed User
Please note:

If you already have a gmail account, click the "G" to sign it. This will use your current existing profile. None of your credentials are stored on the Firebase database. When you do, your gmail profile pic, name will be picked up

1599695774201.png


If you dont have gmail, you can click on register to specify your email address and password. You can then use those to login. No name & profile pic will be displayed in that instance. As we are still exploring this lib, we will add more stuff to it as we go along.

Ta!
 

Mashiane

Expert
Licensed User
Adding rules

Wow. I've been wanting to implement rules this way like forever, from eversince I started wrapping this.

1599699190051.png


When implementing rules with vuetify, you bind a variable to a list of call back functions.

For example:

B4X:
txtEmail.AddAttributes(CreateMap(":rules": "emailrules1"))

Here the email will get applicable rules to it from the emailrules1 state variable.

Now let's add the rules, this should be a list of callback results.

B4X:
'adding rules
    Dim v As Object
    Dim checkemailcallback As BANanoObject = BANAno.CallBack(Me, "checkemail1", Array(v))
    Dim emailrules As List = vue.newlist
    emailrules.Add(checkemailcallback.Result)
signin.SetData("emailrules1", emailrules)

One can have as many rules as they want.

This rule will execute the checkemail1 sub to validate entries made on the email.

For this example, the callback just checks if there is a value entered, if not returns an error msg or else returns true.

B4X:
Sub checkemail1(v As String) As Object
    If v = "" Then
        Return "The email should be specified!"
    Else
        Return True
    End If
End Sub

Ta!

NB: All code bases on first post updated!
 
Last edited:

Mashiane

Expert
Licensed User
So what happens when a user clicks the image / file selector?

Below is the flow of processes that take place after the file selector (input control) is clicked. There is a lot of use of promises here to make the app work seamlessly.

1599740287155.png


1. The end user wants to send a message and selects the message sending button for the image.
2. This activates a file selector and the user selects the image file to send.
3. This fires a @change event, sendImage is called passing the received file object.
4. The file input is bound to a key attribute, we refresh this key attribute so that the component is refreshed. We need this in case we want to send the same image. This redraws the component on the screen.
5. addImage is fired with the received fileObject. This creates a new message on fireStoreDB using the current timestamp and a loading image with the current active user details. This saving process returns a msgRef promise. Using that msgRef promise, we get the message id, and define a saving path for the document. The saving path is made up of the userID/messageID/fileName and we fire StoragePut which is supposed to upload the document to the firebase server.
6. The saving process of the image file, returns a promise, from that promise, we use it to getDownloadPath which is the URL that firebase produces so that we can download the document.
7. We use that downloadURL and update the message we last saved so that the message has a firebase URL for the document. The image is shown in our message.


Tutorial completed!

Enjoy.
 
Last edited:

Mashiane

Expert
Licensed User
What else did we do?

The SnackBar


We build a snackbar that shows across the app using the global state. The global state is using the $store global variable. We used the base class to do this.

B4X:
Sub BuildSnackBar
    vue.SetDataGlobal("snackshow", False)
    vue.SetDataGlobal("snackmsg", "")
    vue.SetDataGlobal("snackcolor", "")
    Dim snack As VMElement = vm.VSnackBar("").AddAttr("v-model", "$store.snackshow").SetText("{{ $store.snackmsg }}")
    snack.AddAttributes(CreateMap(":multi-line": True, "app":True,"centered":True,":color":"$store.snackcolor",":shaped":True))
    vm.VApp.AddElement(snack)
End Sub

Sub ShowSnackBarError(msg As String)
    vue.SetDataGlobal("snackshow", True)
    vue.SetDataGlobal("snackmsg",msg)
    vue.SetDataGlobal("snackcolor", "error")
End Sub

Sub ShowSnackBarSuccess(msg As String)
    vue.SetDataGlobal("snackshow", True)
    vue.SetDataGlobal("snackmsg",msg)
    vue.SetDataGlobal("snackcolor", "success")
End Sub

Multiple Rules

1. Whilst the confirm password should not be blank, it should also match the entered password.

For this we define 2 callbacks, 1 to check if the confirm password is entered and another to check if the passwords match.

B4X:
Dim confirmpasswordrulesCB As BANanoObject = BANano.callback(Me, "checkconfirmpassword", Array(v))
    Dim passwordsshouldmatchCB As BANanoObject = BANano.CallBack(Me, "passwordsshouldmatch", Null)
    Dim confirmpasswordrules As List = vue.newlist
    confirmpasswordrules.add(confirmpasswordrulesCB.Result)
    confirmpasswordrules.Add(passwordsshouldmatchCB.Result)
    register.SetData("confirmpasswordrules", confirmpasswordrules)

These are the callback functions

B4X:
Sub passwordsshouldmatch As Object
    Dim mlogin As Map = register.getdata("login")
    Dim password As String = mlogin.get("password")
    Dim confirmpassword As String = mlogin.get("confirmpassword")
    If password = confirmpassword Then
        Return True
    Else
        Return "The passwords do not match!"
    End If
End Sub


Sub checkconfirmpassword(v As String) As Object
    If v = "" Then
        Return "The confirm password should be specified!"
    Else
        Return True
    End If
End Sub

Ta!

NB: All source codes in first post updated!
 
Top