Build a Location-based Chatroom with Firebase and Vue.js

Build a Location-based Chatroom with Firebase and Vue.js

  • 2019-05-24 02:28 AM
  • 759

Build a Location-based Chatroom with Firebase and Vue.js

Introduction

Recently, me and my friend built a location-based chatroom called — Near (https://near.idgo.me). People can talk with each other nearby in a web app. We would like to share what we did and how to make it. Please feel free to give us feedback by replying this article.

Prerequisites

  1. Basic Knowledge of Geohash
  2. Basic knowledge of Firebase
  3. Basic knowledge of Vue.js component

What We Want to Achieve?

In this tutorial, we would try to make it as simple as possible so that you can see the effect quickly. So, the following steps we will be covered today.

  1. Get the user’s position via Geolocation API and convert the latitude and longitude into geohash
  2. Send a message to a room
  3. Display messages

What is Geohash?

Before talking about how to do it, we would like to briefly introduce what the geohash is. Geohash is a geocoding system which will be used to define a chatroom in this tutorial.

For example, wecnvgm2re3u represents Tsim Sha Tsui in Hong Kong (Latitude and Longitude = 22.29818732, 114.16646970). Each geohash character represents an area of the world and more character means more specific to a place. You can use this tool to know how it works.

In this application, geohash is an ideal mechanism to define a room ID based on user’s location and use the geohash precision (the length of geohash) to define the area coverage.

Create a Vue Project

In this article, we use vue-cli to generate a Vue seed project.

Install vue-cli

Root permission may be needed

npm install -g vue-cli

Initialize a vue project via vue-cli

vue init webpack-simple my-project

In this tutorial, we use webpack-simple template as a demo. For more detail about vue-cli, please see here.

Install dependencies and run the project by the following commands

cd my-project
npm install
npm run dev

Now, you can access your project on localhost:8080

After we created a Vue project and installed the dependencies, we can start to work on the Vue component (my-project/src/App.vue). If you are not familiar with Vue single file component, you can see the official website.

Next, try to modify your script to become the following structure. Inside the component, add some data variables to store the chatroom information.

room — A Firebase reference object which represents the room

precision — A precision of the geohash (default is 6 in this tutorial)

**db **— A Firebase SDK to communicate with Firebase

export default {
  name: 'chat',
  data() {
    return {
      room: null,
      precision: 6, // default precision
      db: null // assign Firebase SDK later
    }
  },
  mounted() {

  },
  methods: {
  }
}

chatroom-vue-component.md

Install Firebase and Geohash Encoder

Apart from Vue.js, we need Firebase SDK to communicate with Firebase and use geohash converter package to encode the latitude and longitude to geohash (room ID). You can install the these two packages by the following command.

npm install --save firebase latlon-geohash

After installing these two packages, you can import them inside the script tag in App.vue. For the Geohash package, we will explain it later.

import * as Firebase from 'firebase'
import Geohash from 'latlon-geohash'
export default{
// ....
}

Initialize Firebase When the Component Is Ready

When Vue component is ready, the mounted hook will be called. So, we can initialize a Firebase client object and assign it into a variable called — db. Remember to put your Firebase credential e.g. apiKey, authDomain, databaseURL and projectId as parameters. We assume that you know how to set up a Firebase project and obtain the credential. If you have no idea how to set it up, go to Firebase Get Started Guide.

import * as Firebase from 'firebase'
import Geohash from 'latlon-geohash'
    
export default {
  // ....
  mounted () {
    // init the database client by given Firebase's API key
    this.db = Firebase.initializeApp({
        apiKey: '<your-api-key>',
        authDomain: '<your-auth-domain>',
        databaseURL: '<your-database-url>'
    })
  }
}

mounted.md

Dependencies are Ready. It is time to Build.

1. Get the user’s position and convert the latitude and longitude into geohash

In this step, we will create a method called — init() to access user’s location via Geolocation API and convert it into geohash so that we can define which room should they go.

When the geolocation is obtained, we use the Geohash encoder imported before to convert the location into geohash.

For example:

Location: 22.29818732, 114.16646970 => wecnvg (geohash)

Since we set the default precision is 6, encoder only returns 6 characters. When we get the geohash, we initialize a Firebase reference with the room ID (geohash) and assign the reference to the room variable which can be reused later.

import Firebase from 'firebase'
import Geohash from 'latlon-geohash'
    
export default {
  // ....
  methods:{
    init() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) => {
          var geohash = Geohash.encode(position.coords.latitude, position.coords.longitude, this.precision);

          // initilize the room based on geohash
          this.room = this.db.database().ref().child('rooms/' + geohash)
        }, (err) => {
          // error handling here
        })
      } else {
        console.error('Cannot access geolocation')
      }
    }
  }
}
          

