Skip to main content
Version: v4

Channel Background Customization

Basic Custom Background

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

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.

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%',
},
});
note

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>
);
};

Did you find this page helpful?