Skip to content

Cookbook v3.0

Vishal Narkhede edited this page Jul 29, 2021 · 22 revisions

🚨 This wiki has been deprecated and will not be maintained here onwards 🚨

Please refer to our latest documentation on website - https://getstream.io/chat/docs/sdk/reactnative/ 🚀


Appendix

Installation

Add dependencies

Install the required packages in your React Native project:

yarn add stream-chat-react-native

Stream Chat has a number of peer dependencies that are required to take advantage of all of the out of the box features. It is suggested you follow the install instructions for each package to ensure it is properly setup. Most if not all of the required packages now support auto-linking so setup should be minimal.

yarn add @react-native-community/blur @react-native-community/cameraroll @react-native-community/netinfo @stream-io/flat-list-mvcp react-native-document-picker react-native-fs react-native-gesture-handler react-native-haptic-feedback react-native-haptic-feedback react-native-image-crop-picker react-native-image-resizer react-native-reanimated@2.0.0-rc.0 react-native-safe-area-context react-native-share react-native-svg

For iOS on a Mac install the pods npx pod-install ios.

react-native-gesture-handler requires the package to be imported at the top of the entry file before anything else, this is usually your App.js or index.js file.

import 'react-native-gesture-handler';
import { AppRegistry } from 'react-native';

import App from './App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

Additional steps

Following package require some additional installation steps:

Note for Android:

If you are using AndroidX app:

AndroidX is a major step forward in the Android ecosystem, and the old support library artifacts are being deprecated. For 0.60, React Native has been migrated over to AndroidX. This is a breaking change, and your native code and dependencies will need to be migrated as well.

