fbpx

Ultimate Guide For Building Chat App With React Native

Ultimate Guide For Building  Chat App With React Native

#1:Basic Messaging

Welcome to part one of our series on building a mobile chat app with React Native and PubNub. PubNub provides our realtime messaging chat APIs and backend, and React Native Gifted Chat provides a complete chat UI for our app.

In this case, we’ll use the PubNub React package, which is a wrapper for the PubNub JavaScript SDK (v4). It simplifies the PubNub integrations with React or React Native.

In the end, we’ll have a simple mobile group chat app that allows multiple users to message one another in real-time. From there, we’ll continue adding new chat features including message history, user online/offline status, and typing indicators.

Before we begin, make sure you have the following:

  • Node.js installed, along with NPM or yarn.
  • PubNub account; this is where you’ll get your unique publish/subscribe keys.
  • Two (or more) mobile devices to test the app.

In this step, we’ll create a React Native project using the following command:

react-native init pubnubchatwithreactnative

Open the project folder named pubnubchatwithreactnative with VSCode or your favorite code editor. Next, open an integrated terminal and run the Android and iOS simulator by using the following code snippets:

react-native run-ios# ORreact-native run-android

Note: It will take a considerable amount of time for the first build of the React project. After the build, you will see the following on your Android and iOS simulators.

Image for post

In this step, we are going to install the Gifted Chat package, providing us with a complete chat UI. You can install react-native-gifted-chat with using following commands:

npm install react-native-gifted-chat --save# ORyarn add react-native-gifted-chat

Grab the example code from the GitHub readme file and paste it into the App.js file.

Next, import the GiftedChat component/module from the react-native-gifted-chat package as shown in the code snippet below:

import { GiftedChat } from 'react-native-gifted-chat'

Replace the code in the default class with the following piece of code:

export default class App extends Component { 
  state = { messages: [], };  componentWillMount() {
    this.setState({
      messages: [ 
        { _id: 1, text: "Hello developer", createdAt: new Date(),    user: { 
       _id: 2,
       name: "React Native",
       avatar: "https://placeimg.com/140/140/any", }, }, ], 
      }); 
  }
  
  onSend(messages = []) { 
    this.setState(previousState => ({
      messages: GiftedChat.append(previousState.messages, messages),
    })
    ); 
  }render() {
    return (
      <GiftedChat messages={this.state.messages} onSend={messages => this.onSend(messages)} user={{ _id: 1, }} /> 
    ); 
}

As seen in the code snippet above, the onSend() function is used for collecting messages and concatenating with the previous message. You’ll see it in the chat UI. The imported component GiftedChat is used to cover the whole chat interface.

You need to save the code and refresh the simulators. You will see an example of message.

Image for post

Keep in mind that you will not be able to send messages because PubNub is not yet implemented.

Now that we’ve got our UI ready to go, it’s time to make our chat app interactive — allowing users to communicate in real-time. That’s where PubNub comes in.

In this step, we’ll install the PubNub SDK. You can install the package using the following commands.

Using NPM:

npm install pubnub-react --save

Using Yarn:

yarn add pubnub-react

Once the installation is done, you need to import the PubNubReact module from the pubnub-react package as shown in the code:

import PubNubReact from 'pubnub-react';

Next, you can copy the example code from README file of the PubNub React GitHub, and paste it into the App.js file of your project:

constructor(props) {
    super(props); this.pubnub = new PubNubReact({ publishKey: 'YOUR PUBLISH KEY', subscribeKey: 'YOUR SUBSCRIBE KEY' });
    this.pubnub.init(this); 
} componentWillMount() {
  this.pubnub.subscribe({ channels: ['channel1'], withPresence: true });
  this.pubnub.getMessage('channel1', (msg) => { console.log(msg); });
  this.pubnub.getStatus((st) => { console.log(st);
  this.pubnub.publish({ message: 'hello world from react', channel: 'channel1' }); }); 
}componentWillUnmount() { this.pubnub.unsubscribe({ channels: ['channel1'] }); }

Next, add your unique pub/sub keys you got when you signed up for PubNub. They’re available in your PubNub Admin Dashboard.

Image for post

Image for post

Save and re-run the simulator. To view the JavaScript console output, we need to use Debug JS remotely.

Image for post

You will now be able to see PubNub data in the browser console.

Image for post

With that, the PubNub implementation is complete.

Next, we need to configure the chat channel using PubNub as set it up along with Gifted Chat. Follow these simple steps:

