Building a Minimal Chat App with React Native and Expo Managed Workflow

Mohsin AliMohsin Ali | Mar 2023
Cover image for Building a Minimal Chat App with React Native and Expo Managed Workflow

Introduction

In this tutorial, we will build a minimal chat app using React Native and Expo managed workflow. We'll use Firebase Firestore as our backend to handle real-time data synchronization.

Step-by-Step Guide

1. Setting Up the Project

First, set up a new Expo project and install the necessary dependencies:

expo init ChatApp
cd ChatApp
expo install firebase
expo install @react-native-async-storage/async-storage

2. Configuring Firebase

Create a Firebase project at Firebase Console. Add a new web app and get the Firebase configuration details.

Create a firebaseConfig.js file to initialize Firebase:

// firebaseConfig.js
import firebase from 'firebase/app';
import 'firebase/firestore';

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
  appId: 'YOUR_APP_ID',
};

if (!firebase.apps.length) {
  firebase.initializeApp(firebaseConfig);
}

const db = firebase.firestore();
export { db };

3. Creating the Chat Interface

Create a ChatScreen.js component to display and send messages:

// ChatScreen.js
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import { db } from './firebaseConfig';
import AsyncStorage from '@react-native-async-storage/async-storage';

const ChatScreen = () => {
  const [message, setMessage] = useState('');
  const [messages, setMessages] = useState([]);
  const [username, setUsername] = useState('');

  useEffect(() => {
    (async () => {
      const storedUsername = await AsyncStorage.getItem('username');
      if (storedUsername) {
        setUsername(storedUsername);
      } else {
        const newUsername = `User${Math.floor(Math.random() * 1000)}`;
        await AsyncStorage.setItem('username', newUsername);
        setUsername(newUsername);
      }
    })();

    const unsubscribe = db.collection('messages').orderBy('timestamp', 'desc').onSnapshot((snapshot) => {
      setMessages(snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })));
    });

    return () => unsubscribe();
  }, []);

  const sendMessage = async () => {
    if (message.trim().length > 0) {
      await db.collection('messages').add({
        text: message,
        username,
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),
      });
      setMessage('');
    }
  };

  return (
    <View style={styles.container}>
      <FlatList
        data={messages}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={styles.messageContainer}>
            <Text style={styles.username}>{item.username}:</Text>
            <Text style={styles.message}>{item.text}</Text>
          </View>
        )}
        inverted
      />
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={message}
          onChangeText={setMessage}
          placeholder="Type a message"
        />
        <Button title="Send" onPress={sendMessage} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
    backgroundColor: '#fff',
  },
  messageContainer: {
    flexDirection: 'row',
    marginBottom: 10,
  },
  username: {
    fontWeight: 'bold',
    marginRight: 5,
  },
  message: {
    flexShrink: 1,
  },
  inputContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    borderTopWidth: 1,
    borderColor: '#ddd',
    paddingTop: 10,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 5,
    padding: 10,
    marginRight: 10,
  },
});

export default ChatScreen;

4. Integrating the ChatScreen in the App

Update App.js to use the ChatScreen component:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import ChatScreen from './ChatScreen';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Chat" component={ChatScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Explanation

  • Firebase Configuration: Set up Firebase and initialize Firestore.
  • Chat Interface: The ChatScreen component handles displaying and sending messages.
  • Real-time Updates: Use Firestore's snapshot listener to get real-time updates.
  • Unique Usernames: Generate and store unique usernames using AsyncStorage.

Conclusion

This tutorial shows you how to build a minimal chat app using React Native, Expo, and Firebase. This simple yet functional app can be a starting point for more advanced features like authentication, media messages, and more complex chat functionalities.