ReactNative Working With Firebase Using ChatApp Example

Firebase

A fully managed platform for building iOS, Android, and web apps that provides automatic data synchronization, authentication services, messaging, file storage, analytics, and more.

In this tutorial, I will demonstrate you how we will store our react native application related data in firebase providing services like database,storage etc.Here we will create FriendlyChatApp and its text data i.e messages we will store  in firebase realtime database and captured and picked images in firebase storage.

Get GITHUB code from Here.

Creating Project in Firebase

  • Go to firebase.google.com and create a new account if not exist or use the existing one and go to console.
  • Now to create a new firebase project click on Add project.
  • Enter project name , select country name and click on Create project button.

Building a FriendlyChatApp with React Native

Set Up the Initial Project

After Firebase (the backend part of our application) is set up, it’s time to work on the frontend.

  • First, initialize the project with the following command:
react-native init FriendlyChatApp
  • Next, install the libraries that are required for the project:

“react-native-image-picker”: allows you select a photo/video from the device library or directly from the camera.You can install and configure project from here.

“react-native-vector-icons”: using which  we can use built_in icons for different platforms(android,windows,ios).You can install and configure project from here.

“react-redux”: official React bindings for Redux

“redux”: a state management library for JavaScript applications

“redux-thunk”: middleware that returns functions of actions

npm install --save redux react-redux redux-thunk
  • To make sure that the application works properly, For the Android application, run this command:

Set Up the Redux

ActionTypes.js defines the action types, on the occurance of which redux state changes.

actionTypes.js

export const SET_MESSAGES = 'SET_MESSAGES';
export const UI_START_LOADING = 'UI_START_LOADING';
export const UI_STOP_LOADING = 'UI_STOP_LOADING';

SET_MESSAGES : In this action we will set the array of objects we get from firebase database in redux state.
UI_START_LOADING : In this action we will display the activityIndicator in our app.
UI_STOP_LOADING : In this action the activityIndicator get disappear.

In this app we will create two reducers friendlyMessage , ui.

friendlyMessage.js

import {
  SET_MESSAGES
} from "../actions/actionTypes";

const initialState = {

  messages :[]

};

const reducer = (state = initialState, action) => {
  switch (action.type) {

    case SET_MESSAGES:
      return {
        ...state,
        messages: action.messages,

      };
    default:
      return state;
  }
};

export default reducer;

ui.js

import { UI_START_LOADING, UI_STOP_LOADING } from "../actions/actionTypes";
const initialState = {
isLoading: false
};

const reducer = (state = initialState, action) => {
switch (action.type) {
case UI_START_LOADING:
return {
...state,
isLoading: true
};
case UI_STOP_LOADING:
return {
...state,
isLoading: false
};
default:
return state;
}
};

export default reducer;

After creating the reducer we will combine the reducers and createStore and then integrate our app with redux store.

configureStore.js

