Examples¶
These examples mirror the sample apps in this repository and focus on the patterns you are most likely to reuse.
Activity-Owned Overlay¶
This is the smallest useful setup: create the window once, set content once, and toggle visibility from the activity.
class MainActivity : AppCompatActivity() {
private val floatingWindow by lazy {
ComposeFloatingWindow(applicationContext).apply {
setContent {
FloatingActionButton(
modifier = Modifier.dragFloatingWindow(),
onClick = { /* Open something */ },
) {
Icon(Icons.Default.Done, contentDescription = "Show dialog")
}
}
}
}
private fun showOverlay() {
if (floatingWindow.isAvailable()) {
floatingWindow.show()
} else {
requestOverlayPermission(this)
}
}
private fun hideOverlay() {
floatingWindow.hide()
}
override fun onDestroy() {
floatingWindow.close()
super.onDestroy()
}
}
Use this shape when the overlay is a companion to an activity screen and does not need to survive process-local navigation.
Service-Owned Overlay¶
For overlays that should keep running after an activity goes away, create the window in a Service.
class MyService : Service() {
private val floatingWindow by lazy {
ComposeServiceFloatingWindow(this).apply {
setContent {
FloatingActionButton(
modifier = Modifier.dragServiceFloatingWindow(),
onClick = { /* Service action */ },
) {
Icon(Icons.Default.Call, contentDescription = "Action")
}
}
}
}
override fun onCreate() {
super.onCreate()
floatingWindow.show()
}
override fun onDestroy() {
floatingWindow.close()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
This is the right model for chat heads, quick tools, and utility overlays that need an independent lifecycle.
Dialog From The Overlay¶
Use SystemAlertDialog when you want a Material-styled prompt above the overlay.
@Composable
fun FloatingScreen(vm: FloatingViewModel = viewModel()) {
val showing by vm.dialogVisible.collectAsStateWithLifecycle()
if (showing) {
SystemAlertDialog(
onDismissRequest = vm::dismissDialog,
confirmButton = {
TextButton(onClick = vm::dismissDialog) {
Text("OK")
}
},
text = { Text("This is a system dialog") },
)
}
FloatingActionButton(
modifier = Modifier.dragFloatingWindow(),
onClick = vm::showDialog,
) {
Icon(Icons.Default.Done, contentDescription = null)
}
}
Full-Screen Custom Dialog¶
Use SystemDialog when the alert-dialog slot API is too restrictive.
if (showingDialog) {
SystemDialog(
onDismissRequest = { showingDialog = false },
properties = SystemDialogProperties(
usePlatformDefaultWidth = false,
),
) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("This is a full-screen dialog")
Button(onClick = { showingDialog = false }) {
Text("Dismiss")
}
}
}
}
}
Text Input Inside An Overlay¶
If your overlay contains TextField or OutlinedTextField, use the provided interaction source.
@Composable
fun OverlayInput() {
var text by rememberSaveable { mutableStateOf("") }
val interactionSource = rememberFloatingWindowInteractionSource()
OutlinedTextField(
value = text,
onValueChange = { text = it },
interactionSource = interactionSource,
label = { Text("Type something") },
)
}
For a service-based overlay, switch to rememberServiceFloatingWindowInteractionSource().
Persist Window Position¶
The drag modifiers expose the current position through onDrag, so persisting coordinates is straightforward.
FloatingActionButton(
modifier = Modifier.dragFloatingWindow(
onDrag = { left, top ->
savedPosition = left to top
},
),
onClick = { /* ... */ },
) {
Icon(Icons.Default.Done, contentDescription = null)
}
Restore those coordinates before showing the window again:
val overlay = ComposeFloatingWindow(applicationContext).apply {
windowParams.x = savedPosition.first
windowParams.y = savedPosition.second
setContent { OverlayContent() }
}
The samples/service-hilt module demonstrates the same idea with a repository-backed value.