  • Initialize PubNub (done)
  • Subscribe to channel name “channel1” with the SDK subscribe method.
  • Send a message to “channel1” with the publish method.
  • Get a message from a channel with getMessagemethod.

In this step, we bring the entire configuration together. We pass a message from Gifted Chat to the PubNub publish() method. You can use the example code from the code snippet below.

onSend(messages = []) {
  this.pubnub.publish({ message: messages, channel: "Channel1", });
}

When the callback function onSend() is called, we will send a message to Channel1.

To get a message from the channel in realtime, we use the following callback to append previous state messages. Here is the code:

this.pubnub.getMessage("ReactChat", m => {
  this.setState(previousState => ({ messages:  GiftedChat.append(previousState.messages, m["message"]), })); 
});

All done! Let’s try it out by reloading the simulators. Now you should see the following in your UI.

Image for post

You will see chat messages appear in the UI, but all the messages appear on the same side. We need to graphically separate messages sent from messages received, to make it appear that two people are chatting.

<GiftedChat messages={this.state.messages} onSend={messages => this.onSend(messages)} user={{ _id: 0, }} />

This is because we haven’t set a new id when we open a new session. We need to hard code the user ID.

Create a function in App.js for generating a random user ID, as shown in the code snippet below.

randomid = () => { return Math.floor(Math.random() * 100); };

Set the variable and add it to the constructor function in App.js.

this.id = this.randomid();

Lastly, add the state and function to the GiftedChat HTML, as shown in the code below.

<GiftedChat messages={this.state.messages} onSend={messages => this.onSend(messages)} user={{ _id: this.id, }} />

Now, reload the simulator and observe the result. You will see the chat messages appear on the other side.

Image for post

At last! We have completed the setup of a chat app using React Native along with Gifted Chat and PubNub.

In this tutorial, we learned how to create a simple chat app with React Native using Gifted Chat and PubNub. You’ve now got a basic mobile chat application allowing users to send and receive messages in real-time.

The next step is to start adding additional chat features — message history, typing indicators, user presence, etc. Keep an eye out for subsequent posts on that!

All the code for this tutorial is available in my GitHub repo.

#2: Basic Messaging

In this part, we’re going to add message history, which will allow you to store and fetch historic messages. When a user reboots their chat app, they’ll be able to see previously sent messages

In this part, we are going to cover two main areas of message history:

  • Fetching message history
  • Visually displaying which user sent a message

Fetching Message History

Currently, our chat app has a global chat interface. Chat messages appear in the global log for all connected users when someone sends a message. But when we close the app, all of the messages vanish. When we re-open the app, the message log is empty. This is because the message history is not stored on the device.

That’s what we’ll solve in this part — storing and fetching the message history. We’ll use PubNub Storage & Playback (sometimes referred to as History or the History API) to do it.

Navigate to your PubNub Admin Dashboard, and activate the feature.

Once the Storage & Playback is activated, we need to fetch the stored message when the app is refreshed or resumed. For that, we need to return to the App.js file and then fetch the stored messages when the app gets started, using the componentDidmount() React callback function.

The following code snippet fetches message history from a PubNub channel:

this.pubnub.history(
  { channel: "MainChat", reverse: true, count: 15 },
  (status, res) => {
    console.log(newmessage);
  }
);

This function works by simply fetching 15 messages from a channel named MainChat. The fetched messages can be seen in the JavaScript developer console.

You can see that it works like a charm. But when we observe the default Gifted Chat message structure, it doesn’t match our design. So we need to reconstruct the message before we append the new messages to the React state. You can use the code from the following snippet to construct a new message array which will match the message structure of the Gifted Chat:

componentDidMount() {
  this.pubnub.history(
    { channel: "MainChat", reverse: true, count: 15 },
    (status, res) => {
      let newmessage = [];
      res.messages.forEach(function(element, index) {
        newmessage[index] = element.entry[0];
      });
      console.log(newmessage);
    }
  );
}

You can see that the message structures are aligned in the following screenshot.

Now, we need to append the new message array to Gifted Chat. For that, you can use the following code snippet:

componentDidMount() {
  this.pubnub.history(
    { channel: "MainChat", reverse: true, count: 15 },
    (status, res) => {
      let newmessage = [];
      res.messages.forEach(function(element, index) {
        newmessage[index] = element.entry[0];
      });
      console.log(newmessage);
      this.setState(previousState => ({
        messages: GiftedChat.append(
          previousState.messages,
          newmessage
        )
      }));
    }
  );
}

Now the message history will load even when the app is refreshed.

However, the messages are ordered in the opposite direction. So we need to reverse the array before we add the array to Gifted Chat. You can do this by using the following code snippet:

componentDidMount() {
  this.pubnub.history(
    { channel: "MainChat", reverse: true, count: 15 },
    (status, res) => {
      let newmessage = [];
      res.messages.forEach(function(element, index) {
        newmessage[index] = element.entry[0];
      });
      console.log(newmessage);
      this.setState(previousState => ({
        messages: GiftedChat.append(
          previousState.messages,
          newmessage.reverse()
        )
      }));
    }
  );
}

And with that, chat messages are now retrieved and displayed in the correct, chronological order that they were sent.

Associating Users to Messages in the Chat UI

You can observe that when you send a message and refresh the app, the messages are all displayed on the recipient side. Why does this happen? We’ve randomly created a user id when we load the app, to simulate multiple users.

We need to add a little user authentication so we can identify users in the chat. This will enable us to show the messages on the correct side of the chat interface. Then, users will be able to visually differentiate their chat messages from their friend’s chat messages.

First, we need to create a couple of files in our app directory.

We need to move the code from our App.js file to MainChat.js and change the class name from App to MainChat as shown in the code snippet below:

import React, { Component } from "react";
import {
  View,
  Text,
  StyleSheet,
  TextInput,
  TouchableOpacity,
  Image
} from "react-native";
export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.logo}>
          <Image
            source={require("../img/react.jpg")}
            style={{ width: 66, height: 58 }}
          />
          <Image
            source={require("../img/pubnublogo.png")}
            style={{ width: 60, height: 60 }}
          />
        </View>
        <Text style={styles.welcome}>Chat with Pubnub</Text>
        <TextInput
          style={styles.input}
          placeholder="Username"
          autoCapitalize="none"
          autoCorrect={false}
          onChangeText={username => this.setState({ username })}
        />
        <TextInput
          style={styles.input}
          secureTextEntry={true}
          placeholder="Password"
          onChangeText={password => this.setState({ password })}
        />
        <View style={styles.btnContiner}>
          <TouchableOpacity style={styles.btn} onPress={() => this.login()}>
            <Text style={styles.btntext}>Login</Text>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    bottom: 66
  },
  welcome: {
    fontSize: 30,
    textAlign: "center",
    margin: 10,
    fontWeight: "300"
  },
  input: {
    width: "90%",
    backgroundColor: "skyblue",
    padding: 15,
    marginBottom: 10
  },
  btnContiner: {
    flexDirection: "row",
    justifyContent: "center",
    width: "80%"
  },
  btn: {
    backgroundColor: "orange",
    padding: 15,
    width: "45%"
  },
  btntext: { fontSize: 16, textAlign: "center" },
  logo: {
    flexDirection: "row"
  }
});

Now you can see the result of our login form styling.

Next, we’ll add user authentication functionality. To do this, first, we create a state object with two variables username and password like in the code snippet below:

state = { username: "", password: "" };

When a user types in the text input field, we add the text data to state variables as shown in the code snippet below:

<View style={styles.container}>
  <Text style={styles.welcome}>Chat with Pubnub</Text>
  <TextInput
    style={styles.input}
    placeholder="Username"
    autoCapitalize="none"
    autoCorrect={false}
    onChangeText={username => this.setState({ username })}
    />
  <TextInput
    style={styles.input}
    secureTextEntry={true}
    placeholder="Password"
    onChangeText={password => this.setState({ password })}
    />
  <View style={styles.btnContiner}>
    <TouchableOpacity style={styles.btn} >
      <Text style={styles.btntext}>Login</Text>
    </TouchableOpacity>
  </View>
</View>

When the user clicks on the login button, it will trigger the login function.

For a simple and quick configuration, we create a const variable named user which contains username and password, as shown in the code sample below.

This is purely for demo purposes. Don’t do this in your production application! Make a secure, server-marshaling authentication system.

const user = {
  0: { username: "user1", password: 1111 },
  1: { username: "user2", password: 1111 }
};

Now, we use this variable to represent a database and create a login function as shown in the code snippet below.