import { createStore, combineReducers, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";

import MessageReducer from "./reducers/friendlyMessage";
import uiReducer from "./reducers/ui";

const rootReducer = combineReducers({
  message: MessageReducer,
  ui: uiReducer,
});

let composeEnhancers = compose;

if (__DEV__) {
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}

const configureStore = () => {
  return createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
};

export default configureStore;

index.js

import React from 'react';
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import App from './App';
import configureStore from './src/configureStore';

const store = configureStore();

const RNRedux = () => (
    <Provider store={store}>
        <App />
    </Provider>
);

AppRegistry.registerComponent('FriendlyChatApp', () => RNRedux);

Storing Messages in Firebase Database

Set Rules to public

After opening your firebase project go to Database click on Rules and after setting rules as public as shown below click on publish.

React Native provides the Fetch API for your networking needs.In order to send data into the database go to firebase database and just  copy the URL  as shown below and pass it in fetch() as first argument.

databaseFetch also takes an optional second argument that allows you to customize the HTTP request.Here we will pass the second argument as object which contains two properties :

method: “POST” : specifies that we want to send data at this url.

body: JSON.stringify(messageData) : specifies what to send at this url.When sending data to a server, the data has to be a string and JSON.stringify() converts a JavaScript object into a string.

export const sendMessage = (message, userName) => {

     return dispatch => {

      dispatch(uiStartLoading());
        const messageData = {
                            message: message,
                            userName: userName

                        };
         fetch("WRITE_YOUR_DATABASE_URL", {
                            method: "POST",
                            body: JSON.stringify(messageData)

                    })
                    .catch(err => {
                        console.log(err);
                        alert("Something went wrong, please try again!");
                      dispatch(uiStopLoading());
                    })
                    .then(res => res.json())
                    .then(parsedRes => {
                      
                        dispatch(uiStopLoading());
                    });
      }
     };

Here catch will throw only NETWORK related errors instead of 4xxx and 5xxx related errors occurs while sending data.If no error occur then fetchApi will return promise. Now we convert the response into JSON using res.json(). res.json() again return a promise that resolves with the result of parsing the body text as JSON.

Now the messages get saved into the database like this :

Storing Images in Firebase Storage

Set Rules to public

After opening your firebase project go to Storage ,click on Rules and after setting rules as public as shown below click on publish.

In the below code we get the image as a base64 string .Although it is a string but technically it is a file contains image data.So we cannot store it in database we have to store it in Storage.

imagePickerHandler = ()=>{
ImagePicker.showImagePicker({title: "Pick an Image", maxWidth: 800, maxHeight: 600}, res => {
      if (res.didCancel) {
        console.log("User cancelled!");
      } else if (res.error) {
        console.log("Error", res.error);
      } else {
        this.setState({
          pickedImage: { uri: res.uri , base64: res.data}
        });

this.props.onImagePicked(
    this.state.pickedImage,
    this.state.userName
    );

      }
    });

};

But the issue is that Storage is accessible through firebase SDK and it has a  javascript SDK .You can use this SDK to send HTTP request and that would work but you cannot access Storage using it.

Now For storage unlike the database has no API we can easily target.But firebase has another cool feature we can use to work around that is Firebase Cloud Functions.Cloud Functions for Firebase lets you automatically run backend code in response to events triggered by HTTP request.What we will do we will send request to some url and when we do this a specific function will run and execute code and there we will write code to accept an image and stored it temporarily at some path then we get the image from that path and stored it in firebase Storage, which(Storage) we can access from within function since that run not on our client but on firebase  sounds a bit complex but it is not, this approach here will work very well with firebase.

Since we don’t want to use the SDK we will write the cloud function.

  • Before writing the cloud function we need the firebase tools which really allows us to write such a function.We install it globally on our machine ,write :
npm install -g firebase-tools

firebase tools is just a cli that makes it easy to put this project under control of firebase and in the end it makes it very convenient to write and deploy cloud function from here.

  • Now write the below command and make sure that you are in your project’s root folder  (i.e FriendlyChatApp):

firebase init

  • Now it will ask you what you want to use , the default is Database but you navigate down to Function press space for selection then click enter.
  • Now it will ask you to select a firebase project from which you want to link your app? ReactAppUsingFirebase
  • what language would you like to use to write cloud functions?select Javascript
  • Do you want to use ESlint to catch probable bugs and enfource style?say No
  • Do you want to install dependencies with npm now?say Yes

Later it will display message  at the  end like Firebase Initialization Complete.Now the functions directory get integrated in your project root folder.

  • Open index.js inside functions directory and create storeImage as a function which we will target later via http request.
  • Here you get a request and response ,we are writing code with node.js that’s the restriction we can’t use other language .If you want to use other language then use your own beckend .
  • The goal of the storeImage function is to extract the image from request and store the image in firebase cloud Storage and then return the response once we are done.

Add Libraries to Functions Directory

  • Firstly navigate into the functions folder and install the external package  @google-cloud/storage :
cd functions

npm install --save  @google-cloud/storage

@google-cloud/storage : We need this package because firebase Storage is based on Google cloud storage and there are some useful SDK we can use to conveniently store data in there.

  • We also need another external package cors.

cors : It is required because it allows access to our function from other origins.

npm install --save cors
  • To write or save this image in  Storage  I will temporarily need to save it cloud function and this will be automatically removed by firebase whenever function execution finished.For this we need another constant fs (file service) .fs is a default node.js package ,no need to install.
  • To generate a unique Id for each image install:
npm install --save uuid-v4
  • Now the storeImage function is ready ,move back to the root directory and deploy your function by writing below line :
cd..

firebase deploy 
  • Firebase deploy will give you the url of cloud function i.e storeImage.

index.js

const functions = require('firebase-functions');
const cors = require("cors")({ origin: true });
const fs = require("fs");
const UUID = require("uuid-v4");
//how you get your firebase projectId and keyFilename I have explained it below.
const gcconfig = {
  projectId: "YOUR_FIREBASE_PROJECT_ID",
  keyFilename: "YOUR_FIREBASE_PROJECT_GENERATED_KEY_FILE"
};

const gcs = require("@google-cloud/storage")(gcconfig);


exports.storeImage = functions.https.onRequest((request, response) => {
 return cors(request, response, () => {
    const body = JSON.parse(request.body);
    fs.writeFileSync("/tmp/uploaded-image.jpg", body.image, "base64", err => {
      console.log(err);
      return response.status(500).json({ error: err });
    });
    const bucket = gcs.bucket("WRITE_YOUR_FIREBASE_PROJECT_STORAGE_URL");
    const uuid = UUID();

    return bucket.upload(
      "/tmp/uploaded-image.jpg",
      {
        uploadType: "media",
        destination: "/images/" + uuid + ".jpg",
        metadata: {
          metadata: {
            contentType: "image/jpeg",
            firebaseStorageDownloadTokens: uuid
          }
        }
      },
      (err, file) => {
        if (!err) {
          return response.status(201).json({
            imageUrl:
              "https://firebasestorage.googleapis.com/v0/b/" +
              bucket.name +
              "/o/" +
              encodeURIComponent(file.name) +
              "?alt=media&token=" +
              uuid
          });
        } else {
          console.log(err);
          return response.status(500).json({ error: err });
        }
      }
    );
  });
 });

keyFilename :

  • Open the firebase project click on the gear icon at the top and select Project Settings.
  • Choose service accounts and there you leave node.js checked and click on Generate new private key and
    then click Generate key.
  • Now download the file and store it in the functions folder of your root directory.

projectId :

  • Open the firebase project click on the gear icon at the top and select Project Settings.
  • By default General get open now from here you copy project id and paste it in projectId property.

Sending Image to storeImage Cloud Function

  • Inside imagePicked actionCreator we will send the captured image to the deployed storeImage cloud function and inside the storeImage function we have write the code which accepts the image and stored it in firebase Storage.
  • Now we get the imageUrl of the captured image inside pickedImage property and stored it into the database .
export const imagePicked = (pickedImage, userName) => { 
return dispatch => {
 dispatch(uiStartLoading());
fetch("WRITE_THE_URL_YOU_GET_FROM_FIREBASE_DEPLOY", {
                method: "POST",
                body: JSON.stringify({
                    image: pickedImage.base64
                })
            })
            .catch(err => {
                console.log(err);
                alert("Something went wrong, please try again!");
                dispatch(uiStopLoading());
            })
            .then(res => res.json())
            .then(parsedRes => {
                const ImageData = {

                    userName: userName,
                    pickedImage: parsedRes.imageUrl


               };

               return fetch("WRITE_YOUR_DATABASE_URL", {
                    method: "POST",
                    body: JSON.stringify(ImageData)
                })
            })
            .catch(err => {
                console.log(err);
                alert("Something went wrong, please try again!");
                dispatch(uiStopLoading());
            })
            .then(res => res.json())
            .then(parsedRes => {
                console.log(parsedRes);
                dispatch(getMessages());
               dispatch(uiStopLoading());
            });
};
};

Firebase Functions

Firebase Functions

Firebase Storage

Firebase Storage

Firebase Database

Firebase Database

 

  • Here getMessages actionCreator get all the data from the firebase database .It passes the array of data to another actionCreator setMessages  and then we will stored it in redux.
export const getMessages = () =>
{
  return dispatch => {
  dispatch(uiStartLoading());
     return fetch(
          "WRITE_YOUR_DATABASE_URL"

        )
       .catch(err => {
                                console.log(err);
                                alert("Something went wrong, please try again!");
                               dispatch(uiStopLoading());
                            })
                            .then(res => res.json())
                            .then(parsedRes => {

                                const messages = [];
                                        for (let key in parsedRes) {
                                          messages.push({
                                            ...parsedRes[key],

                                            key: key
                                          });

                                          }

                                        dispatch(setMessages(messages));
                                dispatch(uiStopLoading());
                            }).catch(err => {
                                                                    alert("Something went wrong, sorry :/");
                                                                     console.log(err);
                                                                     dispatch(uiStopLoading());
                                                                   });

    };
    };


export const setMessages = messages => {
  return {
    type: SET_MESSAGES,
    messages: messages
  };
};

When you run the app it will fetch the data from the database and it look like this :

Complete code :

App.js

import React, { Component } from 'react';
import {
  StyleSheet,
  TextInput,
  Button,
  TouchableOpacity,
  ActivityIndicator,
  View
  } from 'react-native';
import { connect } from "react-redux";
import Icon from 'react-native-vector-icons/Ionicons';
import ImagePicker from "react-native-image-picker";
import validate from "./src/validation";
import { sendMessage,getMessages,imagePicked } from "./src/actions/index";
import MessageList from "./src/components/MessageList/MessageList";

 class App extends Component {

constructor(props) {
    super(props);
    this.props.onLoadMessages();
  }

  state = {

        pickedImage : null,
        message : "",
        userName :"Arun Kumar",
        validText : false

      };

imagePickerHandler = ()=>{
ImagePicker.showImagePicker({title: "Pick an Image", maxWidth: 800, maxHeight: 600}, res => {
      if (res.didCancel) {
        console.log("User cancelled!");
      } else if (res.error) {
        console.log("Error", res.error);
      } else {
        this.setState({
          pickedImage: { uri: res.uri , base64: res.data}
        });

this.props.onImagePicked(
    this.state.pickedImage,
    this.state.userName
    );

      }
    });

};

sendButtonHandler = ()=>
{

if (this.state.message.trim() === "") {
      return;
    }
    else {

    this.props.onSendMessage(
    this.state.message,
    this.state.userName
    );
this.setState({
message: "",
      validText : false
    });

   }
};


textChangedHandler = val=>{

this.setState({
      message: val,
      validText : validate(val)
    });
};

  render() {
let showProgressBar = null;


 if (this.props.isLoading) {
        showProgressBar = (
        <View style={styles.progressBar}>
        <ActivityIndicator size="large" color="#0000ff"/>
        </View>
        );
      }
      else
      {
      showProgressBar = (<MessageList
                         messages={this.props.messages}
                       />
                        );
      }


    return (
      <View style={styles.container}>

      {showProgressBar}

      <View style={styles.innerContainer}>

        <View>
                    <TouchableOpacity onPress={this.imagePickerHandler}>
                      <View >
                        <Icon

                          size={40}
                          name={ "md-image" }
                          color="#841584"
                        />
                      </View>
                    </TouchableOpacity>
        </View>

      <TextInput
      style={styles.textInput}
      onChangeText={this.textChangedHandler}>
      {this.state.message}
      </TextInput>

       <Button
              onPress={this.sendButtonHandler}
              title="Send"
              color="#841584"
              disabled={
                        !this.state.validText
                        }
              />

      </View>

      </View>
    );
  }
}

const styles = StyleSheet.create({
 container: {
    flex: 1

   },
  innerContainer: {

    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'flex-end',
    margin:10

  },
  textInput: {
      width: "70%",
      borderColor: "transparent",
      padding: 8,
    },
    progressBar: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'

      }

});