chatroom-init.md

OK. The init method is completed, but we still need to call it when the component is ready. So, call init method inside the mounted hook. Like the code below. Then, the component will try to access user’s location when the component is ready.

import * as Firebase from 'firebase'
import Geohash from 'latlon-geohash'

export default {
    name: 'chat',
    data() {
        return {
            room: null,
            precision: 6,
            db: null
        }
    },
    mounted() {
        this.db = Firebase.initializeApp({
            apiKey: '<your-api-key>',
            authDomain: '<your-auth-domain>',
            databaseURL: '<your-database-url>',
            storageBucket: '<your-storage-bucket>',
            messagingSenderId: '<your-sender-id>'
        })

        // access the location and initilize a Firebase reference
        this.init()
    },
    methods: {
        init() {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition((position) => {
                    var geohash = Geohash.encode(position.coords.latitude, position.coords.longitude, this.precision);

                    // initilize the room based on geohash
                    this.room = this.db.database().ref().child('rooms/' + geohash)
                }, (err) => {
                    // error handling here
                })
            } else {
                console.error('Cannot access geolocation')
            }
        }
    }
}   

complete-js-1.md

2. Create an input element to send a message

Create an input element inside template tag in the component (App.vue). We use v-model messageInput to store the message and use trim to remove the leading space of the input. When user presses the enter in the input element. The method send(messageInput) will be called. Remember to create a messageInput key inside the data part.

<template>
    <div id="chat">
        <input type="text" v-model.trim="messageInput" @keyup.enter="send(messageInput)">
    </div>
</template>

send message.md

Now, we are going to implement the send(messageInput) method to handle the event from the input element. Inside this method, we simply create an temporary object with a key called — message to store the message body. After that, call the push() to obtain a key and set the value on that key afterwards.

Remember to clean the messageInput value when the message is sent. Otherwise, your input always be there.

export default {
  // ..
  data () {
    return {
      // ..
      messageInput:'' // this is for v-model
    }
  },
  methods : {
    send(messageInput) {
      // A data entry.
      let data = {
        message: messageInput
      };

      // Get a key for a new message.
      let key = this.room.push().key;
      this.room.child('messages/' + key).set(data)
      
      // clean the message
      this.messageInput = ''
    }
  }
}

sendMessage.md

When you press the enter, probably get an error in your browser console. The image below is from Chrome console.

Don’t worry. Since Firebase only allows authorized user to access by default, so you need to enable a permission for that.

Follow the steps below to enable the permission:

  1. Go to Firebase console
  2. Select your project
  3. Select Database section

You can see the JSON in Rules section and see the default setting.

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

We want to see the effect quickly. We enable anyone can access Firebase by the following setting. (This approach is for demo only. Don’t do it in production).

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

After published the rules, you can try to send a message again. There is no error anymore. If you can access Firebase console, you can see the data from your input (Like the image below).

Notes: Since geolocation is an async process (in step 1), the geolocation cannot be obtain immediately. So, it is good to add a flag to indicate the geolocation is ready. Otherwise, an error will be thrown when you send a message because the room ID (Geohash) is not ready.

3. Display messages