login() {
  if (
    (user[0].username == this.state.username &&
      user[0].password == this.state.password) ||
    (user[1].username == this.state.username &&
      user[1].password == this.state.password)
  ) {
    this.props.navigation.navigate("MainChat", {
      username: this.state.username
    });
  } else {
    console.log(this.state);
    alert("username or password is incorrect");
  }
}

Once done, you’ll have something that looks like this.

We can see that our login authentication is successful. Now, we need to redirect the user to the chat room after the successful login.

Redirect After Login Success

Next, we need to use the react-navigation package to handle routes and redirects, like web pages. First, you need to follow the React Navigation installation instructions here. Then we need to move all the code from App.js to Login.js and change the class name. Then we need to make App.js compatible with route management with react-navigation.

import { createStackNavigator, createAppContainer } from "react-navigation";
import Login from "./src/components/Login";
import MainChat from "./src/components/MainChat";
const AppNavigator = createStackNavigator(
  {
    Login: {
      screen: Login
    },
    MainChat: {
      screen: MainChat
    }
  },
  {
    initialRouteName: "Login"
  }
);
export default createAppContainer(AppNavigator);

Now we can redirect users to the Main chat screen after login. The functionality will work after we add the navigate function.

login() {
  if (
    (user[0].username == this.state.username &&
      user[0].password == this.state.password) ||
    (user[1].username == this.state.username &&
      user[1].password == this.state.password)
  ) {
    this.props.navigation.navigate("MainChat", {
      username: this.state.username
    });
  } else {
    console.log(this.state);
    alert("username or password is incorrect");
  }
}

Here we need to specify screen name (i.e. MainChat) and parameter (i.e. username) that you want to use on the next page.

We configure MainChat.js by replacing random ID with a parameter value as shown in the following code snippet:

render() {
  return (
    <GiftedChat
      messages={this.state.messages}
      onSend={messages => this.onSend(messages)}
      user={{
        _id: this.props.navigation.getParam("username")
      }}
    />
  );
}

Let’s try everything out!

You can see the login by user1 and then the sending of a new message. User1 will see the message from User2 on the proper side of the UI.

Wrapping Up

In this tutorial, we learned how to store and fetch historic chat messages, and display them in the order they were received.

Your chat app is growing in features, but there’s a lot more we can do. Keep an eye out for subsequent posts on typing indicators, unread message count, user presence, and more.

#3: Online User Count

In our previous two parts, we covered basic messaging, UI and crude authentication, and added message history. If you haven’t already, complete parts one and two, as they lay the groundwork for what we’ll cover going forward.

In this part, we will learn how to display an online user count to identify how many users are in the chatroom in realtime. To implement this feature in our app, we are going to use PubNub Presence.

Activating PubNub Presence

To activate Presence, navigate to the PubNub Admin Dashboard. The dashboard will have multiple configurations which you can see in the screenshot below. The minimum value set for the interval is 10 seconds. This will help when you are in dev mode. To reduce costs in production, raise this number to a larger interval length, like 1 minute.

Users will trigger the following presence events in the channels they subscribe to:

  • join — A user subscribes to a channel.
  • leave — A user unsubscribes from a channel.
  • timeout — A timeout event is fired when a connection to a channel is severed and the subscriber has not been seen in 320 seconds (just over 5 minutes). This timeout interval can be customized using the heartbeat and heartbeat interval settings (SDK v3.6 or later).
  • state-change — A state-change event will be fired any time the state is changed using the state API (function signature varies by SDK).
  • interval — An occupancy count is sent every 10 seconds (the default setting for the Presence Interval property, which is also configurable in the Presence add-on settings in your admin dashboard).

Activating Presence in our React Native App

In this step, we are going to activate Presence in our React Native chat app that we’ve built up in our previous parts.

First, you need to change the hardcoded “MainChat1” room name to a variable as shown in the code snippet below (MainChat.js file):

const RoomName = "MainChat1";

Then, you need to add UUID to identify the device and add a timeout for dev mode, which you can see in the code snippet below (MainChat.js file):

this.pubnub = new PubNubReact({
   publishKey: "yourapikeyhere", 
   subscribeKey: "yourapikeyhere", 
   uuid: this.props.navigation.getParam("username"),   presenceTimeout: 10
 });

Then, you need to activate user presence when subscribing to the channel by setting the withPresence state to true like in the code snippet below:

this.pubnub.subscribe({ channels: [RoomName], withPresence: true });

