Skip to main content

Handling States and Configs

Source Code: https://github.com/rtk-io/react-native-samples/tree/main/samples/create_your_own_ui

RtkMeeting component does a lot more than just providing the user interface.

It does the following things internally.

  1. Keeps a mapping of components and show them according to the preset's view_type such as group_call, webinar, and livestream.
  2. Provides background color, text colors and other such CSS properties.
  3. Maintains states of modals, sidebars between web-core & ui-kit
  4. Shifts the control bar buttons to More menu if the screen size is small.
  5. Passes config, states, translation, icon packs to all child components.
  6. It is the target element that gets full screened on click of full screen toggle.
  7. Joins the meeting automatically if showSetupScreen is false.

Since we are splitting RtkMeeting component in pieces, we need to do these ourselves now.

import React, {useEffect, useState} from 'react';
import {
RealtimeKitProvider,
useRealtimeKitClient,
useRealtimeKitMeeting,
} from '@cloudflare/realtimekit-react-native';
import RealtimeKitClient from '@cloudflare/realtimekit';
import {
RtkUIProvider,
UIConfig,
defaultConfig,
generateConfig,
} from '@cloudflare/realtimekit-react-native-ui';
import {RtkThemePresetV1} from '@cloudflare/realtimekit';
import {RtkStateListenersUtils} from './rtk-state-listeners';
import {CustomStates} from './types';
import {store} from './utils/store';
import {Provider} from 'react-redux';

function Meeting() {
const {meeting} = useRealtimeKitMeeting();
const [config, setConfig] = useState<UIConfig>(defaultConfig);
const [states, setStates] = useState<CustomStates>({
meeting: 'setup',
sidebar: 'chat',
activeMoreMenu: false,
activeLeaveConfirmation: false,
permissionGranted: true,
prefs: {
mirrorVideo: true,
muteNotificationSounds: false,
autoScroll: true,
},
designSystem: {
colors: {
brand: {
300: '#497CFD',
400: '#356EFD',
500: '#2160FD',
600: '#0D51FD',
700: '#2160FD',
},
background: {
1000: '#080808',
900: '#1A1A1A',
800: '#333333',
700: '#4C4C4C',
600: '#666666',
},
text: '#FFFFFF',
textOnBrand: '#FFFFFF',
videoBg: '#333333',
success: '#83D017',
danger: '#FF2D2D',
warning: '#FFCD07',
},
},
});

useEffect(() => {
async function setupMeetingConfigs() {
const theme = meeting!.self.config;
const generatedConfig = generateConfig(theme as RtkThemePresetV1, {});
const newConfig = generatedConfig.config;

setConfig({...newConfig});

const stateListenersUtils = new RtkStateListenersUtils(
() => meeting,
() => states,
() => setStates,
);
stateListenersUtils.addRtkEventListeners();
}

setupMeetingConfigs();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [meeting]);
return <CustomRtkMeetingUI meeting={meeting} config={config} states={states} setStates={setStates} />;
}

function CustomRtkMeetingUI({ meeting, config, states, setStates }: { meeting: RealtimeKitClient, config: UIConfig, states: CustomStates, setStates: SetStates}) {
return (
<View>
<RtkText>Your Custom UI will come here </RtkText>
</View>
);
}

class RtkStateListenersUtils {
getStates: () => CustomStates;

getStateSetter: () => (newState: CustomStates) => void;

getMeeting: () => RealtimeKitClient;

get states() {
return this.getStates();
}

get setGlobalStates() {
return this.getStateSetter();
}

get meeting() {
return this.getMeeting();
}

constructor(
getMeeting: () => RealtimeKitClient,
getGlobalStates: () => CustomStates,
getGlobalStateSetter: () => (newState: CustomStates) => void,
) {
this.getMeeting = getMeeting;
this.getStates = getGlobalStates;
this.getStateSetter = getGlobalStateSetter;
}
private updateStates(newState: CustomStates) {
this.setGlobalStates((oldState: CustomStates) => {
return {
...oldState,
...newState,
};
});
}
private roomJoinedListener = () => {
this.updateStates({meeting: 'joined'});
};

private socketServiceRoomJoinedListener = () => {
if (
this.meeting.stage.status === 'ON_STAGE' ||
this.meeting.stage.status === undefined
) {
return;
}
this.updateStates({meeting: 'joined'});
};

private waitlistedListener = () => {
this.updateStates({meeting: 'waiting'});
};

private roomLeftListener = ({state}: {state: RoomLeftState}) => {
const states = this.states;
if (states?.roomLeftState === 'disconnected') {
this.updateStates({meeting: 'ended', roomLeftState: state});
return;
}
this.updateStates({meeting: 'ended', roomLeftState: state});
};

private mediaPermissionUpdateListener = ({
kind,
message,
}: {
kind: any; // PermissionSettings['kind'];
message: string;
}) => {
if (['audio', 'video'].includes(kind!)) {
if (
message === 'ACCEPTED' ||
message === 'NOT_REQUESTED' ||
this.states.activeDebugger
) {
return;
}
const permissionModalSettings: any = {
enabled: true,
kind,
};
this.updateStates({activePermissionsMessage: permissionModalSettings});
}
};

private joinStateAcceptedListener = () => {
this.updateStates({activeJoinStage: true});
};

private handleChangingMeeting(destinationMeetingId: string) {
this.updateStates({
activeBreakoutRoomsManager: {
...this.states.activeBreakoutRoomsManager,
active: this.states.activeBreakoutRoomsManager!.active,
destinationMeetingId,
},
});
}

addRtkEventListeners() {
if (this.meeting.meta.viewType === 'LIVESTREAM') {
this.meeting.self.addListener(
'socketServiceRoomJoined',
this.socketServiceRoomJoinedListener,
);
}
this.meeting.self.addListener('roomJoined', this.roomJoinedListener);

this.meeting.self.addListener('waitlisted', this.waitlistedListener);
this.meeting.self.addListener('roomLeft', this.roomLeftListener);
this.meeting.self.addListener(
'mediaPermissionUpdate',
this.mediaPermissionUpdateListener,
);
this.meeting.self.addListener(
'joinStageRequestAccepted',
this.joinStateAcceptedListener,
);

if (this.meeting.connectedMeetings.supportsConnectedMeetings) {
this.meeting.connectedMeetings.once(
'changingMeeting',
this.handleChangingMeeting,
);
}
}
cleanupRtkEventListeners() {}
}

Let's discuss the bits and pieces one by one.

const theme = meeting!.self.config;
const { config } = generateConfig(theme, meeting!);

In the above code snippets, we are generating configs using the preset configurations & meeting configs.

Post this, We are extending the config to pass the targetElement to full screen toggle and storing this config to be passed to child components.

setConfig({ ...config });

We need to also ensure that web-core & ui-kit states are in sync. Since we are handling states now, we will have to add web-core & ui-kit listeners.

To add react-native-core listeners, RtkStateListenersUtils class, is being used.

const stateListenersUtils = new RtkStateListenersUtils(
() => meeting,
() => states,
() => setStates,
);
stateListenersUtils.addRtkEventListeners();

To join the meeting, we are using await meeting.join();.

Now that we know the extra overhead that comes with splitting RtkMeeting component, let's start with showing custom UIs as per the meeting state.