🎉 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

Playing audio and video file attachments in a React Native chatroom

  • Wern Ancheta
April 9th, 2019
You will need Node 11+, Yarn and React Native 0.5+ installed on your machine. Experience with React Native will be helpful.

In this tutorial, we will build a chat app that can play mp3 and mp4 files right from the app itself.

Prerequisites

Knowledge of React and React Native is required to follow this tutorial.

The following package versions are used. If you encounter any issues in compiling the app, try to use the following:

  • Node 11.2
  • Yarn 1.13
  • React Native 0.59

You’ll need a Chatkit app instance with the test token provider enabled. If you don’t know the basics of using Chatkit yet, be sure to check out the official docs. This tutorial assumes that you a least know how to create, configure, and inspect a Chatkit app instance.

Lastly, you’ll need an ngrok account for exposing the server to the internet.

App overview

As mentioned earlier, we will build a chat app which can view mp3 and mp4 files. When a user opens the app, they will be greeted by a login screen:

Once logged in, the user will be navigated to the chat screen. This is where they can pick files to send and view on the screen:

You can find the code on this GitHub repo.

Bootstrapping the app

In order to save time for building the crucial parts of the app, I’ve created a starter branch in the GitHub repo. This contains the server, login, and navigation code. We will only briefly go through what each one does, so I recommend you to check out my previous tutorial if you need a more detailed explanation of what the code does.

Start by cloning the repo and switching to the starter branch:

    git clone https://github.com/anchetaWern/RNChatkitFileViewer.git
    cd RNChatkitFileViewer
    git checkout starter

Next, install and link the packages:

    yarn
    react-native eject
    react-native link react-native-gesture-handler
    react-native link react-native-video
    react-native link react-native-config
    react-native link react-native-document-picker

We’re also using a package called React Native Audio Toolkit, but it doesn’t play nice with react-native link so it’s better if you just follow the instructions from their installation doc to manually link the package.

Next, update your android/app/build.gradle file and add the following. This allows us to copy the Gradle file from this module at build time. This helps us avoid problems when upgrading the package later on:

    apply from: "../../node_modules/react-native/react.gradle"

    // add these:
    apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
    apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

Next, delete the node_modules/react-native-gifted-chat/node_modules/react-native-video folder. This will prevent the following error from showing:

This error occurs because both React Native Gifted Chat and the project needs to use the React Native Video component. This leads to the name conflict that you see above. Removing Gifted Chat’s copy prevents the issue from happening, though it’s not the ideal solution. A GitHub issue was already created for dealing with this, so keep an eye on that issue if you’re reading this in the future.

Lastly, install the server dependencies and update the .env file and server/.env file with your Chatkit credentials:

    cd server
    yarn

Overview of the server and login screen

Since we won’t be going through the code of the server and login screen, I’ll just provide a brief overview of what they do.

The Login screen is used for getting the username of the current user and their friend’s username. The friend’s username doesn’t already need to have been created with Chatkit when this happens. The Login screen is responsible for passing the Chatkit user ID (returned from the server when the user is created), username, and friend’s username to the Chat screen.

The server is responsible for creating a Chatkit user and creating a room. In both instances, the user or room is only created if it hasn’t been created already. Note that we’re storing the rooms in an array, so if you restart the server, all the rooms that were created will no longer be available and will be considered to be non-existent (even if they are). This means that the server will create the room again, and it will be the one to be used when sending messages. To alleviate this issue, log in to your Chatkit app console and click on the ROOMS tab to view the ID and name of the rooms that were previously created. Copy those and update the server/index.js file:

    let rooms = [{
      id: MY_ROOM_ID,
      name: 'MY ROOM NAME'
    }];

Chat screen

Now we’re ready to start coding the app. Open the app/screens/Chat.js file and import the packages and components we need:

    // app/screens/Chat.js
    import React, { Component } from 'react';
    import { View, ActivityIndicator, TouchableOpacity, Alert } from 'react-native';
    import { GiftedChat, Send, Message } from 'react-native-gifted-chat'; // for building the chat UI
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client'; // for implementing chat functionality
    import axios from 'axios'; // for making http requests
    import Config from 'react-native-config'; // for getting config from .env file
    import Icon from 'react-native-vector-icons/FontAwesome'; 
    import { DocumentPicker } from 'react-native-document-picker'; // for picking files
    import * as mime from 'react-native-mime-types'; // for file type validation
    import Modal from 'react-native-modal'; 

    import ChatBubble from '../components/ChatBubble'; // custom chat bubble
    import AudioPlayer from '../components/AudioPlayer'; // for playing mp3 files
    import VideoPlayer from '../components/VideoPlayer'; // for playing mp4 files