(Reference: https://facebook.github.io/react-native/blog/2019/07/03/version-60#androidx-support)

In current context, dependencies such as react-native-document-picker, react-native-gesture-handler, and react-native-reanimated don't have AndroidX support. But an awesome tool named jetifier is quite useful to patch these dependencies with AndroidX support.

NOTE If you are planning to use file picker functionality, make sure you enable iCloud capability in your app - Enable iCloud capability

Installing dependencies for Expo

Stream Chat React Native is set up for parity on Expo, expo requires a different set of dependencies, in your project directory run:

expo install stream-chat-expo
expo install @react-native-community/netinfo expo-blur expo-document-picker expo-file-system expo-haptics expo-image-manipulator expo-image-picker expo-media-library expo-permissions expo-sharing react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg`

Components

Stream Chat components make extensive use of React Context to maintain state and provide an optimal user experience. To access these contexts your screens, components, or entire app must be wrapped in the Stream Chat Context components.

OverlayProvider

The highest level of these components is the OverlayProvider. The OverlayProvider allows users to interact with messages on long press above the underlying views, use the full screen image viewer, and use the AttachmentPicker as a keyboard-esk view.

Message Interaction Image Viewer Attachment Picker

Because these views must exist above all others OverlayProvider should wrap your navigation stack as well. Assuming React Navigation is being used, your highest level navigation stack should be wrapped in the provider:

<NavigationContainer>
  <OverlayProvider>
    <Stack.Navigator>
      <Stack.Screen />
    </Stack.Navigator>
  </OverlayProvider>
</NavigationContainer>

stream-chat-react-native like stream-chat-js is written in TypeScript with full support for custom object types via provided generics. These generics are given to components as designated in the TypeScript language docs but can appear unusual if you have not used them before. The previous code snippet with all 7 possible generics given to the OverlayProvider would be written as:

<NavigationContainer>
  <OverlayProvider<
    AttachmentType,
    ChannelType,
    CommandType,
    EventType,
    MessageType,
    ResponseType,
    UserType
  >>
    <Stack.Navigator>
      <Stack.Screen />
    </Stack.Navigator>
  </OverlayProvider>
</NavigationContainer>

NOTE: For simplicity in the code snippets not all generics that may be needed are shown. In many cases in practice the generics can be inferred from the provided props. But this is not true in all cases and you may need to provide the proper generics explicitly to ensure type safety and that the TypeScript compiler does not throw an error.

The OverlayProvider can be used with no props provided but there are a plethora of props for customizing the components in the overlay. Three core props that will you will likely want to use are bottomInset, i18nInstance, and value. value is a Partial of the OverlayContextValue. It provides the theme to the components in the overlay and thus if you are using a custom theme you can provide it to the overlay as value={{ style: theme }}. The ThemeProvider inherits from parent contexts and thus the theme will also be provided to the child components used later, such as Chat and Channel; therefore, this can be used as the main theming entry point. i18nInstance is the instance of Streami18n you have for translations. bottomInset is important as it is required to determine the height of the AttachmentPicker and the underlying shift to the MessageList when it is opened. In the example shown, the bottom safe area is and is not taken into account and the resulting UI difference is obvious. This can also be set via the setBottomInset function provided by the useAttachmentPickerContext hook.

const streami18n = new Streami18n({ language: 'en' });
const { bottom } = useSafeAreaInsets();
const theme = useStreamChatTheme();

<OverlayProvider
  bottomInset={bottom}
  i18nInstance={streami18n}
  value={{ style: theme }}
>

Additionally a topInset must be set to ensure that when the picker is completely open it is opened to the desired height. This can be done via props, but can also be set via the setTopInset function provided by the useAttachmentPickerContext hook. The bottom sheet will not render without this height set, but it can be set to 0 to cover the entire screen, or the safe area top inset if desired. In the example it is being set using the useHeaderHeight hook from React Navigation.

IMPORTANT: The current implementation of the scrolling bottom-sheet in which the image picker resides does not re-evaluate heights after the topInset is set. So only set this to one value.

const headerHeight = useHeaderHeight();
const { setTopInset } = useAttachmentPickerContext();

useEffect(() => {
  setTopInset(headerHeight);
}, [headerHeight]);
With bottomInset Without bottomInset With topInset

The OverlayProvider contains five providers to which you can add customizations and retrieve data using the appropriate hooks: TranslationProvider, OverlayContext.Prover, MessageOverlayProvider, AttachmentPickerProvider, and ImageGalleryProvider

NOTE: As mentioned there are many modifications that can be performed to the UI. Custom styling via the theme gives you the ability to shape the look of the application as a whole and/or implement dark mode. But additionally the majority of the UI can be modified or replaced via Stream Chat settings or props. It is trivial to replace or modify most UI elements.

No Reactions or Replies Custom Header and Footer Custom Grid Layout

Chat

Chat is the next level down of context component from OverlyProvider that is required for stream-chat-react-native to function as designed. You can choose to wrap your entire application in Chat similar to what is required for the OverlayProvider or you can implement Chat at the screen level. Chat takes two important props, client and i18nInstance. The client should be an instance of StreamChat from stream-chat configured for your app, and i18nInstance should be an instance of Streami18n from stream-chat-react-native configured for the desired language. Chat can also accept a style prop with the theme, this can be used to overwrite styles inherited from OverlayProvider. If you are using TypeScript you should add the appropriate generics to your instantiation of StreamChat. Follow the documentation for stream-chat to ensure proper setup.

import { StreamChat } from 'stream-chat';
import { Streami18n } from 'stream-chat-react-native';

const streami18n = new Streami18n({ language: 'en' });
const chatClient = StreamChat.getInstance<
  AttachmentType,
  ChannelType,
  CommandType,
  EventType,
  MessageType,
  ResponseType,
  UserType
>('key');

<Chat client={chatClient} i18nInstance={streami18n}>

Channel

When creating a chat screen it is required that Channel wrap the stream-chat-react-native components being used. Channel provides multiple contexts to the enclosed components and allows for modification of many of the enclosed components via props that are then kept in context.

Three key props to Channel are channel, keyboardVerticalOffset, and thread. channel is a StreamChat channel. It can be created via const channel = client.channel('type', 'id') or is available as a callback on the ChannelList component via the prop onSelect. keyboardVerticalOffset is needed for adjusting the keyboard compatible view and should be the spacing above the Channel component, e.g. if you have a header it should be the header height. thread is a message object in the context of which a thread is occurring, i.e. the parent message of a thread. This can be set to any message, but is easily accessible on the MessageList component using the prop onThreadSelect. Channel also keeps an internal thread state which can be manipulated via openThread and closeThread using the ThreadContext if you would prefer not to use your own state for the thread.

const channel = client.channel('type', 'id');
const headerHeight = useHeaderHeight();
const { thread } = useContext(AppContext);

<Channel
  channel={channel}
  keyboardVerticalOffset={headerHeight}
  thread={thread}
>
Missing keyboardVerticalOffset Missing channel Missing Thread

Channel contains five providers to which you can add customizations and retrieve data using the appropriate hooks. They are ChannelProvider, MessagesProvider, ThreadProvider, SuggestionsProvider, and MessageInputProvider. These are all contained within a KeyboardCompatibleView then ensures the encompassed views respect the keyboard layout.

The type definition for Channel provide a full overview of the customizations available. A small sample of what is possible is can be seen in modifying hasFilePicker, messageContentOrder, and supportedReactions.

NOTE: When messageContentOrder is changed the default styling no longer matches the design as the bottom inner corner does not a have a radius. This can be altered using the theme, or more appropriately in this case to both theme and myMessageTheme. myMessageTheme will apply a theme to only the current users messages and thus allow for differing styles on sent and received messages.

hasFilePicker={true} (default) messageContentOrder={['gallery', 'files', 'text', 'attachments']} (default) supportedReactions={reactionData} (default)
hasFilePicker={false} messageContentOrder={['text', 'gallery', 'files', 'attachments']} supportedReactions={reactionData.slice(0, 3)}

MessageList

MessageList is the next component that is necessary for rendering a chat interface. It does not require any props as it uses the surrounding contexts.

<MessageList<
  AttachmentType,
  ChannelType,
  CommandType,
  EventType,
  MessageType,
  ResponseType,
  UserType
  >
/>

Similar to the other components props are available for modification of the UI. Although most modifications are provided via the Channel component some are provided through the MessageList, such as additionalFlatListProps to pass props directly to the flat list, onListScroll to access the scroll handler, and setFlatListRef to directly access the FlatList ref.

If you choose to track thread state locally the thread when selected can be accessed via a callback provided to the prop onThreadSelect. In this case proper typing can be added via generics.

<MessageList<
  AttachmentType,
  ChannelType,
  CommandType,
  EventType,
  MessageType,
  ResponseType,
  UserType
>
  onThreadSelect={(thread) => {
    setThread(thread);
    navigation.navigate('Thread');
  }}
/>

MessageInput

The final component necessary to create a fully functioning Chat screen is MessageInput. Similar to MessageList this component can be used without any props as it utilizes the surrounding contexts for functionality. The majority of MessageInput customizations are set at the Channel level. But one prop that should be local is threadList which is a boolean indicating whether or not the current MessageList is a thread.

<MessageInput />

Note: You can also utilize the Thread component which is already setup with both a MessageList and MessageInput component when creating a thread screen. A key prop on Thread is onThreadDismount as it can be used to set a locally tracked thread state to null | undefined.

Putting it all together

It takes very few components to put together a fully functioning Chat screen in practice:

<OverlayProvider
  bottomInset={bottom}
  i18nInstance={streami18n}
>
  <Chat client={chatClient} i18nInstance={streami18n}>
    <Channel
      channel={channel}
      keyboardVerticalOffset={headerHeight}
    >
      <View style={{ flex: 1 }}>
        <MessageList />
        <MessageInput />
      </View>
    </Channel>
  </Chat>
</OverlayProvider>

Once you have Chat up and running there are tons of customization possibilities and the TypeScript intellisense is a great asset in customizing the UI and functionality to your needs. Check out the examples for implementations of these components in apps you can build locally.

Customization

This SDK provides quite rich UI components in terms of design and functionality. But sometimes you may want to replace the default components with something that better fits your application requirements. For this purpose, we have made it quite easy to either replace the existing components with custom components or add your own styling on existing components

theme

The majority of components used in stream-chat-react-native can have custom styles applied to them via the theming system. You can add a theme object on Chat component as shown in following code snippet: To accurately create a theme we suggest utilizing our exported types to create your own theme. You can find the default theme object in theme.ts (check for the line export type Theme). We perform a deep merge on the styles so only styles designated in the custom theme overwrite the default styles.

Where possible we have also used displayName to expose the the path to the style for components. e.g., lets say if you want to customize styles on file attachment

  • Open the in-app developer menu
  • Turn on inspector by pressing "Show Inspector" button
  • Select the attachment file on UI. You will the displayName of the component in inspector (as showin in screenshot below)

For displayName FileAttachment{messageSimple{file}} we are saying the component name is FileAttachment and the style keys are messageSimple -> file. There are often multiple keys on a designated display name corresponding to different sub-components styles. In this case file has five sub-component keys, that can modify the styling.

file: {
  container: ViewStyle;
  details: ViewStyle;
  fileSize: TextStyle;
  icon: IconProps;
  title: TextStyle;
};

Modifying the theme for this component is done by adding custom styles at the desired keys.

import type { DeepPartial, Theme } from 'stream-chat-react-native';

const theme: DeepPartial<Theme> = {
  messageSimple: {
    file: {
      container: {
        backgroundColor: 'red',
      },
      icon: {
        height: 16,
        width: 16,
      },
    },
  },
};

<Chat style={theme}>
</Chat>
Display Name in Inspector Non-Themed Component Themed Component

NOTE: Most of the styles are standard React Native styles, but some styles applying to SVGs, Markdown, or custom components are numbers, strings, or other specified types. The TypeScript documentation of Theme should help you in this regard. Message text is an instance of an exception as it is rendered using react-native-markdown-package and the MarkdownStyle is added to the theme at key messageSimple -> content -> markdown. Standard React Native styles is a departure from the 2.x version of stream-chat-react-native in which styled-components was utilized for theming.

Custom components

We have prepare this doc to make things easy around component customization. You can replace any of the default components by providing prop either on OverlayProvider or Channel or ChannelList, depending on which component you are customizing. You can find which parent component needs to consume this prop, at bottom of screenshots.

e.g, looking at 1st slide, if you decide to replace default PreviewMessage component, you can do it as following:

// To remove the component
<ChannelList PreviewMessage={() => null}>

// To add custom UI
<ChannelList PreviewMessage={({ channel }) => {
    const latestMessage = channel.state.messages[channel.state.messages.length - 1];

    return <Text>{latestMessage.text}</Text>
}}>

While using custom component, you can access most of the necessary information available on props. Additionally, you can access values available on contexts that we use within this SDK.

e.g.,

import { ChannelList, useChannelsContext } from 'stream-chat-react-native';

<ChannelList PreviewMessage={({ channel }) => {
    const latestMessage = channel.state.messages[channel.state.messages.length - 1];
    const { channels } = useChannelsContext();

    // console.log('Number of channels in list - ', channels.length);
    return <Text>{latestMessage.text}</Text>
}}>

Please check the list of all available contexts here: https://getstream.github.io/stream-chat-react-native/v3/#section-contexts

Channel list components

IMAGE ALT TEXT HERE

IMAGE ALT TEXT HERE

Input and Attachment picker components

IMAGE ALT TEXT HERE

IMAGE ALT TEXT HERE

Message overlay components

IMAGE ALT TEXT HERE

Gallery components

IMAGE ALT TEXT HERE

IMAGE ALT TEXT HERE

Message list component

IMAGE ALT TEXT HERE

Attachment component

IMAGE ALT TEXT HERE

Message component

IMAGE ALT TEXT HERE

Thread component

IMAGE ALT TEXT HERE

Faq

How to customize message component

Channel component accepts following props, for which you can provide your own components:

  • Message - This is a higher order component, that wraps the UI component (MessageSimple) for message bubble. This component provides underlying UI component with all the handlers necessary. Mostly you shouldn't need to use customize this component, unless you want to write your own handlers for message actions, gesture etc. Using the Message Component as an example can be helpful to understand what props and hooks provide different information to the component. It is also suggested you optimize the component for rendering using memoization as is the standard suggested practice for FlatList items.
<OverlayProvider
  bottomInset={bottom}
  i18nInstance={streami18n}
>
  <Chat client={chatClient} i18nInstance={streami18n}>
    <Channel
      channel={channel}
      keyboardVerticalOffset={headerHeight}
      Message={CustomMessageComponent}
    >
      <View style={{ flex: 1 }}>
        <MessageList />
        <MessageInput />
      </View>
    </Channel>
  </Chat>
</OverlayProvider>
  • MessageSimple - This is the actual UI component for message bubble. You can still get access to all the handlers defined in Message HOC via useMessageContext
const CustomMessageUIComponent = () => {
  /** Custom implementation */
}

<OverlayProvider
  bottomInset={bottom}
  i18nInstance={streami18n}
>
  <Chat client={chatClient} i18nInstance={streami18n}>
    <Channel
      channel={channel}
      keyboardVerticalOffset={headerHeight}
      MessageSimple={CustomMessageUIComponent}
    >
      <View style={{ flex: 1 }}>
        <MessageList />
        <MessageInput />
      </View>
    </Channel>
  </Chat>
</OverlayProvider>

If you want to customize only a specific part of MessageSimple component, you can add your own custom UI components, by providing following props on Channel component:

  • MessageHeader
  • MessageFooter
  • MessageAvatar
  • MessageStatus
  • MessageText
  • MessageSystem
  • MessageContent
  • Attachment
  • Giphy
  • Card
  • FileAttachmentGroup
  • FileAttachment
  • Gallery
  • UrlPreview
<Channel
  channel={channel}
  keyboardVerticalOffset={headerHeight}
  MessageAvatar={CustomAvatarComponent}
  MessageText={CustomTextComponent}
>

Message bubble with custom text styles & fonts

We use react-native-simple-markdown library internally in the Message component to render markdown content of the text. Thus styling text in the Message component requires a slightly different approach than styling just a single standard Text component in React Native.

In the theme there are multiple text types such as replies and emoji-only messages that have the associated type MarkdownStyle, for the main message text this falls in messageSimple -> content -> markdown within theme. To modify the style of the markdown, text styles can be provided for each of the markdown sub-components that are applied based on text parsing.

const themeStyle = {
  messageSimple: {
    content: {
      markdown: {
        heading1: {
          color: 'pink',
        },
        inlineCode: {
          fontSize: 10
        }
      },
    },
  },
};

<Chat style={themeStyle}>
  ...
</Chat>

Following markdown keys are available for styling:

export type MarkdownStyle = Partial<{
  autolink: TextStyle;
  blockQuoteBar: ViewStyle;
  blockQuoteSection: ViewStyle;
  blockQuoteSectionBar: ViewStyle;
  blockQuoteText: TextStyle | ViewStyle;
  br: TextStyle;
  codeBlock: TextStyle;
  del: TextStyle;
  em: TextStyle;
  heading: TextStyle;
  heading1: TextStyle;
  heading2: TextStyle;
  heading3: TextStyle;
  heading4: TextStyle;
  heading5: TextStyle;
  heading6: TextStyle;
  hr: ViewStyle;
  image: ImageStyle;
  inlineCode: TextStyle;
  list: ViewStyle;
  listItem: ViewStyle;
  listItemBullet: TextStyle;
  listItemNumber: TextStyle;
  listItemText: TextStyle;
  listRow: ViewStyle;
  mailTo: TextStyle;
  mentions: TextStyle;
  newline: TextStyle;
  noMargin: TextStyle;
  paragraph: TextStyle;
  paragraphCenter: TextStyle;
  paragraphWithImage: ViewStyle;
  strong: TextStyle;
  sublist: ViewStyle;
  table: ViewStyle;
  tableHeader: ViewStyle;
  tableHeaderCell: TextStyle;
  tableRow: ViewStyle;
  tableRowCell: ViewStyle;
  tableRowLast: ViewStyle;
  text: TextStyle;
  u: TextStyle;
  view: ViewStyle;
}>;

Message bubble with full width

Because of richness of default message bubble (reactions, attachments, etc), we didn't want to add support this feature OOTB for the simplicity of maintainance. This is something that needs to themed on app level. Here is how you can implement full width message:

import { vw } from 'stream-chat-react-native';

const maxWidth = vw(100) - 72;
const themeStyle = {
  messageSimple: {
    card: {
      container: {
        width: maxWidth,
      },
    },
    content: {
      container: {
        width: maxWidth,
      },
      textContainer: {
        width: maxWidth,
        maxWidth: maxWidth,
      },
      wrapper: { width: maxWidth },
    },
    gallery: {
      // -2 because of the 2px border
      width: maxWidth - 2,
    },
    giphy: {
      container: {
        width: maxWidth,
      },
    },
  },
  // Override reply so the reuse of message style does not overflow text in the message input
  reply: {
    textContainer: {
      maxWidth: undefined,
      width: undefined,
    },
  },
};

<Chat style={themeStyle}>
  ...
</Chat>

Message bubble without border

Simple style customization using theme object, does the trick:

const themeStyle = {
  messageSimple: {
      content: {
        container: {
          borderWidth: 0,
        },
        containerInner: {
          borderWidth: 0,
        },
        deletedContainerInner: {
          borderWidth: 0,
        },
        textContainer: {
          borderWidth: 0,
        },
      }
  }
};

<Chat style={themeStyle}>
  ...
</Chat>

Message with custom reactions

To add custom reactions you need to use the supportedReactions prop on Channel. supportedReactions is an array of ReactionData. The default supportedReactions array contains 5 reactions.

export const reactionData: ReactionData[] = [
  {
    Icon: LoveReaction,
    type: 'love',
  },
  {
    Icon: ThumbsUpReaction,
    type: 'like',
  },
  {
    Icon: ThumbsDownReaction,
    type: 'sad',
  },
  {
    Icon: LOLReaction,
    type: 'haha',
  },
  {
    Icon: WutReaction,
    type: 'wow',
  },
];

To create your own reaction you need both a type and Icon. The Icon is a component with IconProps it is suggested you take advantage of react-native-svg for scaling purposes. It is suggested you look at the default icons for examples of how to create your own that is able to properly use the theme and sizing that are provided via props. Using exported type from stream-chat-react-native a custom reaction can be created and added.

export const StreamReaction: React.FC<IconProps> = (props) => (
  <RootSvg height={21} width={42} {...props} viewBox='0 0 42 21'>
    <RootPath
      d='M26.1491984,6.42806971 L38.9522984,5.52046971 C39.7973984,5.46056971 40.3294984,6.41296971 39.8353984,7.10116971 L30.8790984,19.5763697 C30.6912984,19.8379697 30.3888984,19.9931697 30.0667984,19.9931697 L9.98229842,19.9931697 C9.66069842,19.9931697 9.35869842,19.8384697 9.17069842,19.5773697 L0.190598415,7.10216971 C-0.304701585,6.41406971 0.227398415,5.46036971 1.07319842,5.52046971 L13.8372984,6.42816971 L19.2889984,0.333269706 C19.6884984,-0.113330294 20.3884984,-0.110730294 20.7846984,0.338969706 L26.1491984,6.42806971 Z M28.8303984,18.0152734 L20.5212984,14.9099734 L20.5212984,18.0152734 L28.8303984,18.0152734 Z M19.5212984,18.0152734 L19.5212984,14.9099734 L11.2121984,18.0152734 L19.5212984,18.0152734 Z M18.5624984,14.1681697 L10.0729984,17.3371697 L3.82739842,8.65556971 L18.5624984,14.1681697 Z M21.4627984,14.1681697 L29.9522984,17.3371697 L36.1978984,8.65556971 L21.4627984,14.1681697 Z M19.5292984,13.4435697 L19.5292984,2.99476971 L12.5878984,10.8305697 L19.5292984,13.4435697 Z M20.5212984,13.4435697 L20.5212984,2.99606971 L27.4627984,10.8305697 L20.5212984,13.4435697 Z M10.5522984,10.1082697 L12.1493984,8.31366971 L4.34669842,7.75446971 L10.5522984,10.1082697 Z M29.4148984,10.1082697 L27.8178984,8.31366971 L35.6205984,7.75446971 L29.4148984,10.1082697 Z'
      {...props}
    />
  </RootSvg>
);

const newReactionData = [...reactionData, { type: 'stream', Icon: StreamReaction }];

Both the resulting reaction picker and reaction result can then utilize this additional option.

Standard Reactions Modified Reactions Modified Reaction

Instagram style double-tap reaction

stream-chat-react-native uses a combination of react-native-gesture-handler and standard react-native touchables to provide animations to the UI. Because of this there are conditions in which multiple interactions are taking place at once.

e.g. If you press on a message it begins to depress and after a long hold will present the context menu for the message. But release sooner and if you are pressing on an image, the image viewer will appear.

Therefore to allow for something like double-tap reactions three props are required, onPressInMessage, onLongPressMessage, and onDoubleTapMessage. The first is used to prevent the onPress of inner react-native touchable components from firing while waiting for the double press to be evaluated by react-native-gesture-handler. Using a timeout the original onPress can be called if a second press has not ocurred in the expected time for the double tap to fire.

To prevent this event from firing when a long press occurs onLongPressMessage should be set to a function that cancels the timeout.

The onDoubleTapMessage prop can then be used to add a reaction as it is a function that is provided the message for which it is double tapped. This uses react-native-gesture-handler to track double taps. For convenience, as this is a common design pattern, the function is also is passed the handleReactionDoubleTap function. If defined (this is undefined when there is an error message or the status of the message is failed), this function can be passed a string of the reaction type to add or remove a reaction.

To complete the Instagram feel, setting the OverlayReactionList component to an empty component and limiting the supportedReactions as shown allows only 1 type of reaction and limits the UI to double-tap only to add or remove it.

const lastTap = React.useRef<number | null>(null);
const timeOut = React.useRef<NodeJS.Timeout | null>(null);

const handleDoubleTap = ({
  defaultHandler,
}) => {
  const now = Date.now();
  console.log(now, lastTap.current)
  if (lastTap.current && now - lastTap.current < 510) {
    if (timeOut.current) {
      clearTimeout(timeOut.current);
    }
  } else {
    lastTap.current = now;
    timeOut.current = setTimeout(() => {
      if (defaultHandler) {
        defaultHandler();
      }
    }, 510);
  }
};

const onDoubleTapMessage = ({
  actionHandlers
}) => {
  actionHandlers?.toggleReaction('love')
};

const onLongPressMessage = ({
  defaultHandler,
}) => {
  console.log('onLongPress')
  if (timeOut.current) {
    clearTimeout(timeOut.current);
    defaultHandler()
  }
};

<Channel
  channel={channel}
  keyboardVerticalOffset={headerHeight}
  onDoubleTapMessage={onDoubleTapMessage}
  onPressInMessage={handleDoubleTap}
  onLongPressMessage={onLongPressMessage}
  OverlayReactionList={() => null}
  thread={thread}
>

Slack style messages all on the left side

By default, received messages are shown on left side of the MessageList and sent messages are shown on right side of the MessageList.

You can change this at the Message level via the prop forceAlignMessages or set the alignment for the entire Channel using the same forceAlignMessages prop.

<Channel
  channel={channel}
  forceAlignMessages='left'
  keyboardVerticalOffset={headerHeight}
  thread={thread}
>

Message bubble with name of sender

In group messaging it's important to show the name of the sender associated message bubble - similar to Slack or WhatsApp. By default this is done in the MessageFooter component. This component is fully replaceable via props on Channel and is provided a set of props itself, MessageFooterProps, that can be used for rendering. Any additional data for rendering a custom footer can be pulled from contexts such as the MessageContext via the useMessageContext hook.

If you wanted to move the information about the sender to the top of the message you can provide a MessageHeader component to Channel which is provided the same props, MessageHeaderProps, as the footer, MessageFooterProps, and again can utilize the contexts as needed.

<Channel
  channel={channel}
  keyboardVerticalOffset={headerHeight}
  MessageHeader={(props) =>
    props.message?.user?.id !== chatClient.userID ? (
      <View
        style={{ flexDirection: 'row' }}
      >
        {Object.keys(props.members).length > 2 &&
          props.message.user?.name ? (
            <Text style={[{ color: grey, marginRight: 8 }]}>
              {props.message.user.name}
            </Text>
          ) : null}
        <Text style={[{ color: grey, textAlign: props.alignment }]}>
          {props.formattedDate}
        </Text>
      </View>
    ) : null
  }
  MessageFooter={() => null}
  thread={thread}
>
Standard Footer No Footer Header Added

Swipe message left to delete and right to reply

To add swipe controls to your messages it is suggested that you create a custom Message component to replace the default one. An easy solution is to wrap the standard exported message component from stream-chat-react-native in a Swipeable from react-native-gesture-handler/Swipeable. You can then use the functions provided by Swipeable to fine tune to functionality to your liking.

You can add reply functionality by calling setQuotedMessageState, available from the useMessagesContext hook. Or you can delete the message using a combination of client.deleteMessage and updateMessage, the latter of which is also available from the useMessagesContext hook. You can find the internal implementation of these functions in the Message component; or you can add any other functionality you like. It is suggested to add custom logic when implementing swipeable messages to ensure you only can swipe appropriate messages, i.e. you can only swipe to delete messages you have the ability to delete and have not yet been deleted. Using Message props and contexts this is easily achievable.

const SwipeableMessage = (
  props: MessageProps<
    AttachmentType,
    ChannelType,
    CommandType,
    EventType,
    MessageType,
    ResponseType,
    UserType
  >,
) => {
  return (
    <Swipeable
      onSwipeableLeftOpen={reply(props.message)}
      onSwipeableRightOpen={delete(props.message)}
      overshootLeft={false}
      overshootRight={false}
      renderLeftActions={(progress) => (
        <Animated.View
          style={{
            backgroundColor: 'blue',
            transform: [
              {
                translateX: progress.interpolate({
                  inputRange: [0, 1],
                  outputRange: [-100, 0],
                }),
              },
            ],
            width: 100,
          }}
        />
      )}
      renderRightActions={(progress) => (
        <Animated.View
          style={{
            justifyContent: 'center',
            opacity: progress.interpolate({
              inputRange: [0, 1],
              outputRange: [0, 1],
            }),
          }}
        >
          <StreamReaction />
        </Animated.View>
      )}
    >
      <Message {...props} />
    </Swipeable>
  );
};
Swiping partially open (opacity partial) Swiping all the way open Swiping using transform -> translateX

Message list for livestreaming video

There are two common scenarios in livestream applications.

Faded chat with video as background Split screen between video and chat

Here is how you can implement these two use cases:

Faded chat with video as background

import React from 'react';
import { SafeAreaView, StyleSheet, View } from 'react-native';

// Make sure you have installed following two dependencies
import MaskedView from '@react-native-community/masked-view';
import LinearGradient from 'react-native-linear-gradient';

import { Chat, Channel, MessageList } from 'stream-chat-react-native';

const theme = {
  messageList: {
    container: {
      backgroundColor: 'transperant',
    },
  },
  messageSimple: {
    content: {
      textContainer: {
        backgroundColor: 'white',
      },
    },
  },
};

// When you render your chat screen
<SafeAreaView style={{ flex: 1 }}>
  {/* For the sake of example, we are using image as background, you can replace it with your Video component. */}
  <Image source={{
    uri: 'https://i.pinimg.com/474x/59/a2/aa/59a2aae82b34bace9dc4d4df90457a3b.jpg'
  }} style={{ height: '100%', width: '100%' }} />

  <View style={[{ position: 'absolute' }, StyleSheet.absoluteFillObject]}>
    <Chat client={chatClient} style={theme}>
      <Channel
        channel={channel}
        keyboardVerticalOffset={headerHeight}
        thread={thread}
      >
        <View style={{ flex: 1 }} />
        <View style={{ flex: 2 }}>
          <MaskedView
            style={{ flex: 1 }}
            maskElement={
              <LinearGradient
                colors={['rgba(0,0,0,0)', 'rgba(0,0,0,1)']}
                style={{
                  flex: 1,
                }}
                start={{ x: 0, y: 0 }}
                end={{ x: 0, y: 1 }}
                locations={[0, 0.5]}
              />
            }>
            <MessageList />
          </MaskedView>
        </View>
        <MessageInput />
      </Channel>
    </Chat>
  </View>
</SafeAreaView>

Split screen between video and chat

import React from 'react';
import { SafeAreaView, StyleSheet, View } from 'react-native';

import { Chat, Channel, MessageList } from 'stream-chat-react-native';

// When you render your chat screen
<SafeAreaView style={{ flex: 1 }}>
  <View style={[{ position: 'absolute' }, StyleSheet.absoluteFillObject]}>
      <Chat client={chatClient} i18nInstance={streami18n}>
        <Channel
          channel={channel}
          keyboardVerticalOffset={headerHeight}
          thread={thread}
        >
          <View style={{ flex: 1 }}>
            <Image source={{
              uri: 'https://i.ibb.co/rfx5PCr/Screenshot-2021-02-24-at-14-20-57.png'
            }} style={{ height: '100%', width: '100%'}} resizeMode={'cover'} />
          </View>
          <MessageList<
            LocalAttachmentType,
            LocalChannelType,
            LocalCommandType,
            LocalEventType,
            LocalMessageType,
            LocalResponseType,
            LocalUserType
          >
            onThreadSelect={(thread) => {
              setThread(thread);
              navigation.navigate('Thread');
            }}
          />
          <MessageInput />
        </Channel>
      </Chat>
  </View>
</SafeAreaView>

How to build custom attachment

Attachments is simply a property on message object:

const messageObject = {
  id: '12312jh3b1jh2b312',
  text: 'This is my test message!',
  attachments: [
    {
      type: 'image',
      thumb_url: ''
    },
    {
      type: 'file',
      asset_url: ''
    }
  ]
}

Depending on value of message.attachments[index].type, we render attachments in different views. By default, we have following built-in views for rendering attachments:

  • attachment type image - Gallery (single or multiple images)
  • attachment type giphy - Giphy
  • attachment type file - FileAttachment
  • attachment with url - UrlPreview

We have some default implementation for these type of attachments, to display them in MessageList. But you can override these components with your own implementation, as shown in following example:

const CustomGiphy = ({ attachment, onPressIn }) => {
  console.log(attachment.type);
  console.log(attachment.actions)
  console.log(attachment.image_url)
  console.log(attachment.thumb_url)
  console.log(attachment.title)
  console.log(attachment.type)

  return (/** Your custom UI */)
}

const CustomGallery = ({ images, onPressIn }) => {
  console.log(images);

  return (/** Your custom UI */)
}

const CustomFileAttachment = ({ attachment }) => {
  console.log(attachemnt.mime_type);
  console.log(attachemnt.title);
  console.log(attachemnt.file_size);
  console.log(attachemnt.actions);

  return (/** Your custom UI */)
}

const CustomUrlPreview = () => {
  console.log(text);
  console.log(thumb_url);
  console.log(title);

  return (/** Your custom UI */)
}

// Provide these custom components to Channel, as props.
<Channel
  Gallery={CustomGallery}
  Giphy={CustomGiphy}
  FileAttachment={CustomFileAttachment}
  UrlPreview={CustomUrlPreview}
>

You can also assign custom type on attachment. Any custom attachment gets rendered in Card view. You can add your own implementation for Card component.

const CustomCardComponent = ({ type, ...otherProperties }) => {
  console.log(type);
  console.log(otherProperties);

  return (/** Your custom UI */)
}

<Channel Card={CustomCardComponent} />

Let's try to build location sharing functionlity, using custom attachment:

Location sharing example

Lets build an example of location sharing option in the app:

IMAGE ALT TEXT HERE
  • Show a "Share Location" button next to input box. Channel component accepts a prop InputButtons, to add some custom buttons next to input box.

  • When user presses this button, it should fetch the current location coordinates of user, and send a message on channel as following:

    const messageWithLocation = {
     text: 'This is my location',
     attachments: [
       {
         type: 'location',
         latitude: '50.212312',
         longitude: '-71.212659',
         // You can add more custom properties if needed.
       }
     ]
    }

    For our example, we are going to use react-native-geolocation library. Please check their setup instruction on their docs.

    NOTE If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned here. Also don't forget to enable "location update" capability in background mode, from xcode

  • On the receiver end, location type attachment should be rendered in map view, in message list. We are going to use google static maps api to render map in message. You can use other libraries as well such as react-native-maps

NOTE: Before you start using the Maps Static API, you need a project with a billing account and the Maps Static API enabled. To learn more, see Set up in Cloud Console

  • When user presses on location type attachment, it should take him to Google Maps application, with given coordinates.

Here is the full implementation of share location example:

import React, {useEffect, useState} from 'react';
import {Image, Linking, StyleSheet, TouchableOpacity, View} from 'react-native';
import {StreamChat} from 'stream-chat';
import {
  Channel,
  Chat,
  MessageInput,
  MessageList,
  OverlayProvider as ChatOverlayProvider,
  useChannelContext,
} from 'stream-chat-react-native';
import {
  SafeAreaProvider,
  SafeAreaView,
  useSafeAreaInsets,
} from 'react-native-safe-area-context';
import Geolocation from '@react-native-community/geolocation';
import Svg, {Path} from 'react-native-svg';

// ============================================================
//   Fill in following values
// ============================================================
const API_KEY = '';
const USER_ID = '';
const USER_TOKEN = '';
const CHANNEL_ID = '';
// Reference: https://developers.google.com/maps/documentation/maps-static/get-api-key
const MAPS_API_KEY = '';

const chatClient = StreamChat.getInstance(API_KEY);
const user = {id: USER_ID};

// We are going to `await` following two calls, before rendering any UI component.
// Please check the App component at bottom.
const connectUserPromise = chatClient.connectUser(user, USER_TOKEN);
const channel = chatClient.channel('messaging', CHANNEL_ID);

// Basic utilities required for location sharing

// Given the location coordinates, this function generates url for google map,
// and opens this Url using Linking module of react-native.
// Please check documentation of `Linking` module from react-native, for details:
// https://reactnative.dev/docs/linking
//
// Generally this url will be opened in google maps application.
// https://developers.google.com/maps/documentation/urls/get-started
const goToGoogleMaps = (lat, long) => {
  const url = `https://www.google.com/maps/search/?api=1&query=${lat},${long}`;

  Linking.canOpenURL(url).then((supported) => {
    if (supported) {
      Linking.openURL(url);
    } else {
      console.log(`Don't know how to open URI: ${url}`);
    }
  });
};