This completes the setup of Presence in our application.

Handling Presence State

In this step, we are going to handle the presence states. Start off by creating a new state object. This will handle data like messages, online users, and online user counts as shown in the code snippet below. Put this in the constructor of MainChat:

this.state = { 
    isTyping: false, 
    messages: [], 
    onlineUsers: [], 
   onlineUsersCount: 0 
};

Handling Presence Events

Here we are going to handle the Presence event. First, you need to create a new function named PresenceStatus and get the presence state using a getPresence function and pass two parameters. They are the room name and a callback function, which is shown in the code snippet below. Put these in the MainChat class:

PresenceStatus = () => { 
    this.pubnub.getPresence(RoomName, presence => {})
 }

Note: There are two types of events that we need to handle, which are user-generated and PubNub generated.

PubNub User Presence Events

These are user-generated events such as logging in, logging out, etc. When a user clicks on login, it generates a join event. When a user clicks on logout to close the app, it generates a leave event and when a user doesn’t do anything for a while, it generates a timeout event.

Join Event

The join event is very simple. Whenever a user successfully logs into the app, they join the chat room and receive a join event. This can be done by using the code in the following code snippet:

PresenceStatus = () => { 
    this.pubnub.getPresence(RoomName, presence => { 
       if (presence.action === "join") { 
          let users = this.state.onlineUsers;
          users.push({ 
              state: presence.state, 
              uuid: presence.uuid 
          }); 
          this.setState({
            onlineUsers: users, 
             onlineUsersCount: this.state.onlineUsersCount + 1 }); 
         }
 }) 
}

First, we need to check if the presence event is a “join.” Then we add the new user to the state collection. Finally, we update the React State instance.

Leave or Timeout Events

Next, we generate a leave event and timeout event whenever the user logs out or times-out of the chatroom. When a user clicks “back” or anything that initiates the componentDidMount event, the app generates a leave event.

When a user is idle for too long, they get pushed out of the chat room. The code to implement this is provided in the code snippet below. Add this to your PresenceStatus function:

if (presence.action === "leave" || presence.action === "timeout") {    let leftUsers = this.state.onlineUsers.filter( 
      users => users.uuid !== presence.uuid );      
          this.setState({ onlineUsers: leftUsers });
          const length = this.state.onlineUsers.length;        
          this.setState({ onlineUsersCount: length }); 
   this.props.navigation.setParams({
      onlineUsersCount: this.state.onlineUsersCount 
    }); 
}

First, we get other users and filter them out of the state, then put data back into the state. Then we need to count online users and put the count back into the onlineUsersCount state, as shown in the code snippet.

PubNub Network Presence Events

This part of the tutorial addresses the events generated by PubNub. The interval event is a PubNub network generated the event. We can use this when a user comes online on the channel. They will be able to see a real-time update such as online users joining/leaving. Also, users can identify which other users are online.

We will do the same logic as user-generated events, but by checking conditions in the interval event data.

Join Event

Add this to your PresenceStatus function:

if (presence.action === "interval") { 
    if (presence.join || presence.leave || presence.timeout) {
    let onlineUsers = this.state.onlineUsers; 
     let onlineUsersCount = this.state.onlineUsersCount; 
     if (presence.join) {
       presence.join.map( user => user !== this.uuid &&        onlineUsers.push({ state: presence.state, uuid: user }) );   onlineUsersCount += presence.join.length; 
  }
 } 
}

First, we are checking for an interval event. If new member matches the criteria, then we get current state data and create a new state instance. This gets pushed to the array of online users as seen in the code snippet above.

Next we are going to check the state after a join event. If the new UUID does not match a UUID in the collection, then we assume that the new user needs to be added to the onlineUsers collection. Then we increase the onlineUsersCount number.

Leave Event

if (presence.leave) { 
   presence.leave.map(leftUser =>    onlineUsers.splice(onlineUsers.indexOf(leftUser), 1) 
);
 onlineUsersCount -= presence.leave.length; 
}

For the leave event, we are going to keep the track of users that leave, and splice them from onlineUsers array. Then we need to decrease the onlineUsersCount instance to decrease the count of online users.

Timeout Event

Here, we need to do the same process as the leave event.

if (presence.timeout) {   
    presence.timeout.map(timeoutUser => onlineUsers.splice(onlineUsers.indexOf(timeoutUser), 1) ); onlineUsersCount -= presence.timeout.length; 
}