Next, add your Chatkit app config:

    const CHATKIT_INSTANCE_LOCATOR_ID = `v1:us1:${Config.CHATKIT_INSTANCE_LOCATOR_ID}`;
    const CHATKIT_SECRET_KEY = Config.CHATKIT_SECRET_KEY;
    const CHATKIT_TOKEN_PROVIDER_ENDPOINT = `https://us1.pusherplatform.io/services/chatkit_token_provider/v1/${Config.CHATKIT_INSTANCE_LOCATOR_ID}/token`;

    const CHAT_SERVER = "YOUR NGROK HTTPS URL/rooms"; // don't forget to replace this

Create the Chat screen and initialize the state:

    class Chat extends Component {

      static navigationOptions = ({ navigation }) => {
        const { params } = navigation.state;
        return {
          headerTitle: `Chat with ${params.friends_username}`
        };
      };

      state = {
        messages: [], // messages currently being shown in the chat UI
        is_initialized: false, // if Chatkit has already been initialized
        is_picking_file: false, // if the user is currently picking a file
        is_modal_visible: false, // whether the modal for viewing the video is visible or not
        video_uri: null // URL of the video being shown in the modal
      };

      // next: add constructor
    }

Inside the constructor, get the nav params that were passed from the Login screen and initialize the class variables that we will be using:

    constructor(props) {
      super(props);
      const { navigation } = this.props;
      const user_id = navigation.getParam("user_id");
      const username = navigation.getParam("username");
      const friends_username = navigation.getParam("friends_username");

      const members = [username, friends_username];
      members.sort(); // alphabetically sort 

      this.user_id = user_id;
      this.username = username;
      this.room_name = members.join("-");
    }

    // next: add componentDidMount

When the component is mounted, we initialize Chatkit with the test token provider then connect to the Chat Manager by using the Chatkit user ID. After that, we make a request to the server to create a room. If the room already exists, it will simply return the room ID. We make use of the room ID to subscribe to the room so we can be notified whenever a new message comes in:

    async componentDidMount() {

      try {
        const chatManager = new ChatManager({
          instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID,
          userId: this.user_id,
          tokenProvider: new TokenProvider({ url: CHATKIT_TOKEN_PROVIDER_ENDPOINT })
        });

        let currentUser = await chatManager.connect();
        this.currentUser = currentUser;

        const response = await axios.post(
          CHAT_SERVER,
          {
            user_id: this.user_id,
            room_name: this.room_name
          }
        );

        const room = response.data;

        this.room_id = room.id.toString();
        await this.currentUser.subscribeToRoom({
          roomId: this.room_id,
          hooks: {
            onMessage: this.onReceive
          }
        });

        this.setState({
          is_initialized: true
        });

      } catch (err) {
        console.log("error with chat manager: ", err);
      }
    }

    // next: add onReceive()

