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"?>
&lt;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">

&lt;com.cloudflare.realtimekit.ui.view.headers.RtkMeetingHeaderView
        android:id="@+id/headerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

&lt;com.cloudflare.realtimekit.ui.view.grid.RtkGridView
        android:id="@+id/gridView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

&lt;com.cloudflare.realtimekit.ui.view.controlbars.RtkMeetingControlBarView
        android:id="@+id/controlBarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

&lt;/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 = "&lt;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 &lt;RtkSetupScreen meeting={meeting} />;
    }

if (currentState === "waiting") {
        return &lt;RtkWaitingScreen />;
    }

if (currentState === "joined") {
        return (
            &lt;View style={{ flex: 1 }}>
                &lt;RtkHeader meeting={meeting} />
                &lt;View style={{ flex: 1 }}>
                    &lt;RtkGrid meeting={meeting} />
                    {storeStates.activeSidebar && &lt;RtkSidebar meeting={meeting} />}
                &lt;/View>
                &lt;RtkControlbar meeting={meeting} />
                &lt;RtkDialogManager meeting={meeting} />
            &lt;/View>
        );
    }

if (currentState === "ended") {
        return &lt;RtkEndedScreen meeting={meeting} />;
    }

return &lt;Text>Loading...&lt;/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 &lt;Text>Initializing...&lt;/Text>;

return (
        &lt;RealtimeKitProvider value={meet}>
            &lt;MeetingScreens meeting={meet} />
        &lt;/RealtimeKitProvider>
    );
}

export default function App() {
    return (
        &lt;RtkUIProvider>
            &lt;CustomMeeting authToken="&lt;auth-token>" />
        &lt;/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

Build Your Own UI·续2 - 图1Docs

{"@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


Skip to content

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.