// Generates static map url for given location coordinates.
// For reference, please check - https://developers.google.com/maps/documentation/maps-static/overview
const prepareStaticMapUrl = (lat, long) => {
  let baseURL = 'https://maps.googleapis.com/maps/api/staticmap?';
  let url = new URL(baseURL);
  let params = url.searchParams;
  params.append('center', `${lat},${long}`);
  params.append('zoom', '15');
  params.append('size', '600x300');
  params.append('maptype', 'roadmap');
  params.append('key', MAPS_API_KEY);
  params.append('markers', `color:red|${lat},${long}`);

  return url.toString();
};

// Send your current location attachment, as message, on current channel.
const sendCurrentLocation = () => {
  Geolocation.getCurrentPosition((info) => {
    channel?.sendMessage({
      text: 'This is my location',
      attachments: [
        {
          type: 'location',
          latitude: info.coords.latitude,
          longitude: info.coords.longitude,
        },
      ],
    });
  });
};

// UI Component for rendering `location` type attachment
const LocationCard = ({type, latitude, longitude}) => {
  if (type === 'location') {
    const mapApi = prepareStaticMapUrl(latitude, longitude);
    console.log(mapApi);
    return (
      <TouchableOpacity onPress={() => goToGoogleMaps(latitude, longitude)}>
        <Image source={{uri: mapApi}} style={{height: 200, width: 300}} />
      </TouchableOpacity>
    );
  }
};

