# How Back Button Handling Works Differently on Android vs iOS in Muse

> Explore how Muse handles back button presses differently on Android and iOS. Discover the Kotlin Multiplatform implementation and platform-specific approaches for seamless navigation.

- Repository: [Ko Shin/muse](https://github.com/kkoshin/muse)
- Tags: deep-dive
- Published: 2026-03-05

---

**Muse implements back button handling via a Kotlin Multiplatform `expect` function that intercepts hardware back presses on Android using `OnBackPressedDispatcher`, while the iOS implementation is intentionally a no-op because the platform lacks a system back button.**

The [Muse](https://github.com/kkoshin/muse) repository demonstrates how **back button handling is implemented differently on Android versus iOS** within a shared Compose Multiplatform codebase. The solution uses Kotlin's `expect`/`actual` mechanism to provide platform-specific behavior while exposing a uniform API to common UI code.

## Common API Declaration

The shared interface resides in [`muse/src/commonMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.kt). It declares an `expect` composable function that accepts a lambda to execute when a back action occurs.

```kotlin
package io.github.kkoshin.muse.platformbridge

import androidx.compose.runtime.Composable

@Composable
expect fun BackHandler(onBack: () -> Unit)

```

This declaration allows any composable in the common source set to register back-navigation logic without knowing platform specifics.

## Android Implementation

In [`muse/src/androidMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.android.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/androidMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.android.kt), the `actual` implementation hooks into the Android activity's back-press dispatcher. It uses `LocalOnBackPressedDispatcherOwner` to obtain the current dispatcher and registers an `OnBackPressedCallback` via a `DisposableEffect`.

```kotlin
package io.github.kkoshin.muse.platformbridge

import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.activity.OnBackPressedCallback
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect

@Composable
actual fun BackHandler(onBack: () -> Unit) {
    val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
    DisposableEffect(dispatcher) {
        val callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                onBack()
            }
        }
        dispatcher?.addCallback(callback)
        onDispose {
            callback.remove()
        }
    }
}

```

The **enabled** flag set to `true` ensures the callback is active immediately. When the user presses the hardware back button, Android invokes `handleOnBackPressed()`, executing the shared lambda provided by the caller. The `DisposableEffect` guarantees cleanup when the composable leaves the composition, preventing memory leaks.

## iOS Implementation

Conversely, [`muse/src/iosMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.ios.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/iosMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.ios.kt) provides a no-op implementation. Because iOS devices do not expose a system back button (navigation relies on swipe gestures or custom UI elements), the `actual` function body is empty.

```kotlin
package io.github.kkoshin.muse.platformbridge

import androidx.compose.runtime.Composable

@Composable
actual fun BackHandler(onBack: () -> Unit) {
    // iOS does not have a system back button in the same way as Android.
    // No-op implementation.
}

```

This design allows shared code to call `BackHandler` unconditionally on both platforms without compilation errors or runtime crashes on iOS.

## Using BackHandler in Shared Code

Developers consume the API identically across platforms. The following snippet demonstrates a settings screen that intercepts back navigation on Android while remaining harmless on iOS.

```kotlin
@Composable
fun SettingsScreen(onClose: () -> Unit) {
    BackHandler {
        // Custom back logic executes only on Android
        onClose()
    }
    // UI content...
}

```

On Android, pressing the hardware back button triggers `onClose()`. On iOS, the composable renders without side effects, leaving navigation to the SwiftUI hosting container or custom gesture handlers.

## Summary

- **Common contract**: [`BackHandler.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.kt) declares an `expect fun` providing a platform-agnostic API.
- **Android behavior**: [`BackHandler.android.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.android.kt) registers an `OnBackPressedCallback` with the activity dispatcher to intercept hardware back presses.
- **iOS behavior**: [`BackHandler.ios.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.ios.kt) implements an empty function because iOS lacks a system back button equivalent.
- **Integration**: UI code in `commonMain` calls `BackHandler` once; the Kotlin Multiplatform toolchain resolves the correct implementation at compile time.

## Frequently Asked Questions

### Why is the iOS implementation a no-op?

iOS devices do not provide a hardware back button; navigation occurs via swipe gestures or custom toolbar buttons managed by SwiftUI or UIKit. Therefore, the iOS `actual` implementation in [`BackHandler.ios.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.ios.kt) intentionally performs no action, avoiding conflicts with the platform's native navigation stack.

### How does Android intercept the hardware back button?

The Android implementation leverages `LocalOnBackPressedDispatcherOwner` to access the current activity's `OnBackPressedDispatcher`. It creates an enabled `OnBackPressedCallback` inside a `DisposableEffect` (located in [`BackHandler.android.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.android.kt)), which Android invokes when the user presses the hardware back key.

### Can I use BackHandler on iOS for custom swipe-back gestures?

No. The current implementation in [`BackHandler.ios.kt`](https://github.com/kkoshin/muse/blob/main/BackHandler.ios.kt) is a hard-coded no-op. To handle iOS-specific gestures, you must implement separate expect/actual pairs or use platform-specific SwiftUI modifiers outside the shared `BackHandler` API.

### Where is the common BackHandler API defined?

The shared `expect` declaration resides in [`muse/src/commonMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/platformbridge/BackHandler.kt). This file serves as the single source of truth for the API signature used across both Android and iOS source sets.