const mapStateToProps = state => {
  return {
    messages: state.message.messages,
    isLoading: state.ui.isLoading
  };
};

const mapDispatchToProps = dispatch => {
  return {
    onSendMessage: (message, userName) =>
    dispatch(sendMessage(message, userName)),
    onImagePicked: (pickedImage, userName) =>
    dispatch(imagePicked(pickedImage, userName)),
    onLoadMessages: () => dispatch(getMessages())
  };
};

export default connect( mapStateToProps ,mapDispatchToProps)(App);

ListItem.js

import React , { Component }from "react";
import { View, Text, StyleSheet, Image } from "react-native";

class ListItem extends Component {

render(){

let chatMessage = null;
 if (this.props.obj.hasOwnProperty("pickedImage")) {
      chatMessage = (
<View style={styles.innerContainer}>
          <Image  source={{uri: this.props.obj.pickedImage}} style={styles.image} />
              <Text style={styles.userName}>{this.props.obj.userName}</Text>
</View>
      );
    }
    else {
    chatMessage = (
<View style={styles.innerContainer}>
             <Text style={styles.message}>{this.props.obj.message}</Text>
                  <Text style={styles.userName}>{this.props.obj.userName}</Text>
</View>
          );
    }

return(
    <View style={styles.container}>

      {chatMessage}

    </View>

);
}
}

const styles = StyleSheet.create({
  container: {
      width: "100%",
  },
  innerContainer: {
      marginTop: 10,
      padding :10
    },
  image: {
      height: 300,
      width: 300
  },
  userName: {
      fontSize: 16,
    },
  message: {
       fontSize: 22,
       fontWeight: "bold",
       color: 'black'
        }
});

export default ListItem;

MessageList.js

import React from "react";
import { StyleSheet, FlatList } from "react-native";
import ListItem from "../ListItem/ListItem";

const messageList = props => {

return (
<FlatList
style={styles.listContainer}
data={props.messages}
renderItem={(info) => (
<ListItem
obj={info.item}
/>
)}
/>
);
};

const styles = StyleSheet.create({
listContainer: {
width: "100%"
}
});


export default messageList;

Leave a Reply