// Icon for "Share Location" button, next to input box.
const ShareLocationIcon = (props) => (
  <Svg width={24} height={24} viewBox="0 0 24 24" fill="none" {...props}>
    <Path
      d="M12 12c-1.654 0-3-1.345-3-3 0-1.654 1.346-3 3-3s3 1.346 3 3c0 1.655-1.346 3-3 3zm0-4a1.001 1.001 0 101 1c0-.551-.449-1-1-1z"
      fill="#000"
    />
    <Path
      fillRule="evenodd"
      clipRule="evenodd"
      d="M12 22s7-5.455 7-12.727C19 5.636 16.667 2 12 2S5 5.636 5 9.273C5 16.545 12 22 12 22zm1.915-4.857C15.541 15.032 17 12.277 17 9.273c0-1.412-.456-2.75-1.27-3.7C14.953 4.664 13.763 4 12 4s-2.953.664-3.73 1.573C7.456 6.523 7 7.86 7 9.273c0 3.004 1.459 5.759 3.085 7.87.678.88 1.358 1.614 1.915 2.166a21.689 21.689 0 001.915-2.166zm-.683 3.281s0 .001 0 0z"
      fill="#000"
    />
  </Svg>
);

// UI component to add Share Location button next to input box.
const InputButtons = () => {
  const {channel: currentChannel} = useChannelContext();

  return (
    <TouchableOpacity
      onPress={() => sendCurrentLocation(currentChannel)}
      style={{marginRight: 10}}>
      <ShareLocationIcon />
    </TouchableOpacity>
  );
};

