Custom Channel Background

Basic Custom Background

You can change the background statically by wrapping MessageList and MessageInput with the ImageBackground component.

167857632 C0bc9d67 0a84 4cf5 9d75 305e3bcd1f3d

Also make sure you adjust the theme correctly.

import { Channel, MessageInput, MessageList, ThemeProvider } from 'stream-chat-react-native';
import { ImageBackground } from 'react-native';

export const theme = {
  messageList: {
    container: {
      backgroundColor: 'transparent',
    },
  },
};

const IMAGE_URI =
  'https://images.unsplash.com/photo-1549125764-91425ca48850?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8NjF8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60';

const ChannelScreen = ({ channel }) => (
  <ThemeProvider style={theme}>
    <Channel channel={channel}>
      <ImageBackground
        style={{ flex: 1 }}
        source={{
          uri: IMAGE_URI,
        }}
      >
        <MessageList />
        <MessageInput />
      </ImageBackground>
    </Channel>
  </ThemeProvider>
);

Custom Background With Selection Screen

In this step, we will add a button that will navigate to a separate screen, where the user will be able to select their favorite background image for a specific channel.

168006715 Acae4b85 00cb 4b45 A127 Aa8f94c13895

168006193 8fd4ad85 7553 4956 A7c6 5d6979e15ee4

Chat screen with customize background buttonWallpaper overview screen with background image options

Store and manage channel preferences

To persist the channel preferences (at the moment, only the URI of the selected background), we will need to store some data. We will do it by using react-native-mmkv, a key-value storage framework.

Follow the installation steps, and let’s get started, shall we?

We will start by creating our ChannelBackgroundView component. This component will be in charge of rendering the custom background by retrieving it from our key-value store. We save an object to a key-value store to be scalable and future-proof. You might want to add other preferences later, such as dimming value, background color, etc.

import type { ViewProps } from 'react-native';
import { useMMKVObject } from 'react-native-mmkv';

type ChannelPreferences = {
  imageUri: string;
};

const DEFAULT_BACKGROUND_URI = 'https://i.redd.it/3jfjc53fsyb61.jpg';

const ChannelBackgroundView = ({
  channelId,
  ...props
}: {
  channelId: string;
} & ViewProps) => {
  const [channelPreferences] = useMMKVObject<ChannelPreferences>(channelId);
  const uri = channelPreferences?.imageUri || DEFAULT_BACKGROUND_URI;

  return <ImageBackground {...props} source={{ uri }} />;
};

We will then use it in our previously built ChannelScreen. Replace the static ImageBackground with ChannelBackgroundView and pass the channelId.

const ChannelScreen = ({ channel }) => {
  return (
    <ThemeProvider style={theme}>
      <Channel channel={channel}>
        <ChannelBackgroundView channelId={channel?.id} style={{ flex: 1 }}>
          <MessageList />
          <MessageInput />
        </ChannelBackgroundView>
      </Channel>
    </ThemeProvider>
  );
};

Wallpaper overview screen

Let’s now add a screen where the user can choose a wallpaper from a particular predefined list of images.

import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { useMMKVObject } from 'react-native-mmkv';
import { View, SafeAreaView, Pressable, Image, StyleSheet } from 'react-native';

const WallpaperOverviewScreen = ({
  navigation: { navigate },
  route: {
    params: { channelId },
  },
}: WallpaperOverviewScreenProps) => {
  const [_, setChannelPreferences] = useMMKVObject<ChannelPreferences>(channelId);
  return (
    <SafeAreaView
      style={{
        flex: 1,
        justifyContent: 'center',
      }}
    >
      <View style={styles.container}>
        {BRIGHT_IMAGES?.map(({ imageUri = '' }, i) => {
          const handleOnPress = () => {
            setChannelPreferences({ imageUri });
            navigate('Channel');
          };
          return (
            <Pressable
              key={i}
              onPress={handleOnPress}
              style={{
                margin: 1,
                width: GRID_ITEM_WIDTH,
              }}
            >
              <Image style={styles.image} source={{ uri: imageUri }} />
            </Pressable>
          );
        })}
      </View>
    </SafeAreaView>
  );
};