We add the update state logic, like in the code snippet below:

this.setState({ onlineUsers, onlineUsersCount });

Next, update the componentWillMount function with the following code:

componentWillMount() { 
     this.props.navigation.setParams({ onlineUsersCount:   this.state.onlineUsersCount,
   leaveChat: this.leaveChat.bind(this) 
}); 
this.pubnub.subscribe({
    channels: [RoomName], withPresence: true 
}); 
this.pubnub.getMessage(RoomName, m => { 
    this.setState(previousState => ({ messages: GiftedChat.append(previousState.messages, m["message"])
 }));
 }); 
// this.hereNow(); this.PresenceStatus();
}

Now we are done with implementing code for PresenceStatus.

Display Presence Data

In this section, we are going to display state data that we manipulated in the last section. First, we are going to display data on the navbar. Then we are going to display the user’s avatars below the navbar.

Display User Count

Navbar is a component that is generated from the React Navigation package. We need to pass the onlineUserCount state to the navbar. Then we need to set parameters from outside the navbar scope to get the parameter from the navbar.

Setting State Data on Update

We have multiple lines where we need to update onlineUserCount. So, we are going to pass it as a parameter, as shown in the code snippet below (MainChat.js):

componentWillMount() { t
his.props.navigation.setParams({ 
   onlineUsersCount: this.state.onlineUsersCount });
 }

Online User Count Data in The NavBar

To update the navbar with state data, we can get data from parameters, like in the code snippet below:

static navigationOptions = ({ navigation }) => {
     return { 
        headerTitle: navigation.getParam("onlineUsersCount") + "   member online" 
   };
};

We need to add navigationOptions which is the variable used to configure the navbar. Then, using getParam and onlineUsersCount, we can set the text in the UI.

Display Online Users in Chat with an Avatar

In the next step, we will implement avatars for user presence below the navbar. For this, you need to copy the code provided in the code snippet below and replace it in your MainChat.js file.

render() {    
let username = this.props.navigation.getParam("username"); 
   return ( 
     <View style={{ flex: 1 }}> 
     <View style={styles.online_user_wrapper}> {
this.state.onlineUsers.map((item, index) => {
 return ( 
<View key={item.uuid}>
 <Image key={item.uuid} style={styles.online_user_avatar} source={{ uri: "https://robohash.org/" + item.uuid }} /> </View> );
      })
   } 
    </View>
    <GiftedChat messages={this.state.messages} 
        onSend={messages => this.onSend(messages)}
        user={{ _id: username, name: username, avatar: "https://robohash.org/" + username }} />
   </View> ); 
   } 
}

Next, we are going to create a main wrapper that wraps a new section and chat view. Then we need to add a wrapper to contain the avatar and iterate through the onlineUsers state collection. We will create a unique image for each user using robohash.

Then, finally complete it by adding some CSS, like in the code snippet below (MainChat.js):

const styles = StyleSheet.create({   
online_user_avatar: { 
      width: 50, 
      height: 50, 
      borderRadius: 20,
      margin: 10 
}, 
container: { 
      flex: 1,
      justifyContent: "center", 
      alignItems: "center",
      backgroundColor: "#F5FCFF" 
 },
 welcome: { 
      fontSize: 20,
      textAlign: "center",
       margin: 10 
 }, 
  online_user_wrapper: {
      height: "8%", 
      justifyContent: "flex-end", 
      alignItems: "center", 
      flexDirection: "row", 
      backgroundColor: "grey", 
     flexWrap: "wrap"
 } 
});

Chat Logout Functionality

Before we consider this tutorial section complete, let’s implement a logout feature for our chat app. When the user clicks on logout, the user presence data needs to be adjusted. Also, the user must unsubscribe from the channel.

Create a leaveChat function

Here, we are going to create a leaveChat function like shown in the code snippet below (MainChat.js):

leaveChat = () => { 
  this.pubnub.unsubscribe({ 
    channels: [RoomName] });  
    return this.props.navigation.navigate("Login");
 };

When the function is executed, the user will be unsubscribed from the PubNub channel and redirected to the Login screen.

We need to invoke this function in two places, which are mentioned below. Before we implement a componentWillUnmount function, we need to implement the leaveChat function as shown in the code snippet below:

componentWillUnmount() { this.leaveChat(); }

Add Logout to the NavBar

