DEV Community

Colin Hale
Colin Hale

Posted on

Learning Firebase and React-Native: Private Message App

In this tutorial I show you what I learned along my journey to test out making a simple test application creating a private messaging app. I won't be covering the firebase authentication process found in the app. That was covered in a previous post here: https://dev.to/colinah/learning-firebase-and-react-native-authentication-2822.

While thinking of ways to build this out, I wanted to make the private chat system flexible so if I wanted to change that chat into a group chat the architecture would already be set up. To do this there was a couple of things to consider. Firstly how we are going to store the data for the messages, what data we need and how to have users tied to that data.

To store the messages I decided to go with a Firebase collection named rooms, that would have a unique key generated by the combination of the two users id's. Then the room document would contain the following properties, color, lastMessage, messages, and users. Where color is a string hex code for a color to assign the group, lastMessage is the last message that was sent in the chat to display on the list of chats screen, messages is an array of messages in the room, and lastly users is the an array of people in the room. Here is an example of a rooms document.

Image description

We also need a way for each user to know what chats/rooms they have already started to display in the messages screen. I decided to store the room id's in the user object in an array.

Image description

Now that we know what the firebase data base will look like with this project let's get started on the React-Native application.

There are three main screens for the chat portion of the application, chats, messages and newChat. The chats screen displays a list of the rooms the user is in, the messages screen displays the messages sent between the two users, and lastly the newChat screen allows the user to search for users to start a chat with.

Let's start with building the chat screen. We want to subscribe to the users firebase user object that way the rooms will automatically update if there is a change. Here is what the Chats Component looks like the ChatGroup component is a simple display showing the name of the people in the chat and the last message.

export default function Chats() {
  const [rooms, setRooms] = React.useState([]);
  const { user } = useContext(UserContext);
  const router = useRouter();

  React.useEffect(() => {
    const userRef = doc(firestore, 'users', user.uid);

    const unSub = onSnapshot(userRef, (doc) => {
      doc.exists() && setRooms(doc.data().rooms);
    });

    return () => {
      unSub();
    };
  }, []);

  return (
    <View style={styles.container}>
      <ScrollView>
        {rooms ? (
          rooms.map((room) => <ChatGroup roomId={room} key={room} />)
        ) : (
          <View
            style={{
              padding: 6,
              borderRadius: 6,
              borderWidth: 1,
              borderColor: '#ffffff60',
              margin: 20,
            }}>
            <Text>You aren't in any rooms. Press the "+" to start a chat.</Text>
          </View>
        )}
      </ScrollView>
      <FAB icon="plus" style={styles.fab} onPress={() => router.push('/(chat)/newChat')} />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

The next screen is the messages screen. To add a bit more functionality easier I decided to go with the react-native-gifted-chat package which handles displaying the messages. To get the messages we need to subscribe to the rooms document the same way we did for the user object. This will keep the messages up to date.

export default function Messages() {
  const [messages, setMessages] = React.useState<IMessage[]>([]);
  const { user } = React.useContext(UserContext);
  const { id } = useSearchParams();

  React.useEffect(() => {
    const roomRef = doc(firestore, 'rooms', id as string);

    const unSub = onSnapshot(roomRef, (doc) => {
      doc.exists() &&
        setMessages(
          doc.data().messages.map((message: any) => ({
            ...message,
            createdAt: message.createdAt.toDate(),
          }))
        );
    });

    return () => {
      unSub();
    };
  }, [id]);

  const onSend = async (messages: IMessage[]) => {
    const roomRef = doc(firestore, 'rooms', id as string);
    await updateDoc(roomRef, {
      messages: arrayUnion(messages[0]),
      lastMessage: messages[0],
    });
  };

  return (
    <View style={styles.container}>
      <Stack.Screen
        options={{
          title: 'Messages',
        }}
      />
      <GiftedChat
        renderBubble={ThemedBubble}
        renderInputToolbar={ThemedInputToolbar}
        minInputToolbarHeight={60}
        messages={messages.reverse()}
        showAvatarForEveryMessage
        onSend={(messages) => onSend(messages)}
        user={{
          _id: user ? user.uid : 'Not logged it',
          name: user ? (user.displayName as string) : 'No Display Name',
          avatar: user?.photoURL ? user.photoURL : '',
        }}
      />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Lastly we need a way to start new chats with users, this is handled in the newChat screen. To do this we can just have a search input that connects to the users collection and searches for users by name.

export default function NewChat() {
  const [search, setSearch] = React.useState('');
  const [results, setResults] = React.useState<UserInfo[]>([]);

  const onSearchHandler = async (text: string) => {
    setSearch(text);
    const q = query(collection(firestore, 'users'), where('displayName', '==', text.toLowerCase()));

    try {
      const querySnapshot = await getDocs(q);

      const list = [] as UserInfo[];
      querySnapshot.forEach((doc) => list.push(doc.data() as UserInfo));
      setResults(list);
    } catch (err) {
      console.log('error', err);
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <TextInput value={search} onChangeText={onSearchHandler} />
      {results.map((user) => (
        <SearchResult username={user.displayName} email={user.email} id={user.uid} key={user.uid} />
      ))}
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

That is the basic framework for making a private messaging app using react native and firebase. If you want to look at the entire project you can find it here at https://github.com/codingcruxes/react-native-firebase-chat.

Hope you enjoyed following along and where able to learn as much as I did along this process.

Top comments (0)