const ChannelScreen = () => {
  const {bottom} = useSafeAreaInsets();

  return (
    <ChatOverlayProvider bottomInset={bottom} topInset={0}>
      <SafeAreaView>
        <Chat client={chatClient}>
          {/* Setting keyboardVerticalOffset as 0, since we don't have any header yet */}
          <Channel
            channel={channel}
            keyboardVerticalOffset={0}
            Card={LocationCard}
            InputButtons={InputButtons}>
            <View style={StyleSheet.absoluteFill}>
              <MessageList />
              <MessageInput />
            </View>
          </Channel>
        </Chat>
      </SafeAreaView>
    </ChatOverlayProvider>
  );
};

export default function App() {
  const [ready, setReady] = useState();

  useEffect(() => {
    const initChat = async () => {
      await connectUserPromise;
      await channel.watch();
      setReady(true);
    };

    initChat();
  }, []);

  if (!ready) {
    return null;
  }

  return (
    <SafeAreaProvider>
      <ChannelScreen channel={channel} />
    </SafeAreaProvider>
  );
}

Keyboard

React Native provides an in built component called KeyboardAvoidingView. This component works well for most of the cases where height of the component is 100% relative to screen. If you have a fixed height then it may create some issues (it depends on your use case - and how you use wrappers such as navigation around chat components).

