C1CTech

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

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.

react-native init FriendlyChatApp

“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

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.

Fetch 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.

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.

firebase init

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

Add Libraries to Functions Directory

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.

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

npm install --save cors
npm install --save uuid-v4
cd..

firebase deploy 

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 :

projectId :

Sending Image to storeImage Cloud Function

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

 

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;
Exit mobile version