Message Status Indicators

The Stream Chat SDK for React Native provides a built-in way to display message status indicators to users. This feature enhances user experience by allowing users to see the status of the messages they are sending.

Message status indicators

In this cookbook, we will show you how to use the built-in message status indicators to display the status of the messages they are sending.

Status indicators states

The possible states of the status indicators are:

  • sending: The message is pending to be sent to the server. It shows a timer icon.
  • received: The message has been received by the server successfully. It is shown as a gray checkmark.
  • delivered: The message has been delivered to at least one of the channel members devices. It is shown as double gray checkmark.
  • read: The message has been read by at least one of the channel members. It is shown as double blue checkmark.

The delivered state is only available since version 8.8.0 and it needs to be enabled in the Dashboard for each channel type by enabling the Delivery Events flag/switch.

The read_events flag needs to be enabled in the Dashboard for each channel type by enabling the Read Events flag/switch.

PendingSentDeliveredReadRead by many
Message Status Pending
Message Status Sent
Message Status Delivered
Message Status Read
Message Status Read Group

Basic Customization

Hide status indicators

In case your app does not need to show the status indicators, you can hide them by overriding the MessageStatus component and passing it to the MessageStatus prop of the Channel component.

<Channel MessageStatus={() => null}>
  <MessageList />
</Channel>

Show all read and delivered members

Message Delivery State Read and Delivered

In this example, we will show you how to create a custom bottom sheet that shows the members that have read the message and the ones that were delivered but not read yet.

  1. First, create the bottom sheet component that will be used to show the members that have read the message and the ones that were delivered but not read yet.
import React, { useMemo } from "react";
import BottomSheet, { BottomSheetFlatList } from "@gorhom/bottom-sheet";
import { BottomSheetView } from "@gorhom/bottom-sheet";
import {
  Avatar,
  useChatContext,
  useMessageDeliveredData,
  useMessageReadData,
  useTheme,
} from "stream-chat-react-native";
import { LocalMessage, UserResponse } from "stream-chat";
import { StyleSheet, Text, View } from "react-native";

const renderUserItem = ({ item }: { item: UserResponse }) => (
  <View style={styles.userItem}>
    <Avatar image={item.image} name={item.name ?? item.id} size={32} />
    <Text style={styles.userName}>{item.name ?? item.id}</Text>
  </View>
);

const renderEmptyText = ({ text }: { text: string }) => (
  <Text style={styles.emptyText}>{text}</Text>
);

export const MessageInfoBottomSheet = ({
  message,
  ref,
}: {
  message?: LocalMessage;
  ref: React.RefObject<BottomSheet | null>;
}) => {
  const {
    theme: { colors },
  } = useTheme();
  const { client } = useChatContext();
  const deliveredStatus = useMessageDeliveredData({ message });
  const readStatus = useMessageReadData({ message });

  const otherDeliveredToUsers = useMemo(() => {
    return deliveredStatus.filter(
      (user: UserResponse) => user.id !== client?.user?.id,
    );
  }, [deliveredStatus, client?.user?.id]);

  const otherReadUsers = useMemo(() => {
    return readStatus.filter(
      (user: UserResponse) => user.id !== client?.user?.id,
    );
  }, [readStatus, client?.user?.id]);

  return (
    <BottomSheet enablePanDownToClose ref={ref} index={-1} snapPoints={["50%"]}>
      <BottomSheetView
        style={[styles.container, { backgroundColor: colors.white_smoke }]}
      >
        <Text style={styles.title}>Read</Text>
        <BottomSheetFlatList
          data={otherReadUsers}
          renderItem={renderUserItem}
          keyExtractor={(item) => item.id}
          style={styles.flatList}
          ListEmptyComponent={renderEmptyText({
            text: "No one has read this message.",
          })}
        />
        <Text style={styles.title}>Delivered</Text>
        <BottomSheetFlatList
          data={otherDeliveredToUsers}
          renderItem={renderUserItem}
          keyExtractor={(item) => item.id}
          style={styles.flatList}
          ListEmptyComponent={renderEmptyText({
            text: "The message was not delivered to anyone.",
          })}
        />
      </BottomSheetView>
    </BottomSheet>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    justifyContent: "center",
    height: "100%",
  },
  title: {
    fontSize: 16,
    fontWeight: "bold",
    marginVertical: 8,
  },
  flatList: {
    borderRadius: 16,
  },
  userItem: {
    flexDirection: "row",
    alignItems: "center",
    padding: 8,
    backgroundColor: "white",
  },
  userName: {
    fontSize: 16,
    fontWeight: "bold",
    marginLeft: 16,
  },
  emptyText: {
    fontSize: 16,
    marginVertical: 16,
    textAlign: "center",
  },
});
  1. Then, you can open this bottom sheet by a custom message action that can be added as per the guide Custom Message Actions.

Mark as Delivered on Background push notification on Android

In this example, we will show you how to mark a message as delivered on background push notification on Android.

Using the setBackgroundMessageHandler method, you can mark a message as delivered on background push notification on Android.

An example of the above can be found below:

bootstrapBackgroundMessageHandler.ts
import {
  FirebaseMessagingTypes,
  setBackgroundMessageHandler,
} from '@react-native-firebase/messaging';

const displayNotification = async (
  remoteMessage: FirebaseMessagingTypes.RemoteMessage,
  channelId: string,
) => {
  const { stream, ...rest } = remoteMessage.data ?? {};
  const data = {
    ...rest,
    ...((stream as unknown as Record<string, string> | undefined) ?? {}), // extract and merge stream object if present
  };
  if (data.body && data.title) {
    await notifee.displayNotification({
      android: {
        channelId,
        pressAction: {
          id: 'default',
        },
      },
      body: data.body as string,
      title: data.title as string,
      data,
    });
  }
};

setBackgroundMessageHandler(messaging, async (remoteMessage) => {
  try {
    const loginConfig = // get the login config from the storage
    if (!loginConfig) {
      return;
    }
    const chatClient = StreamChat.getInstance(loginConfig.apiKey);
    await chatClient._setToken(
      { id: loginConfig.userId },
      loginConfig.userToken,
    );

    const notification = remoteMessage.data;

    const deliverMessageConfirmation = [
      {
        cid: notification?.cid,
        id: notification?.id,
      },
    ];

    await chatClient?.markChannelsDelivered({
      latest_delivered_messages:
        deliverMessageConfirmation as DeliveredMessageConfirmation[],
    });
    // create the android channel to send the notification to
    const channelId = await notifee.createChannel({
      id: "chat-messages",
      name: "Chat Messages",
    });
    // display the notification
    await displayNotification(remoteMessage, channelId);
  } catch (error) {
    console.error(error);
  }
});

Now, import the bootstrapBackgroundMessageHandler.ts file in the index.ts on the top of the file before the App component.

index.ts
import "./bootstrapBackgroundMessageHandler";

Now, you can test the above by sending a push notification to the app.

© Getstream.io, Inc. All Rights Reserved.