To avoid this issue we built our own component - KeyboardCompatibleView. It contains simple logic - when keyboard is opened (which we can know from events of Keyboard module), adjust the height of Channel component, and when keyboard is dismissed, again adjust the height of Channel component accordingly. KeyboardCompatibleView is near identical to KeyboardAvoidingView from react-native, with some adjustments for app state.

You can provide following props to Channel to customize the builtin KeyboardCompatibleView behavior.

disableKeyboardCompatibleView - boolean 
keyboardBehavior - 'padding' | 'position' | 'height'
keyboardVerticalOffset  - number

You can pass additional props directly to the component using the additionalKeyboardAvoidingViewProps.

You can also replace the KeyboardCompatibleView with your own custom component by passing it as a prop to channel.

<Channel
  KeyboardCompatibleView={CustomizedKeyboardView}
  ...
/>

Or disable the KeyboardCompatibleView and use the standard KeyboardAvoidingView from react-native. You can disable KeyboardCompatibleView by using prop disableKeyboardCompatibleView on the Channel component.

<Channel
  disableKeyboardCompatibleView
  ...
/>

How to modify the underlying FlatList of MessageList or ChannelList

You can additionally pass props to the underlying FlatList using additionalFlatListProps prop.

<ChannelList
  additionalFlatListProps={{ bounces: true }}
  filters={filters}
  sort={sort}