We have already known how to send a message to Firebase. Now, we are going to create a listener to listen to an event from Firebase and display the messages when they comes.

The code below shows how to create an event listener and activate the event listener after the room is selected. When a new message added into Firebase, an event child_added will be triggered and run the callback method with a snapshot data. If you don’t know how Firebase works, good to read the tutorial here. Inside the callback method, the snapshot of the data will be pushed into the messages variable.

export default {
  // ...
  data () {
    return {
      // ...
      messages: []
    }
  },
  methods : {
    init() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) => {
          var geohash = Geohash.encode(position.coords.latitude, position.coords.longitude, this.precision);

          // initilize the room based on geohash
          this.room = this.db.database().ref().child('rooms/' + geohash)
          
          // must call messageListener() to listen to the new message event
          this.messageListener()
        }, (err) => {
          // error handling here
        })
      } else {
        console.error('Cannot access geolocation')
      }
    },
    messageListener () {
      this.room.child('messages').on('child_added', (snapshot) => {
        // push the snapshot value into a data attribute
        this.messages.push(snapshot.val())
      })
    }
  }
}

messageListener.md

Next, we need to render the data in HTML level when the message variable change. Add the code below to inside the template tag.

<div v-for="msg in messages">{{msg.message}}</div>

Since the basic data structure of a message in Firebase in Step 2 is the following:

{
  "message":"my message"
}

So, in the HTML level, we can access msg.message to display the data. When you send a message, a new message will show.

Combine Together

<template>
    <div id="chat">
        <input type="text" v-model.trim="messageInput" @keyup.enter="sendMessage(messageInput)">
        <div v-for="msg in messages">{{msg.message}}</div>
    </div>
</template>

<script>
    import * as Firebase from 'firebase'
    import Geohash from 'latlon-geohash'

    export default {
        name: 'chat',
        data () {
            return {
                room: null,
                messageInput: '',
                messages: [],
                precision: 6,
                db: null
            }
        },
        mounted (){
            this.db = Firebase.initializeApp({
                apiKey: '<your-api-key>',
                authDomain: '<your-auth-domain>',
                databaseURL: '<your-database-url>',
                storageBucket: '<your-storage-bucket>',
                messagingSenderId: '<your-sender-id>'
            })
            
            // access the location and initilize a Firebase reference
            this.init()
        },
        methods: {
            init() {
              if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition((position) => {
                  var geohash = Geohash.encode(position.coords.latitude, position.coords.longitude, this.precision);

                  // initilize the room based on geohash
                  this.room = this.db.database().ref().child('rooms/' + geohash)

                  // must call messageListener() to listen to the new message event
                  this.messageListener()
                }, (err) => {
                  // error handling here
                })
              } else {
                console.error('Cannot access geolocation')
              }
            },
            messageListener () {
              this.room.child('messages').on('child_added', (snapshot) => {
                // push the snapshot value into a data attribute
                this.messages.push(snapshot.val())
              })
            },
            send(messageInput) {
              // A data entry.
              let data = {
                message: messageInput
              };

              // Get a key for a new message.
              let key = this.room.push().key;
              this.room.child('messages/' + key).set(data)

              // clean the message
              this.message = ''
            }
        }
    }
</script>

complete-1.md

Final View

Conclusion

Finally, there are some important points in this tutorial:

  1. Make sure you have permission to access Firebase
  2. Since getting the geolocation is an async process, make sure you have obtained the geolocation before sending a message to Firebase.
  3. Create appropriate variable(s) in each section
  4. Activate the event listener to listen to new messages

There is a completed application — https://near.idgo.me

Feel free to give us feedback. No matter it is good or bad. In addition, we will prepare part 2 to make the chatroom more fun.

30s ad

VueJS V1 Introduction to VueJS JavaScript Framework

Getting started with Vuejs for development

Vuejs 2 + Vuex + Firebase + Cloud Firestore

Building Applications with VueJs, Vuex, VueRouter, and Nuxt

Ethereum and Solidity: Build Dapp with VueJS