Here’s the function that will be executed every time a new message is received. This appends the message to the messages array via Gifted Chat’s append method:

    onReceive = async (data) => {
      const { message } = await this.getMessage(data);

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

    // next: add getMessage

Here’s the getMessage function. This is used for extracting relevant message data to be used by Gifted Chat:

    getMessage = async ({ id, senderId, text, attachment, createdAt }) => {

      let msg_data = {
        _id: id,
        text: text,
        createdAt: new Date(createdAt),
        user: {
          _id: senderId,
          name: senderId,
          avatar: "https://png.pngtree.com/svg/20170602/0db185fb9c.png"
        },
        attachment
      };

      // so Gifted Chat shows a preview thumbnail of the video if the message has a video attachment
      if (attachment && attachment.type === 'video') {
        Object.assign(msg_data, { video: attachment.link });
      }

      return {
        message: msg_data
      };
    }

    // next: add render

Next, add the code for rendering the chat UI. The bulk of the work is already done by Gifted Chat, so all we have to do is to pass in the needed props. At the bottom is the modal for showing the video:

    render() {
      const { is_initialized, messages, video_uri } = this.state;

      return (
        <View style={styles.container}>
          {!is_initialized && (
            <ActivityIndicator
              size="small"
              color="#0064e1"
              style={styles.loader}
            />
          )}

          {is_initialized && (
            <GiftedChat
              messages={messages}
              onSend={messages => this.onSend(messages)}
              user={{
                _id: this.user_id
              }}
              renderActions={this.renderCustomActions}
              renderSend={this.renderSend}
              renderMessage={this.renderMessage}
            />
          )}

          <Modal isVisible={this.state.is_modal_visible}>
            <View style={styles.modal}>
              <TouchableOpacity onPress={this.hideModal}>
                <Icon name={"close"} size={20} color={"#FFF"} style={styles.close} />
              </TouchableOpacity>
              <VideoPlayer uri={video_uri} />
            </View>
          </Modal>

        </View>
      );
    }

    // next: add onSend

Here’s the onSend function. This gets executed when the user clicks on the send button:

    onSend([message]) {
      if (message.text) {
        let msg = {
          text: message.text,
          roomId: this.room_id
        };

        if (this.attachment) { // check if the user attached something
          const filename = this.attachment.name;
          const type = this.attachment.file_type;

          msg.attachment = {
            file: {
              uri: this.attachment.uri,
              type: type,
              name: `${filename}`
            },
            name: `${filename}`,
            type: this.attachment.type
          };
        }

        // replaces send button with ActivityIndicator
        this.setState({
          is_sending: true
        });

        // send the message
        this.currentUser.sendMessage(msg).then(() => {
          this.attachment = null;

          // show the send button  
          this.setState({
            is_sending: false
          });
        });
      }
    }

    // next: add renderCustomActions

Here’s the function for rendering custom actions. This will be used by Gifted Chat to render a button for picking files:

    renderCustomActions = () => {
      if (!this.state.is_picking_file) {
        const icon_color = this.attachment ? "#0064e1" : "#808080";

        return (
          <View style={styles.customActionsContainer}>
            <TouchableOpacity onPress={this.openFilePicker}>
              <View style={styles.buttonContainer}>
                <Icon name="paperclip" size={23} color={icon_color} />
              </View>
            </TouchableOpacity>
          </View>
        );
      }

      return (
        <ActivityIndicator size="small" color="#0064e1" style={styles.loader} />
      );
    }

    // next: add openFilePicker

Here’s the function that will be executed when the user clicks on the button for picking files:

    openFilePicker = async () => {
      await this.setState({
        is_picking_file: true // show ActivityIndicator instead of the button to prevent it this function from being executed again while picking files
      });

      // show the file picker
      DocumentPicker.show({
        filetype: [mime.lookup('mp3'), mime.lookup('mp4')], 
      }, (err, file) => {

        if (!err) {
          this.attachment = {
            name: file.fileName,
            uri: file.uri,
            type: "file",
            file_type: mime.contentType(file.fileName)
          };

          Alert.alert("Success", "File attached!");
        }

        this.setState({
          is_picking_file: false
        });
      });
    }

    // next: add renderSend

Next, add the function for rendering the send button. Gifted Chat’s default button works, but we need to make sure nobody clicks the send button again while a message is being sent. To implement this, we simply show an ActivityIndicator if the app is currently sending a message:

    renderSend = props => {
      if (this.state.is_sending) {
        return (
          <ActivityIndicator
            size="small"
            color="#0064e1"
            style={[styles.loader, styles.sendLoader]}
          />
        );
      }

      return <Send {...props} />;
    }

    // next: add renderMessage

Next is the function for rendering a message. Don’t confuse this with the chat bubble, as it’s only a part of the message. Check the code for the Message component so you have an idea. In the code below, we render a custom chat bubble if the attachment is an audio file. We also add a custom function for when the chat bubble is long-pressed if the attachment is a video file:

    renderMessage = (msg) => {

      const { attachment } = msg.currentMessage;
      const renderBubble = (attachment && attachment.type === 'audio') ? this.renderPreview.bind(this, attachment.link) : null;
      const onLongPress = (attachment  && attachment.type === 'video') ? this.onLongPressMessageBubble.bind(this, attachment.link) : null;

      const modified_msg = {
        ...msg,
        renderBubble, // for audio
        onLongPress, // for video
        videoProps: {
          paused: true // Gifted Chat auto plays video previews by default, we don't want that because we have our own component for viewing videos
        }
      }

      return <Message {...modified_msg} />
    }

    // next: add onLongPressMessageBubble

Note: The onLongPress isn’t really ideal for production apps. What you want instead is to overlay the video’s thumbnail preview with a play icon. When the user clicks on it, the modal for viewing the video will show up. You can pass the renderMessageVideo prop to Gifted Chat to implement this.

Here’s the function to be executed when the user long presses on a message with a video attachment on it:

    onLongPressMessageBubble = (link) => {
      this.setState({
        is_modal_visible: true, // show the modal for viewing video
        video_uri: link // set the URL for the VideoPlayer component
      });
    }

    // next: add renderPreview

Next is the renderPreview function. This is responsible for showing a custom chat bubble for playing an audio attachment:

    renderPreview = (uri, bubbleProps) => {
      const text_color = (bubbleProps.position == 'right') ? '#FFF' : '#000'; // current user's messages are always displayed on the right side with the chat bubble background color as blue
      const modified_bubbleProps = {
        ...bubbleProps
      };

      return (
        <ChatBubble {...modified_bubbleProps}>
          <AudioPlayer url={uri} />
        </ChatBubble>
      );
    }

Here’s the function for hiding the modal for viewing videos:

    hideModal = () => {
      this.setState({
        is_modal_visible: false
      });
    }

Lastly, add the styles:

    const styles = {
      container: {
        flex: 1
      },
      loader: {
        paddingTop: 20
      },
      sendLoader: {
        marginRight: 10,
        marginBottom: 10
      },
      customActionsContainer: {
        flexDirection: "row",
        justifyContent: "space-between"
      },
      buttonContainer: {
        padding: 10
      },
      modal: {
        flex: 1
      },
      close: {
        alignSelf: 'flex-end',
        marginBottom: 10
      }
    }

    export default Chat;

AudioPlayer component

Now that we’ve added the code for the Chat screen, it’s time to add the code for the audio player and video player. Let’s start with the audio player first. As you’ve seen in the gif earlier, this will simply show a play/pause button and a progress bar:

    // app/components/AudioPlayer.js
    import React, { Component } from 'react';
    import { View, Text, TouchableOpacity, Animated } from 'react-native';
    import Icon from 'react-native-vector-icons/FontAwesome';
    import { Player } from 'react-native-audio-toolkit'; // for playing audio

Next, create the component. We will use animations for the progress bar:

    class AudioPlayer extends Component {

      constructor(props) {
        super(props);
        this.progress = new Animated.Value(0);
        this.state = {
          progress: 0,
          icon: 'play' // default button to show
        };
        this.player = null; // the audio player
      }

      // next: add componentDidMount()
    }

If you want to learn more about animations in React Native, I recommend you to read my tutorial on it.

Next, add componentDidMount. This is where we initialize the audio player so it’s immediately preloaded the moment the audio_url becomes available. This means the user doesn’t have to wait for a few seconds for the audio to start after they click the play button:

    componentDidMount() {
      this.audio_url = this.props.url;

      this.player = new Player(this.audio_url);
      this.player.prepare((err) => {
        if (this.player.isPrepared) {
          this.player_duration = this.player.duration; // duration of the audio file in milliseconds
        }
      });

      this.player.on('ended', () => {
        this.player = null;
        this.setState({
          icon: 'play'
        });
      });
    }

    // next: add render()

Next, add the render method. The whole component is a touchable component (more target area is always better for usability), pressing it will toggle the player between pause and play:

    render() {
      return (
        <TouchableOpacity onPress={this.toggleAudioPlayer}>
          <View style={styles.container}>
            <Icon name={this.state.icon} size={15} color={"#FFF"} />
            <View style={styles.rail}>
              <Animated.View
                style={[this.getProgressStyles()]}
              >
              </Animated.View>
            </View>
          </View>
        </TouchableOpacity>
      );
    }

    // next: add getProgressStyles

The getProgressStyles function is used for returning dynamic styles for the actual progress of the audio file (this.progress). The progress can be anywhere between 0 and 100 at any given point in time. The higher the progress value, the longer the width of the progress bar:

    getProgressStyles = () => {
      const animated_width = this.progress.interpolate({
        inputRange: [0, 50, 100],
        outputRange: [0, 50, 100]
      });

      return {
        width: animated_width,
        backgroundColor: '#f1a91b',
        height: 5
      }
    }

    // next: add toggleAudioPlayer

The toggleAudioPlayer function is used for toggling the player between pause and play. This uses the play and pause methods from React Native Audio Toolkit. Aside from that, we’re also toggling between the play and pause icons depending on whether the player is currently playing the audio or not. The progress bar animation is also paused when the player is paused, this is how we keep the audio and UI in synced across pauses:

    toggleAudioPlayer = () => {
      if (this.player.isPlaying) {
        this.setState({
          icon: 'play'
        });
        this.player.pause(); // pause the audio

        // pause the progress bar animation
        this.progress.stopAnimation((value) => { // returns the current value of this.progress (between 0 to 100) at the moment the animation was stopped
          this.player_duration = this.player.duration - (this.player.duration * (Math.ceil(value) / 100)); // get the remaining duration after the final animation value has been deducted
        });
      } else {
        this.setState({
          icon: 'pause'
        });
        this.player.play(); // play the audio

        // start the animation
        Animated.timing(this.progress, {
          duration: this.player_duration,
          toValue: 100
        }).start();

      }
    }

If you’re wondering how the calculation for the remaining duration works, remember that this.player_duration is the total remaining duration in milliseconds. We’ve set this to default to the audio file’s total duration. On the other hand, the value returned from stopAnimation is the current value of this.progress when the animation was stopped. This can be a value between 0 and 100, so it actually represents the actual percentage of the animation when it was stopped. This means that we can simply use a percentage formula to figure out the completed milliseconds based on the value that was returned.

Lastly, add the styles:

    const styles = {
      container: {
        flexDirection: 'row',
        alignItems: 'center',
        margin: 10
      },
      rail: {
        width: 100,
        height: 5,
        marginLeft: 5,
        backgroundColor: '#2C2C2C'
      }
    }

    export default AudioPlayer;

VideoPlayer component

We’re using the VideoPlayer component to play a video attachment. Just like the audio component, it has a play/pause button and a progress bar:

    // app/components/VideoPlayer.js
    import React, { Component } from 'react';

    import {
      TouchableOpacity,
      View
    } from 'react-native';

    import Video from 'react-native-video';
    import Icon from 'react-native-vector-icons/FontAwesome';

Create the component and declare the default settings for React Native Video:

    class VideoPlayer extends Component {

      state = {
        rate: 1,
        volume: 1,
        muted: false,
        resizeMode: 'contain',
        duration: 0.0,
        currentTime: 0.0,
        paused: true
      };

      // next: add render()
    }

Inside the render function, we’re using a function called getCurrentTimePercentage to get the progress of the video as it is being played. This returns the flex value to be used for styling the progress bar. This is an alternative approach to what we did with the AudioPlayer component earlier:

    render() {
      const flexCompleted = this.getCurrentTimePercentage() * 100;
      const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
      const icon = (this.state.paused) ? 'play' : 'pause';

      const { uri } = this.props;

      return (
        <View style={styles.container}>
          <TouchableOpacity
            style={styles.fullScreen}
            onPress={() => this.setState({ paused: !this.state.paused })}
          >
            <Video
              ref={(ref: Video) => { this.video = ref }}
              source={{ uri }}
              style={styles.fullScreen}
              paused={this.state.paused}
              resizeMode={'contain'}
              onLoad={this.onLoad}
              onProgress={this.onProgress}
              onEnd={this.onEnd}
              repeat={false}
            />
          </TouchableOpacity>

          <View style={styles.controls}>
            <Icon name={icon} size={20} color={"#FFF"}  />
            <View style={styles.progress}>
              <View style={[styles.innerProgressCompleted, { flex: flexCompleted }]} />
              <View style={[styles.innerProgressRemaining, { flex: flexRemaining }]} />
            </View>
          </View>
        </View>
      );
    }

    // next: add onLoad()

The onLoad function is executed when the video is loaded and is ready to be played. Here, we set the total duration of the video:

    onLoad = (data) => {
      this.setState({ duration: data.duration });
    }

The onProgress function is executed while the video is playing. This works hand in hand with the getCurrentTimePercentage to implement the progress bar for the video:

    onProgress = (data) => {
      this.setState({ currentTime: data.currentTime });
    }

This is the one thing that the React Native Audio Toolkit didn’t provide. It’s the main reason why we used animations for the progress bar for the AudioPlayer component.

The onEnd function is executed when a video reaches its end. At this point, we pause the video and reset the progress bar back to zero:

    onEnd = () => {
      this.setState({ paused: true });
      this.video.seek(0);
    }

Here’s the function for getting the flex value for the progress bar:

    getCurrentTimePercentage() {
      if (this.state.currentTime > 0) {
        return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
      }
      return 0;
    };

Here are the styles:

    const styles = {
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#000'
      },
      fullScreen: {
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
      },
      controls: {
        flexDirection: 'row',
        backgroundColor: 'transparent',
        borderRadius: 5,
        position: 'absolute',
        bottom: 20,
        left: 20,
        right: 20,
        alignItems: 'center'
      },
      progress: {
        flex: 1,
        flexDirection: 'row',
        borderRadius: 3,
        overflow: 'hidden',
        marginLeft: 10
      },
      innerProgressCompleted: {
        height: 10,
        backgroundColor: '#f1a91b',
      },
      innerProgressRemaining: {
        height: 10,
        backgroundColor: '#2C2C2C',
      }
    }

    export default VideoPlayer;

