🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Add group live chat to your React Native video app

  • Lanre Adelowo
February 15th, 2019
You will need Node 8+ and React Native installed on your machine.

In this tutorial, we will be building a React Native application that will allow users to view videos and chat the same time. Here is a sample of what we will be building:

Prerequisites

To follow along this tutorial, you will need the following:

  • NodeJS >=8
  • Pusher Chatkit account. Create one here.
  • React Native. Find out how to install it here. You will need to follow the section Building Projects with Native Code.

This project will consist of a client and server. The server will be written in NodeJS and it will be used to create a Pusher Chatkit user. The client will be a React Native app. Both projects will be created inside of a directory called groupchat.

Sign up for Chatkit

To make use of Pusher Chatkit features in the application, you need to sign up for a free account. Once your account has been created, you will need to create a new Pusher Chatkit instance.

As soon as the instance is created, you will need to locate the Credentials tab and take note of the Instance Locator and Secret key as you will be needing it to proceed with the next section.

You will also need to create a room and take note of its ID.

Creating the server

Basically the server will be used to create a user in the Pusher Chatkit dashboard. To build the server, we will need to install some dependencies.

You will need to create a server directory. After which you will need to create a package.json file inside the server directory with the following contents:

    // groupChat/server/package.json
    {
      "dependencies": {
        "@pusher/chatkit-server": "^1.0.4",
        "body-parser": "^1.18.3",
        "dotenv": "^6.2.0",
        "express": "^4.16.4"
      }
    }

To install them, you will need to run the following command:

    $ npm install

Once we are done installing the dependencies, we will need to create an index.js file which will contain the actual server we are to build.

    // groupChat/server/index.js

    require('dotenv').config({ path: 'variables.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const Chatkit = require('@pusher/chatkit-server');
    const port = process.env.PORT || 5200;

    const app = express();

    const chatkit = new Chatkit.default({
      instanceLocator: process.env.CHATKIT_INSTANCE_LOCATOR,
      key: process.env.CHATKIT_SECRET_KEY,
    });

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/users', (req, res) => {
      const { username } = req.body;

      chatkit
        .createUser({
          id: username,
          name: username,
        })
        .then(data => {
          res.status(201).send({ id: username, username: username });
        })
        .catch(err => {
          if (err.error === 'services/chatkit/user_already_exists') {
            res.status(200).send({ id: username, username: username });
          } else {
            res.status(500).json(err);
          }
        });
    });

    app.post('/authenticate', (req, res) => {
      const authData = chatkit.authenticate({
        userId: req.query.user_id,
      });
      res.status(authData.status).send(authData.body);
    });

    app.listen(port, function() {
      console.log(`Service is running at ${port}`);
    });

In the above code, we create a server that has two endpoint, /users and /authenticate. The /users endpoint basically reaches out to Pusher ChatKit’s server to register a user while the authenticate endpoint generates a token for the user. That token will be used to make subsequent chat requests.

Before running the server, you will be needing to create a variables.env file that contains your credentials:

    // groupChat/variables.env

    PORT=5000
    CHATKIT_INSTANCE_LOCATOR=YOUR_PUSHER_INSTANCE_LOCATOR
    CHATKIT_SECRET_KEY=YOUR_PUSHER_SECRET_KEY

Once this is done, you are ready to run the server. That can be done by:

    $ node index.js 

Creating the React Native project

To do that we will be needing to make use of the react-native CLI tool to create a new iOS project. Open a terminal and run the following command:

    $ cd groupChat # the directory root
    $ react-native init client

Depending on your internet connection, the above command should take some time. Once it is done, we are ready to add functionality to our app.

The application will have two pages, one for login and the other the page where the video is streamed and the live chat occurs. We will also be depending on a few libraries:

  • react-native-gifted-chat
  • react-native-video
  • axios
  • @pusher/chatkit-client

To install them, you will need to run the following command:

    $ npm install react-native-gifted-chat react-native-video axios @pusher/chatkit-client

Once the above command succeeds, you will need to link all native dependencies. That can be done by:

    $ react-native link

To get started, we will build the login page. You will need to create Login.js. Inside of it, you will need to paste the following contents:

    // groupChat/client/Login.js

    import React, { Component } from 'react';
    import { Alert, Button, TextInput, View, StyleSheet } from 'react-native';

    export default class Login extends Component {
      constructor(props) {
        super(props);

        this.state = {
          username: '',
        };
      }

      render() {
        return (
          <View style={styles.container}>
            <TextInput
              value={this.state.username}
              onChangeText={username =>
                this.setState({ username: username.trim() })
              }
              placeholder={'Username'}
              style={styles.input}
            />

            <Button title={'Login'} style={styles.input} onPress={() => {this.props.cb(this.state.username)}} />
          </View>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
      },
      input: {
        width: 200,
        height: 44,
        padding: 10,
        borderWidth: 1,
        borderColor: 'black',
        marginBottom: 10,
      },
    });