/>
<MessageList additionalFlatListProps={{ bounces: true }} />

Image compression

If an image is too big it may cause a delay while uploading to our server. You can elect to compress images prior to upload by adding the compressImageQuality prop to Channel.

compressImageQuality can be a value from 0 to 1, where 1 is the best quality, i.e. no compression. On iOS, values larger than 0.8 don't decrease the quality a noticeable amount on most images, while still reducing the file size significantly when compared to the uncompressed version.

How to customize message actions

[supported since v3.1.0]

Message actions pop up in message overlay, when you longpress a message. We have provided a granular control over these actions.

Channel component accepts a prop called - messageActions. You can use this prop as a callback function to render message actions selectively. Array of actions returned by this function, will be rendered in message overlay (on long press).

By default we render following message actions:

  • edit message
  • delete message
  • block user
  • reply
  • thread reply
  • copy message
  • flag message

Partially remove some message actions

e.g., if you only want to keep "copy message" and "delete message", here is how you do it:

<Channel
  messageActions={({
    blockUser, // MessageAction | null;
    canModifyMessage, // boolean;
    copyMessage, // MessageAction | null;
    deleteMessage, // MessageAction | null;
    dismissOverlay, // function
    editMessage, // MessageAction | null;
    error, // boolean;
    flagMessage, // MessageAction | null;
    isMyMessage, // boolean;
    isThreadMessage, // boolean;
    message, // MessageType<At, Ch, Co, Ev, Me, Re, Us>;
    messageReactions, // boolean;
    muteUser, // MessageAction | null;
    reply, // MessageAction | null;
    retry, // MessageAction | null;
    threadReply, // MessageAction | null;
    repliesEnabled?, // boolean;
  }) => (
    // You can also use `isMyMessage` boolean flag to decide which actions to render for
    // sent messages vs received messages.
    [
      copyMessage,
      deleteMessage
    ]
  )}
