Skip to main content

Back Navigation

Handle the Android back button and iOS swipe gesture correctly with the channel's internal navigation.

Quick Start: Automatic​

The simplest approach — let the SDK handle everything:

<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
handleBackButton={true}
onMediaRequest={(mediaInfo) => setMedia(mediaInfo)}
/>

With handleBackButton={true}, the SDK automatically:

  1. Closes the fullscreen player if open
  2. Navigates back within the channel (detail page -> main)
  3. Falls through to the system default (exit app) when on the main page

This is sufficient for most full-screen channel setups. For anything more complex, use manual handling.

Manual: Using Refs​

For custom back logic — e.g., integration with React Navigation, confirmation dialogs, or multi-step flows — handle back navigation manually.

import { BackHandler } from 'react-native';

function ChannelScreen() {
const channelRef = useRef<BBChannelRef>(null);
const [media, setMedia] = useState<BBMediaInfo | null>(null);
const canGoBackRef = useRef(false);

useEffect(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
// Priority 1: Close fullscreen player
if (media) {
setMedia(null);
return true;
}
// Priority 2: Channel internal navigation
if (canGoBackRef.current) {
channelRef.current?.goBack();
return true;
}
// Priority 3: Let system handle it
return false;
});
return () => handler.remove();
}, [media]);

return (
<>
<BBChannel
ref={channelRef}
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
onCanGoBackChange={(canGoBack) => {
canGoBackRef.current = canGoBack;
}}
onMediaRequest={(mediaInfo) => setMedia(mediaInfo)}
/>
<BBFullscreenPlayer
media={media}
visible={media !== null}
onClose={() => setMedia(null)}
/>
</>
);
}

Why Refs Instead of State?​

This is a common pitfall. Consider this broken example:

// BAD: canGoBack will be stale in the BackHandler callback
const [canGoBack, setCanGoBack] = useState(false);

useEffect(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
// canGoBack is captured at the time this effect ran.
// Rapid back presses may read an outdated value!
if (canGoBack) {
channelRef.current?.goBack();
return true;
}
return false;
});
return () => handler.remove();
}, []); // Empty deps = canGoBack is always false here

The BackHandler listener captures the canGoBack value from when the effect ran. Even if you add canGoBack to the dependency array, there's a brief window during re-registration where a back press could be missed.

Refs solve this because canGoBackRef.current always returns the latest value:

// GOOD: ref always has the current value
const canGoBackRef = useRef(false);

useEffect(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
if (canGoBackRef.current) { // Always current
channelRef.current?.goBack();
return true;
}
return false;
});
return () => handler.remove();
}, []); // No dependency needed

Priority Order​

When the back button is pressed, handle events in this order:

  1. Close fullscreen player — If a player is open in fullscreen/modal
  2. Channel back navigation — If the channel is on a sub-page (detail, overview, search)
  3. App navigation — Pop the screen, switch tabs, or exit
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
// 1. Player
if (media) {
setMedia(null);
return true;
}
// 2. Channel
if (canGoBackRef.current) {
channelRef.current?.goBack();
return true;
}
// 3. App (return false to let React Navigation or system handle it)
return false;
});

React Navigation Coordination​

When the channel is inside a React Navigation stack or tab navigator, use useFocusEffect to register the back handler only when the screen is focused:

import { useFocusEffect } from '@react-navigation/native';

function VideoScreen({ navigation }) {
const channelRef = useRef<BBChannelRef>(null);
const canGoBackRef = useRef(false);

useFocusEffect(
React.useCallback(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
if (canGoBackRef.current) {
channelRef.current?.goBack();
return true;
}
// Let React Navigation handle it (pop screen, switch tab, etc.)
return false;
});

return () => handler.remove();
}, [])
);

return (
<BBChannel
ref={channelRef}
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
onCanGoBackChange={(val) => {
canGoBackRef.current = val;
}}
/>
);
}
warning

Do not use handleBackButton={true} together with your own BackHandler. They will conflict. Choose one approach.

iOS Swipe Gesture​

On iOS, users expect to swipe from the left edge to go back. When the channel has internal navigation, disable the native swipe gesture to prevent conflicts:

import { useFocusEffect } from '@react-navigation/native';

function VideoScreen({ navigation }) {
const canGoBackRef = useRef(false);

// Disable iOS swipe-back when channel can go back
useFocusEffect(
React.useCallback(() => {
navigation.setOptions({
gestureEnabled: !canGoBackRef.current,
});
}, [])
);

return (
<BBChannel
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
onCanGoBackChange={(canGoBack) => {
canGoBackRef.current = canGoBack;
navigation.setOptions({ gestureEnabled: !canGoBack });
}}
/>
);
}

When canGoBack is true, the iOS swipe gesture is disabled so swiping doesn't pop the screen while the user is on a detail page. When they return to the main page, the gesture is re-enabled.

Custom Header Back Button​

If you have a custom header with a back button:

function VideoScreen() {
const channelRef = useRef<BBChannelRef>(null);
const [canGoBack, setCanGoBack] = useState(false);

const handleBackPress = () => {
if (canGoBack) {
channelRef.current?.goBack();
} else {
navigation.goBack();
}
};

return (
<View style={{ flex: 1 }}>
<View style={styles.header}>
<TouchableOpacity onPress={handleBackPress}>
<Text>{canGoBack ? '← Back' : '← Close'}</Text>
</TouchableOpacity>
</View>
<BBChannel
ref={channelRef}
channelUrl="https://demo.bbvms.com/ch/channel_name.json"
safeAreaEdges={[]}
onCanGoBackChange={setCanGoBack}
/>
</View>
);
}

Internal Implementation​

The SDK maintains its own navigation history stack, ensuring reliable back navigation across all channel pages.