ChatBubble component

The ChatBubble component is used as a container for the AudioPlayer:

    // app/components/ChatBubble.js

    import React from 'react';
    import { View } from 'react-native';
    import { MessageText, Time } from 'react-native-gifted-chat';

    const ChatBubble = (props) => {
      const { position, children, currentMessage, uri } = props;
      return (
        <View style={styles[position].container}>
          <View style={styles[position].wrapper}>
            <MessageText {...props} />
            {children}
            <Time {...props} />
          </View>
        </View>
      );
    }

You can also use the ChatBubble component to create a custom preview for video attachments.

Lastly, here are the styles:

    const styles = {
      left: {
        container: {
          flex: 1,
          alignItems: 'flex-start',
        },
        wrapper: {
          borderRadius: 15,
          backgroundColor: '#f0f0f0',
          marginRight: 60,
          minHeight: 20,
          justifyContent: 'flex-end',
        }
      },
      right: {
        container: {
          flex: 1,
          alignItems: 'flex-end',
        },
        wrapper: {
          borderRadius: 15,
          backgroundColor: '#0084ff',
          marginLeft: 60,
          minHeight: 20,
          justifyContent: 'flex-end',
        }
      }
    }

    export default ChatBubble;

Running the app

Now it’s time to run the app:

    cd server
    yarn start
    ./ngrok http 5000

