Build Your Own UI·续2
required init?(coder: NSCoder) { fatalError("init(coder:) not supported") }
override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black
// Header let header = RtkMeetingHeaderView(meeting: meeting) header.translatesAutoresizingMaskIntoConstraints = false view.addSubview(header)
// Control bar let controlBar = RtkMeetingControlBar( meeting: meeting, delegate: nil, presentingViewController: self ) controlBar.translatesAutoresizingMaskIntoConstraints = false view.addSubview(controlBar)
NSLayoutConstraint.activate([ header.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), header.leadingAnchor.constraint(equalTo: view.leadingAnchor), header.trailingAnchor.constraint(equalTo: view.trailingAnchor), header.heightAnchor.constraint(equalToConstant: 50), controlBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), controlBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), controlBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), controlBar.heightAnchor.constraint(equalToConstant: 80), ])
// Listen for meeting events selfListener = RtkEventSelfListener(rtkClient: meeting) selfListener?.observeSelfRemoved { [weak self] _ in self?.dismiss(animated: true, completion: self?.completion) } selfListener?.observeSelfMeetingEndForAll { [weak self] _ in self?.dismiss(animated: true, completion: self?.completion) } } }
Present the meeting with your custom flow delegate:
```swift
let delegate = CustomFlowDelegate()
let rtkUI = RealtimeKitUI(
meetingInfo: RtkMeetingInfo(
authToken: "<auth-token>",
enableAudio: true,
enableVideo: true
),
flowDelegate: delegate
)
let controller = rtkUI.startMeeting {
self.dismiss(animated: true)
}
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
Individual components available for custom meeting screens:
| Component | Description |
|---|---|
RtkMeetingHeaderView |
Header with meeting title, participant count, clock, and recording indicator |
RtkMeetingControlBar |
Control bar with microphone, camera, and end meeting buttons |
RtkParticipantTileView |
Single participant video tile with avatar fallback and name tag |
RtkVideoView |
Raw video rendering view |
RtkAvatarView |
Participant avatar with initials |
RtkNameTag |
Participant name with microphone status icon |
RtkAudioButtonControlBar |
Standalone microphone toggle button |
RtkVideoButtonControlBar |
Standalone camera toggle button |
RtkEndMeetingControlBarButton |
End/leave meeting button |
RtkEventSelfListener |
Observer for audio/video state, removal, reconnection, and permissions |
The following example creates a custom meeting screen using individual UI Kit views in an XML layout with the activate(meeting) pattern:
activity_custom_meeting.xmlxml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#000000">
<com.cloudflare.realtimekit.ui.view.headers.RtkMeetingHeaderView
android:id="@+id/headerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.cloudflare.realtimekit.ui.view.grid.RtkGridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.cloudflare.realtimekit.ui.view.controlbars.RtkMeetingControlBarView
android:id="@+id/controlBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
CustomMeetingActivity.ktkotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.cloudflare.realtimekit.RealtimeKitClient
import com.cloudflare.realtimekit.RtkMeetingRoomEventListener
import com.cloudflare.realtimekit.models.RtkMeetingInfo
import com.cloudflare.realtimekit.ui.RealtimeKitUIBuilder
import com.cloudflare.realtimekit.ui.RealtimeKitUIInfo
import com.cloudflare.realtimekit.ui.view.controlbars.RtkMeetingControlBarView
import com.cloudflare.realtimekit.ui.view.grid.RtkGridView
import com.cloudflare.realtimekit.ui.view.headers.RtkMeetingHeaderView
class CustomMeetingActivity : AppCompatActivity() {
private lateinit var headerView: RtkMeetingHeaderView
private lateinit var gridView: RtkGridView
private lateinit var controlBarView: RtkMeetingControlBarView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_meeting)
headerView = findViewById(R.id.headerView)
gridView = findViewById(R.id.gridView)
controlBarView = findViewById(R.id.controlBarView)
val meetingInfo = RtkMeetingInfo(authToken = "<auth-token>")
val uiKitInfo = RealtimeKitUIInfo(
activity = this,
rtkMeetingInfo = meetingInfo,
)
val rtkUI = RealtimeKitUIBuilder.build(uiKitInfo)
val meeting: RealtimeKitClient = rtkUI.meeting
// Listen for meeting lifecycle events
meeting.addMeetingRoomEventListener(object : RtkMeetingRoomEventListener {
override fun onMeetingRoomJoinCompleted(meeting: RealtimeKitClient) {
runOnUiThread {
// Activate all components once the meeting is joined
headerView.activate(meeting)
gridView.activate(meeting)
controlBarView.activate(meeting)
}
}
override fun onMeetingEnded() {
finish()
}
override fun onMeetingRoomLeaveCompleted() {
finish()
}
})
}
}
Individual components available for custom layouts:
| Component | Activate with | Description |
|---|---|---|
RtkGridView |
RealtimeKitClient |
Paginated participant video grid |
RtkMeetingHeaderView |
RealtimeKitClient |
Header with title, participant count, clock, and recording indicator |
RtkMeetingControlBarView |
RealtimeKitClient |
Control bar with mic, camera, more, and leave buttons |
RtkParticipantTileView |
RtkMeetingParticipant |
Single participant video tile with avatar and name tag |
RtkMicToggleButton |
RealtimeKitClient |
Standalone microphone toggle |
RtkCameraToggleButton |
RealtimeKitClient |
Standalone camera toggle |
RtkLeaveButton |
RealtimeKitClient |
Leave/end meeting button |
RtkAvatarView |
RtkMeetingParticipant |
Participant avatar |
RtkNameTagView |
RtkMeetingParticipant |
Name tag with audio indicator |
RtkJoinButton |
RealtimeKitClient |
Join meeting button for setup screens |
Note
You must manage layout and sizing for these components yourself. Use standard Android layout techniques (XML, ConstraintLayout, LinearLayout) to position components.
The following example uses RtkUIProvider and individual components with state-based screen rendering, similar to the web React pattern:
Caution
When building without RtkMeeting, you must manage state transitions yourself. Listen for roomJoined, roomLeft, and socket events on the meeting object, and call setStates to update storeStates.meeting between idle, setup, waiting, joined, and ended.
CustomMeeting.tsxtsx
import React, { useContext, useEffect } from "react";
import { View, Text } from "react-native";
import {
useRealtimeKitClient,
RealtimeKitProvider,
} from "@cloudflare/realtimekit-react-native";
import {
RtkUIProvider,
RtkUIContext,
RtkHeader,
RtkGrid,
RtkControlbar,
RtkSidebar,
RtkDialogManager,
RtkSetupScreen,
RtkWaitingScreen,
RtkEndedScreen,
} from "@cloudflare/realtimekit-react-native-ui";
function MeetingScreens({ meeting }) {
const { storeStates, setStates } = useContext(RtkUIContext);
const currentState = storeStates.meeting;
useEffect(() => {
// Listen for meeting events and drive state transitions
const onRoomJoined = () => {
setStates({ ...storeStates, meeting: "joined" });
};
const onRoomLeft = ({ state }) => {
if (state === "ended" || state === "left") {
setStates({ ...storeStates, meeting: "ended" });
}
};
meeting.self.addListener("roomJoined", onRoomJoined);
meeting.self.addListener("roomLeft", onRoomLeft);
return () => {
meeting.self.removeListener("roomJoined", onRoomJoined);
meeting.self.removeListener("roomLeft", onRoomLeft);
};
}, [meeting]);
if (currentState === "setup") {
return <RtkSetupScreen meeting={meeting} />;
}
if (currentState === "waiting") {
return <RtkWaitingScreen />;
}
if (currentState === "joined") {
return (
<View style={{ flex: 1 }}>
<RtkHeader meeting={meeting} />
<View style={{ flex: 1 }}>
<RtkGrid meeting={meeting} />
{storeStates.activeSidebar && <RtkSidebar meeting={meeting} />}
</View>
<RtkControlbar meeting={meeting} />
<RtkDialogManager meeting={meeting} />
</View>
);
}
if (currentState === "ended") {
return <RtkEndedScreen meeting={meeting} />;
}
return <Text>Loading...</Text>;
}
function CustomMeeting({ authToken }) {
const [meet, initMeeting] = useRealtimeKitClient();
const { setStates, storeStates } = useContext(RtkUIContext);
useEffect(() => {
initMeeting({
authToken,
defaults: { audio: true, video: true },
});
}, [authToken]);
useEffect(() => {
if (meet) {
setStates({ ...storeStates, meeting: "setup" });
}
}, [meet]);
if (!meet) return <Text>Initializing...</Text>;
return (
<RealtimeKitProvider value={meet}>
<MeetingScreens meeting={meet} />
</RealtimeKitProvider>
);
}
export default function App() {
return (
<RtkUIProvider>
<CustomMeeting authToken="<auth-token>" />
</RtkUIProvider>
);
}
First level split of RtkMeeting has the following components:
| Component | Required prop | Description |
|---|---|---|
RtkHeader |
meeting |
Header with meeting title, participant count, clock, and recording indicator |
RtkGrid |
meeting |
Paginated participant video grid |
RtkControlbar |
meeting |
Control bar with mic, camera, screenshare, and more toggles |
RtkSidebar |
meeting |
Sidebar with chat, polls, participants, and plugins panels |
RtkDialogManager |
meeting |
Manages all dialogs (settings, leave confirmation, join stage) |
RtkSetupScreen |
meeting |
Pre-join screen with video preview and device selection |
RtkWaitingScreen |
— | Waiting room screen |
RtkEndedScreen |
meeting |
Meeting ended screen |
RtkMicToggle |
meeting |
Standalone microphone toggle button |
RtkCameraToggle |
meeting |
Standalone camera toggle button |
RtkLeaveButton |
— | Leave/end meeting button |
RtkParticipantTile |
meeting, participant |
Single participant video tile |
RtkNotifications |
meeting |
Toast notifications for join/leave/chat events |
Note
Wrap your component tree in RtkUIProvider. All UI Kit components read design tokens and state from RtkUIContext. Without RtkUIProvider, components will not render correctly.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/build-your-own-ui/#page","headline":"Build Your Own UI","description":"Build a custom meeting interface video UI using RealtimeKit SDK components and Core SDK.","url":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/build-your-own-ui/","inLanguage":"en","image":"https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/build-your-own-ui/og.png?v=c556a149a1af1f54","dateModified":"2026-10-19","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Build a scalable chat panel for your custom RealtimeKit meeting UI with paginated UI Kit chat components. title: Build a Chat Panel image: https://developers.cloudflare.com/realtime/realtimekit/humans/ui-kit/chat-panel/og.png?v=ea17480b73fe9a5a
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/realtime/llms.txt
Use this file to discover all available pages before exploring further.
Docs