Skip to main content
Home/versions/latest
Latest stable docs

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

  1. Call canDrawOverlays().
  2. If it returns false, call requestPermission().
  3. When the app becomes active again, call canDrawOverlays() one more time.
  4. Call showBubble() only after permission is granted.

Renderers

  • Use setBubbleRenderer() or setBubbleRendererForBubble() 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 overlay shared values

Store small numeric values in native state when app controls and overlay controls both need to update the same value. The example app uses this path for counters, timers, and the shared resize-window step.

import { Pressable, Text } from 'react-native';
import {
refreshAllOverlaySharedValueStates,
setOverlaySharedValue,
useOverlaySharedValueState,
} from 'expo-draw-over-apps';

const valueKey = 'window-container-counter';

export function ResizeControls() {
const state = useOverlaySharedValueState(valueKey);
const isLarge = state.value >= 1;

function toggleSize() {
setOverlaySharedValue(valueKey, isLarge ? 0 : 1, 'app');
}

return (
<Pressable onPress={toggleSize}>
<Text>{isLarge ? 'Use small window' : 'Use big window'}</Text>
</Pressable>
);
}

function refreshAfterForeground() {
refreshAllOverlaySharedValueStates();
}

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>
);
}