type StackNavigatorParamList = {
  WallpaperOverviewScreen: {
    channelId: string;
  };
};

type WallpaperOverviewScreenProps = {
  navigation: StackNavigationProp<StackNavigatorParamList, 'WallpaperOverviewScreen'>;
  route: RouteProp<StackNavigatorParamList, 'WallpaperOverviewScreen'>;
};

type ChannelPreferences = {
  imageUri: string;
};

const GRID_ITEM_WIDTH = '32.7%';

// Some random images that will get you started
const BRIGHT_IMAGES = [
  'https://images.unsplash.com/photo-1549125764-91425ca48850?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8NjF8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1549241520-425e3dfc01cb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8ODB8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1554226321-24fdcddd5a55?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MjE5fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1550006490-9f0656b79e9d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8ODl8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1551506448-074afa034c05?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTEzfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553114835-6f7674d3c2c0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTMyfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553075712-453f7213c24f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTMzfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1551917951-148edcd8ea8d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTU3fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553969923-bbf0cac2666b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MjA3fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553194642-29b272a173b9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTcwfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553356084-58ef4a67b2a7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTcxfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
  'https://images.unsplash.com/photo-1553526777-5ffa3b3248d8?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTk4fHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=800&q=60',
].map(imageUri => ({
  imageUri,
}));

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    flex: 1,
    alignContent: 'stretch',
    flexWrap: 'wrap',
    padding: 6,
  },
  image: {
    flex: 1,
    width: '100%',
  },
});

Be aware of the fact that channel preferences were implemented with MMKV, a key-value storage framework. There are alternative approaches to achieving the same goal, such as saving the channel preferences as custom data on Stream’s channel object.

Add a configuration button

We will now add a button that will take the user from the Channel screen to our new WallpaperOverview screen.

import { useNavigation } from '@react-navigation/native';
import { Channel, MessageInput, MessageList, ThemeProvider } from 'stream-chat-react-native';
import { Pressable, Text, StyleSheet } from 'react-native';

const ChannelScreen = ({ channel }) => {
  const { navigate } = useNavigation();
  const handleMenuOnPress = () => navigate('WallpaperOverviewScreen', { channelId: channel?.id });

  return (
    <ThemeProvider style={theme}>
      <Channel channel={channel}>
        <ChannelBackgroundView channelId={channel?.id} style={{ flex: 1 }}>
          <Pressable style={styles.menuButton} onPress={handleMenuOnPress}>
            <Text>🎨</Text>
          </Pressable>
          <MessageList />
          <MessageInput />
        </ChannelBackgroundView>
      </Channel>
    </ThemeProvider>
  );
};

const styles = StyleSheet.create({
  menuButton: {
    position: 'absolute',
    right: 0,
    top: 0,
    backgroundColor: 'rgba(255,87,56,0.65)',
    borderRadius: 36,
    padding: 16,
    margin: 16,
    alignItems: 'center',
    zIndex: 10,
  },
});

export const theme = {
  messageList: {
    container: {
      backgroundColor: 'transparent',
    },
  },
};

Optional: Connect all screens by navigation

If applicable to your use case, add our screens to a Navigation Stack by doing the following:

import { createNativeStackNavigator } from 'react-native-screens/native-stack';
import { NavigationContainer } from '@react-navigation/native';

const Stack = createNativeStackNavigator();

export default () => {
  return (
    <SafeAreaProvider>
      <ThemeProvider style={theme}>
        <NavigationContainer>
          <Stack.Navigator initialRouteName='Channel'>
            <Stack.Screen component={ChannelScreen} name='Channel' options={noHeaderOptions} />
            <Stack.Screen component={WallpaperOverviewScreen} name='WallpaperOverviewScreen' />
          </Stack.Navigator>
        </NavigationContainer>
      </ThemeProvider>
    </SafeAreaProvider>
  );
};
© Getstream.io, Inc. All Rights Reserved.