The above code is as simple as can get and we will need to go on creating the video and chat view screens.

We will start with the video screen as it is a not as complex as the chat screen. We will need to create a VideoPlayer.js file with the following contents:

    // groupChat/client/VideoPlayer.js

    import React, { Component } from 'react';
    import { StyleSheet } from 'react-native';
    import Video from 'react-native-video';

    export default class VideoPlayer extends Component {
      render() {
        return (
          <Video
            source={{
              uri:
                'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4',
            }}
            ref={ref => {
              this.player = ref;
            }}
            style={styles.backgroundVideo}
          />
        );
      }
    }

    var styles = StyleSheet.create({
      backgroundVideo: {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
      },
    }

Here we load a sample video of Big Buck Bunny as that is what everyone will be watching and apply very little styling to it. That is literally all we need to play videos, thanks to the react-native-video library.

We will then proceed to the chat view, you will need to create a ChatView.js file with the following contents:

    // groupChat/client/ChatView.js

    import React, { Component } from 'react';
    import {
      ActivityIndicator,
      StyleSheet,
      Text,
      TextInput,
      FlatList,
      View,
    } from 'react-native';
    import { GiftedChat } from 'react-native-gifted-chat';
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client';

    export default class ChatView extends Component {
      state = {
        messages: [],
        currentUser: null,
        roomID: 'YOUR_ROOM_ID',
      };

      onSendMessage = e => {
        this.props.onSendMessage(e.nativeEvent.text);
        this.refs.input.clear();
      };

      componentDidMount() {
        const chatManager = new ChatManager({
          instanceLocator: 'YOUR_INSTANCE_LOCATOR',
          userId: this.props.userID,
          tokenProvider: new TokenProvider({
            url: 'http://localhost:5000/authenticate',
          }),
        });

        chatManager
          .connect()
          .then(currentUser => {
            this.setState({ currentUser });

            currentUser.subscribeToRoom({
              roomId: this.state.roomID,
              hooks: {
                onMessage: this.onReceive,
              },
            });
          })
          .catch(err => {
            console.log(err);
          });
      }

      render() {
        let toDisplay = null;

        if (this.state.currentUser === null) {
          toDisplay = (
            <View>
              <ActivityIndicator size="large" />
            </View>
          );
        } else {
          toDisplay = (
            <GiftedChat
              messages={this.state.messages}
              onSend={messages => this.onSend(messages)}
              user={{
                _id: this.state.currentUser.id,
              }}
            />
          );
        }

        return toDisplay;
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        paddingTop: 10,
      },
    });

The above code seems to be a lot so I will go through it. In componentDidMount, we create a connection to Pusher Chatkit with our credentials. If it succeeds, we keep a reference to the current user and subscribe the user to a given room.

Remember to replace the placeholders with your credentials.

In the render method, we decide if we are to show a loading indicator or the actual chat thread. So while the connection is still being made to Pusher Chatkit and the user isn’t subscribed to the room yet, a spinner is displayed letting the user know a process is underway. The chat thread makes use of react-native-gifted-chat as that helps us build a standard and awesome chat UI.

But if you look closely, you will notice we didn’t define all methods. For example when we subscribe a user to a room with subscribeToRoom , we request that the onReceive method be fired when a new message is gotten. Let’s go ahead to create those.

Update the ChatView.js with the following:

    // groupChat/client/ChatView.js

    onReceive = data => {
        const { id, senderId, text, createdAt } = data;
        const incomingMessage = {
          _id: id,
          text: text,
          createdAt: new Date(createdAt),
          user: {
            _id: senderId,
            name: senderId,
            avatar:
              'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQmXGGuS_PrRhQt73sGzdZvnkQrPXvtA-9cjcPxJLhLo8rW-sVA',
          },
        };

        this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, incomingMessage),
        }));
      };

      onSend = (messages = []) => {
        messages.forEach(message => {
          this.state.currentUser
            .sendMessage({
              text: message.text,
              roomId: this.state.roomID,
            })
            .then(() => {})
            .catch(err => {
              console.log(err);
            });
        });
      };

We defined the onReceive method to append the data we have gotten to the chat UI by calling GiftedChat.append. We have also defined the onSend method that will be triggered whenever a user sends a new chat message.