Next, update your app/screens/Login.js and app/screens/Chat.js file with your ngrok HTTPS URL and then run the app:

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

Don’t forget to delete the node_modules/react-native-gifted-chat/node_modules/react-native-video if you haven’t done so already.

Conclusion and next steps

That’s it! In this tutorial, we built a chat app with React Native and Chatkit that can play audio and video files. Building the app was easy, thanks to the help of the various packages.

What we’ve built is pretty limited in scope though, so here are a few ideas if you want to develop the app further:

  • Add support for viewing image files - I’ve previously written a tutorial which has implemented this feature (Create a file-sharing app with React Native) so I didn’t include it in here. React Native Gifted Chat already has a feature for showing images so all you have to do is use it.
  • Add support for other media file types - For other video and audio file types, you most likely have to stick with mp4 and mp3 as those are the most common formats. So if you want to add support for other file types, you need to create a server for uploading the files and convert it from there.
  • Add support for document viewing - React Native PDF and React Native Doc Viewer are two packages which really looks promising.
  • Cache files locally so they can be viewed offline - you can use the React Native Fetch Blob package to implement this.
  • Add audio and video recording feature - for audio recording, the React Native Audio Toolkit package already has you covered. All you have to do is create the UI for recording. For video recording, React Native Camera comes with the option to record videos.

You can find the project’s code on this GitHub repo.

Clone the project repository
  • Android
  • Chat
  • chatroom
  • iOS
  • JavaScript
  • Node.js
  • 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.