>
  {/** MessageList and MessageInput component here */}
</Channel>

Add a new custom message action

E.g., let's suppose you want to introduce a new message action - "Pin Message"

<Channel
  messageActions={({
    message, // MessageType<At, Ch, Co, Ev, Me, Re, Us>;
    copyMessage, // MessageAction | null
    dismissOverlay,
  }) => (
    [
      copyMessage,
      {
        action: () => {
          client.pinMessage(message);
          dismissOverlay();
        };
        title: "Pin Message";
        icon: PinIcon; // Use some SVG icon here | Optional
        titleStyle: { color: 'blue', fontSize: 13 }; // StyleProp<TextStyle> | Optional
      }
    ]
  )}
>
  {/** MessageList and MessageInput component here */}
</Channel>

Override or intercept individual message action (edit, delete, reaction, reply, etc.)

There are a number of message actions you may want to intercept or override. Channel takes as props and passes on to the MessagesContext all of these intercepts and overrides.

The intercepts will not change the standard functions but will be called during their calls if provided. For actions such as analytics these are good to take advantage of. All of these intercept functions receive the message they are called on, and handleReaction additionally receives the reaction type.

You can also override the built in functions for these actions if you so choose. With the exception of selectReaction all of the functions are provided the message they are called on and must return a MessageAction for use in the context menu. selectReaction is also provided the message but should return an async function that takes a reactionType. This is called with the selected reaction type when a reaction is selected in the context menu.

type MessageAction = {
  action: () => void;
  title: string;
  icon?: React.ReactElement;
  titleStyle?: StyleProp<TextStyle>;
};

MessageAction is provided to the context menu where when selected action is called for the item. title, icon, and titleStyle are used to modify the appearance.

<Channel
  channel={channel}
  editMessage={(message) => ({
    action: () => console.log(message.text),
    icon: <StreamReaction />,
    title: 'Custom Edit',
    titleStyle: { color: '#005FFF' },
  })}
  keyboardVerticalOffset={headerHeight}
  thread={thread}
>
  • handleBlock
  • handleCopy
  • handleDelete
  • handleEdit
  • handleFlag
  • handleMute
  • handleReaction
  • handleReply
  • handleRetry
  • handleThreadReply
  • blockUser
  • copyMessage
  • deleteMessage
  • editMessage
  • flagMessage
  • muteUser
  • selectReaction
  • reply
  • retry
  • threadReply
Props for intercepting Props for overriding editMessage override example

How to change the layout of MessageInput component

We provide the MessageInput container out of the box in a fixed configuration with many customizable features. Similar to other components it accesses most customizations via context, specially the MessageInputContext which is instantiated in Channel. You can also pass the same props as the context provides directly to the MessageInput component to override the context values.

<Channel
  channel={channel}
  Input={() => null}
  keyboardVerticalOffset={headerHeight}
  Message={CustomMessageComponent}
>
  <View style={{ flex: 1 }}>
    <MessageList />
    <MessageInput
      Input={() => <View style={{ height: 40, backgroundColor: 'red' }} />}
    />
  </View>
</Channel>

The code above would render the red View and not null as the props take precedence over the context value.

You can modify MessageInput in a large variety of ways. The type definitions for the props give clear insight into all of the options. You can replace the Input wholesale, as above, or create you own MessageInput component using the provided hooks to access context.

Replace SendButton hasFilePicker & hasImagePicker - false numberOfLines={2}

NOTE: The additionalTextInputProps prop of both Channel and MessageInput is passed the the internal TextInput component from react-native. If you want to change the TextInput component props directly this can be done using this prop.

Disable autocomplete feature on input (mentions and commands)

The auto-complete trigger settings by default include /, @, and : for slash commands, mentions, and emojis respectively. These triggers are created by the exported function ACITriggerSettings, which takes ACITriggerSettingsParams and returns TriggerSettings. You can override this function to remove some or all of the trigger settings via the autoCompleteTriggerSettings prop on Channel. If you remove the slash commands it is suggested you also remove the commands button using the prop on Channel hasCommands. You can remove all of the commands by returning an empty object from the function given to autoCompleteTriggerSettings.

<Channel
  autoCompleteTriggerSettings={() => ({})}
  channel={channel}
  hasCommands={false}
  keyboardVerticalOffset={headerHeight}
  thread={thread}
>

Push Notifications

Setup

iOS
Android

Requirements

  • A user must be a member of channel if they expect a push notification for a message on that channel to be sent.

  • We only send a push notification when a user is NOT connected to chat, i.e. if a user does NOT have any active WS (websocket) connection. A WS connection is established when you call connectUser on the instance of StreamChat. You must also call addDevice with the users device token on the instance of StreamChat to provide the token to the server for push.

Caveats

Usually you want to receive push notification when your app is in the background. When your app is closed (not quit) the WS connection stays active for approximately 15-20 seconds; after which the system will break the connection automatically. For these 15-20 seconds you won't receive any push notifications since the WS is still active.

NOTE If you are using stream-chat-react-native>=3.2.0, you don't need to add following change. We handle this OOTB within Chat component.

To handle this case you have to manually break the WS connection when your app is closed.

// >= stream-chat@3.5.1
await client.closeConnection();

// < stream-chat@3.5.1
await client.wsConnection.disconnect();

And when app is re-opened (brought to foreground) you need to re-establish the connection.

// >= stream-chat@3.5.1
await client.openConnection();

// < stream-chat@3.5.1
await client._setupConnection();

You can use AppState from react-native to detect whether the app is in the foreground or background on change.

Clone this wiki locally