Tutorial
Follow the Android overlay flow for this SDK track. Shared behavior is shown in both versions. SDK 56 APIs stay on SDK 56 pages.
- Reference version
- SDK 56
- Bundled version
- 56.0.6
- Platform
- Android
Permission flow
- Call
canDrawOverlays(). - If it returns
false, callrequestPermission(). - When the app becomes active again, call
canDrawOverlays()one more time. - Call
showBubble()only after permission is granted.
Renderers
- Use
setBubbleRenderer()orsetBubbleRendererForBubble()for React Native renderers. - Use
setComposeBubbleRenderer()for the packaged Compose-flavored renderer.
SDK 56 additions
- FloatingWindowPreview for rendering a bubble fixture inside the app before starting the overlay service.
- Edge hide controls for tucking a bubble against the screen edge.
- Close helpers that release overlay surfaces when a window is done.
- Native-backed numeric shared values for counters and window state that need to stay fresh after backgrounding.
- ReactNativeWindowContainer for Reanimated width, height, and radius transitions with system light/dark defaults.
- NativeWindowContainer for an Expo UI Android Host/Surface backdrop with React Native children.
Use FloatingWindowPreview
Render the same bubble renderer inside your app while you tune layout and state. This does not ask for overlay permission or start the Android overlay service.
import { FloatingWindowPreview } from 'expo-draw-over-apps';
import { CounterBubble } from './CounterBubble';
export function BubblePreviewCard() {
return (
<FloatingWindowPreview
width={220}
height={220}
bubbleId="counter"
renderBubble={(props) => <CounterBubble {...props} />}
/>
);
}
Use edge hide
Pass edgeHideEnabled when the bubble is shown, or change it later for the same named bubble.
import { setEdgeHideEnabled, showBubble } from 'expo-draw-over-apps';
const bubbleId = 'counter';
await showBubble(bubbleId, { edgeHideEnabled: true });
function toggleEdgeHide(nextEnabled: boolean) {
setEdgeHideEnabled(nextEnabled, bubbleId);
}
Use ReactNativeWindowContainer
Wrap React Native renderer content when the bubble size or corner radius changes. Numeric width, height, and borderRadius values animate with Reanimated.
import { Pressable, Text } from 'react-native';
import {
ReactNativeWindowContainer,
setOverlaySharedValue,
useOverlaySharedValueState,
type BubbleRendererProps,
} from 'expo-draw-over-apps';
const resizeKey = 'window-container-counter';
export function ResizeBubble({ close }: BubbleRendererProps) {
const sharedStep = useOverlaySharedValueState(resizeKey);
const expanded = sharedStep.value >= 1;
function toggleSize() {
setOverlaySharedValue(resizeKey, expanded ? 0 : 1, 'bubble');
}
return (
<ReactNativeWindowContainer
width={expanded ? 240 : 160}
height={expanded ? 220 : 140}
borderRadius={expanded ? 36 : 24}
>
<Text style={{ color: 'white', fontWeight: '800' }}>Custom window</Text>
<Pressable onPress={toggleSize}>
<Text style={{ color: 'white' }}>Resize</Text>
</Pressable>
<Pressable onPress={close}>
<Text style={{ color: 'white' }}>Close</Text>
</Pressable>
</ReactNativeWindowContainer>
);
}
Use NativeWindowContainer
Use the native container when the app has @expo/ui installed and you want the Android Host/Surface backdrop. It still renders React Native children inside the window and falls back to the React Native container when Expo UI is unavailable.
import { Pressable, Text } from 'react-native';
import {
NativeWindowContainer,
setOverlaySharedValue,
useOverlaySharedValueState,
type BubbleRendererProps,
} from 'expo-draw-over-apps';
const resizeKey = 'window-container-counter';
export function NativeSurfaceBubble({ close }: BubbleRendererProps) {
const sharedStep = useOverlaySharedValueState(resizeKey);
const expanded = sharedStep.value >= 1;
function toggleSize() {
setOverlaySharedValue(resizeKey, expanded ? 0 : 1, 'bubble');
}
return (
<NativeWindowContainer
width={expanded ? 260 : 172}
height={expanded ? 230 : 150}
borderRadius={expanded ? 40 : 26}
>
<Text style={{ color: 'white', fontWeight: '800' }}>
Native surface
</Text>
<Pressable onPress={toggleSize}>
<Text style={{ color: 'white' }}>Resize</Text>
</Pressable>
<Pressable onPress={close}>
<Text style={{ color: 'white' }}>Close</Text>
</Pressable>
</NativeWindowContainer>
);
}