The ChatView.js should look like this after the above addition:

    // groupChat/client/ChatView.js

    import React, { Component } from 'react';
    import {
      ActivityIndicator,
      StyleSheet,
      Text,
      TextInput,
      FlatList,
      View,
    } from 'react-native';
    import { GiftedChat } from 'react-native-gifted-chat';
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client';

    export default class ChatView extends Component {
      state = {
        messages: [],
        currentUser: null,
        roomID: '19376361',
      };

      onSendMessage = e => {
        this.props.onSendMessage(e.nativeEvent.text);
        this.refs.input.clear();
      };

      componentDidMount() {
        const chatManager = new ChatManager({
          instanceLocator: 'v1:us1:dcc43a0d-c7b8-494f-809d-abb9b00a907a',
          userId: this.props.userID,
          tokenProvider: new TokenProvider({
            url: 'http://localhost:5000/authenticate',
          }),
        });

        chatManager
          .connect()
          .then(currentUser => {
            this.setState({ currentUser });

            currentUser.subscribeToRoom({
              roomId: this.state.roomID,
              hooks: {
                onMessage: this.onReceive,
              },
            });
          })
          .catch(err => {
            console.log(err);
          });
      }

      onReceive = data => {
        const { id, senderId, text, createdAt } = data;
        const incomingMessage = {
          _id: id,
          text: text,
          createdAt: new Date(createdAt),
          user: {
            _id: senderId,
            name: senderId,
            avatar:
              'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQmXGGuS_PrRhQt73sGzdZvnkQrPXvtA-9cjcPxJLhLo8rW-sVA',
          },
        };

        this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, incomingMessage),
        }));
      };

      onSend = (messages = []) => {
        messages.forEach(message => {
          this.state.currentUser
            .sendMessage({
              text: message.text,
              roomId: this.state.roomID,
            })
            .then(() => {})
            .catch(err => {
              console.log(err);
            });
        });
      };

      render() {
        let toDisplay = null;

        if (this.state.currentUser === null) {
          toDisplay = (
            <View>
              <ActivityIndicator size="large" />
            </View>
          );
        } else {
          toDisplay = (
            <GiftedChat
              messages={this.state.messages}
              onSend={messages => this.onSend(messages)}
              user={{
                _id: this.state.currentUser.id,
              }}
            />
          );
        }

        return toDisplay;
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        paddingTop: 10,
      },
    });

At this stage, we have all we need for a functioning app, all that is left is to tie all of them together. That will be done in App.js. It is safe to clear all contents it currently contains and paste the following:

    // groupChat/client/App.js

    import React, { Component } from 'react';
    import { Alert, Platform, StyleSheet, Text, View } from 'react-native';
    import Login from './Login';
    import axios from 'axios';
    import ChatView from './ChatView';
    import VideoPlayer from './VideoPlayer';

    export default class App extends Component {
      constructor(props) {
        super(props);

        this.state = {
          isAuthenticated: false,
          id: '',
        };
      }

      onLoginCallBack = username => {
        if (username.length === 0) {
          Alert.alert('Login', 'Please provide your username');
          return;
        }

        axios
          .post('http://localhost:5000/users', { username })
          .then(res => {
            this.setState({
              isAuthenticated: true,
              id: res.data.id,
            });
          })
          .catch(err => {
            Alert.alert('Login', 'Could not log you in');
          });
      };

      render() {
        return (
          <View style={{ flex: 1 }}>
            {!this.state.isAuthenticated || this.state.currentUser === null ? (
              <View style={styles.container}>
                <Login cb={this.onLoginCallBack} />
              </View>
            ) : (
              <View
                style={[
                  { flex: 1, flexDirection: 'column' },
                  { backgroundColor: '#ffff' },
                ]}
              >
                <View style={{ flex: 0.4 }}>
                  <VideoPlayer style={{ backgroundColor: 'red' }} />
                </View>
                <View style={{ flex: 0.6 }}>
                  <ChatView userID={this.state.id} />
                </View>
              </View>
            )}
          </View>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
      },
    });

Once done, you will need to run the following command to run the application:

    $ react-native run-ios # for iOS
    $ react-native run-android # for android

You can test the app on your machine using the emulator or a physical device.

Conclusion

That’s it! In this tutorial, you learned how to create a chat app that allows discussion surrounding a video being streamed by a handful of users. This was possible with React Native and Pusher Chatkit.

The code can be found on GitHub.

Clone the project repository
  • Android
  • Chat
  • JavaScript
  • iOS
  • React Native
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.