We are adding the function to the logout button click in the navbar. This is to cover a user logging out manually. We will save some screen space by passing it to the header, like in the code snippet below. Modify your componentWillMount function:

componentWillMount() { 
  this.props.navigation.setParams({ 
   onlineUsersCount:  this.state.onlineUsersCount,
   leaveChat: this.leaveChat.bind(this)
 }); ...

Lastly, we are going to remove the back button and replace it with our logout button in the header. Add this code below the constructor function in MainChat.js:

static navigationOptions = ({ navigation }) => { 
    return { headerTitle: navigation.getParam("onlineUsersCount", "No") + " member online", headerLeft: null, headerRight: (
   <Button onPress={() => { 
     navigation.state.params.leaveChat(); 
    }} 
     title="Logout" color="red"
 /> )
 };

This completes our tutorial to display online users using PubNub Presence!

Using your command line, run the React Native mobile app simulator.

react-native run-ios 
react-native run-android

Wrapping Up

In this tutorial, we learned how to keep tabs on how many users are currently in the chatroom using PubNub Presence.

Your chat app is growing in features, but there’s a lot more we can do. Keep an eye out for subsequent posts on typing indicators, unread message count, and more.

#4: Typing Indicator

In this chapter, we are going to learn how to display the typing indicator in the React Native Chat application that we had created in our previous tutorials. Before going through this tutorial, we strongly recommend going through our previous tutorials of creating a React Native Chat app as we are going to continue from the previous phase of the tutorial. The base for the React Native chat app and most of the functionalities required in the chat app has already been completed in previous tutorials. In this tutorial, we are just going to add another useful feature that is a typing indicator. 

A typing indicator refers to an indicator that indicates the members in the chatroom which the user is typing and going to send a message. It is a very useful feature that is readily available in most chat applications nowadays and we are going to learn how to implement it in this tutorial.

Here is the summary of the steps we are going through in details in our tutorial:

  1. Detect typing event on the users’ device.
  2. Assign a local event to PubNub API.
  3. Detect event and display status on another device (such as smartphones, tabs, etc) that participate in a chat room.

Requirements

To follow this tutorial, please make sure you have the following installed on your local development environment and have access to the services mentioned below:

  • Nodejs (>= 8.x.x) with NPM/Yarn installed
  • The overall code from our previous chapter.
  • watchman: The file change watcher for React Native projects
  • PubNub account, a free tier.
  • Add your PubNub key on src/components/config.js

Detect typing event on local

In this step, we are going to detect the typing event on the user’s device. The typing event is an action when a user begins to type in the chat interface. Here, we are going to add the inputTextChanged event to handle keyboard event on GiftedChat as shown in the code snippet below:

<GiftedChat

         messages={this.state.messages}

         onSend={messages => this.onSend(messages)}

         onInputTextChanged={this.detectTyping}

         user={{

           _id: username,

           name: username,

           avatar: "https://robohash.org/" + username

         }}

       />

Then, we are going to use lodash debounce to limit the event firing rate when we detect the start typing and stop typing events. This can be done using the code from the following code snippet which we must implement in our componentWillMount() method:

componentWillMount() {

   this.init = false;

   this.startTyping = _.debounce(

     () => {

       if (!this.init) return;

       this.setState({ isTyping: true });

     },

     1000

   );




   this.stopTyping = _.debounce(() => {

     if (!this.init) return;

     this.setState({ isTyping: false });

   }, 4000);

}

this.init code variable is used to set the initial typing state and also used for a flag that switches between the start and the stop state.

this.startTyping state variable has a function to set start the typing state. Here, we are going to set the delay for 1 second before the event is fired.

this.stopTyping state variable has a function to stop the typing state. Here, we are going to set a delay for 4 seconds before the event is fired.

To initialize the use of these two functions, we are going to create a function named detectTyping() and start checking the type event as shown in the code snippet below:

detectTyping = text => {

   if (text != "")  isTyping = true;

   this.startTyping();

   this.stopTyping();

 };

Here, the text is the data from the input. If it is not empty, it means start typing event needs to be triggered by the calling of the startTyping() method. Then, we add two functions for detecting event here as shown in the code snippet above.

Now, we need to test the triggering of the events with the integration of console.logs() in our code as shown in the code snippet below:

componentWillMount() {

   this.init = false;

   this.startTyping = _.debounce(

     () => {

       if (!this.init) return;

       this.setState({ isTyping: true });

       console.log("startTyping", this.state.isTyping);

     },

     1000

   );




   this.stopTyping = _.debounce(() => {

     if (!this.init) return;

     this.setState({ isTyping: false });

     console.log("stopTyping", this.state.isTyping);

     }, 4000);

}

As a result, when we enter many characters, the two functions have fired once and worked on delay as shown in the browser console simulation below:

The next step involves, pushing the state to PubNub service.

Push a local event to Pubnub service 

In this step, we are going to create a new channel to handle the push of local events to PubNub. Here, we are going to create a constant for defining a new channel name using room name (RoomName) and add state-channel to make prefix that makes a StateChannel.

const StateChannel = RoomName + “state-channel”;

Here, we need to add a typing state and a user UUID to the Pubnub service as shown in the code snippet below:

PNState = state => {

   let username = this.props.navigation.getParam("username");

    this.pubnub.publish(

     {

       message: { username: username, isTyping: state },

       channel: StateChannel,

       storeInHistory: false

     },

     function(status, response) {

       console.log(response);

     }

   );

 };

Now, we need to publish to a new channel that we had created before with a message that contains a username and typing state. Then, we don’t need to keep a state data so we set storeInHistory to false.

Now, we are going to replace the local state with PNState() function as shown in the code snippet below with highlighted code lines:

this.startTyping = _.debounce(() => {

     if (!this.init) return;

     this.PNState(true);

   }, 1000);
this.stopTyping = _.debounce(() => {

     if (!this.init) return;

     this.PNState(false);

   }, 4000);

Now, we know who has currently started or stopped typing. Next, we move on to the final step that is to display the typing status on another device.

Display status on another chat room member device

In this last section, we are going to display a status icon that will appear when the user starts typing and disappear when the user stops typing. For that, we are going to subscribe to receive a message.

getTypingState = () => {

   this.pubnub.subscribe({

     channels: [StateChannel],

     withPresence: true

   });




   this.pubnub.getMessage(StateChannel, messages => {

     let typingIn = [];

     if (messages.message.isTyping == true) {

       typingIn.push({ uuid: messages.message.username });

       this.setState({ whoTyping: typingIn });

       console.log(this.state.whoTyping);

     } else {

       let typingOut = typingIn.filter(

         users => users.uuid !== messages.message.username

       );

       this.setState({ whoTyping: typingOut });

       console.log(this.state.whoTyping);

     }

});

First, we need to create a new function named getTypingState then subscribe to a new channel that we had created before. Then, we need to get a message from the channel.

To collect the typing users, we need to create a new array named typingIn and check the typing state from PubNub. Then, we need to push and splice data based upon the condition and push the data in state variable named whoTyping. The code for this is available in the code snippet above.

Lastly, we need to display the typing icon in the view by using the following code snippet:

<View style={styles.online_user_wrapper}>

         {this.state.onlineUsers.map((item, index) => {

           return (

             <View key={item.uuid} style={styles.avatar_wrapper}>

         {this.state.whoTyping.map((data, index) => {

            if (data.uuid == item.uuid && data.uuid != username) {

                   return (

                     <Image

                       key={item.uuid}

                       style={styles.istyping_gif}

                       source={require("../img/istyping.gif")}

                     />

                   );

                 }

               })}

               <Image

                 key={item.uuid}

                 style={styles.online_user_avatar}

                 source={{

                   uri: "https://robohash.org/" + item.uuid

                 }}

               />

             </View>

       </View>

Here, we need to iterate the whoTyping state and check if UUID will match on the avatar. Then, we do not show the icon on typing device. And if it matches all the conditions then, we need to display the typing icon that shows the typing event in the chat interface alongside users’ avatars.

Finally, The typing indicator is working perfectly as shown in the device chat simulation below:

Hence, this means that we have successfully integrated a typing indicator to our React Native Chat application.

Conclusion

In this section of our React Native Chat app tutorial, we learned how to build an awesome feature that we see regularly in our daily life. That is a typing indicator icon that shows other users that someone is typing in the chatroom and is about to send a message. We also learned how to use debounce to delay fired events and how to use Pubnub service. And finally, we successfully got the detailed implementation guidance on how to display typing status in our React Native Chat app. Hope you enjoyed this tutorial. The overall code for this chapter is available on GitHub. In the next chapter, we are going to another awesome feature which is a Push notification.

krissanawat

krissanawat

React Native developer from beautiful Chiangmai, love Americano and travel so much