eygraber
01/14/2024, 5:49 PMBackHandler
?eygraber
01/14/2024, 5:58 PMLocalOnBackPressedDispatcherOwner
, OnBackPressedDispatcher
, et...)Pablichjenkov
01/14/2024, 6:05 PMeygraber
01/14/2024, 6:14 PMpublic class OnBackPressedDispatcher {
public interface Handler {
var isEnabled: Boolean
fun onBackPressed()
}
private val handlers = mutableListOf<Handler>()
public fun addHandler(handler: Handler) {
handlers += handler
}
public fun removeHandler(handler: Handler) {
// optimizing as it's likely that the
// handler being removed was added more recently
val index = handlers.lastIndexOf(handler)
if(index >= 0) handlers.removeAt(index)
}
public fun onBackPressed() {
handlers
.lastOrNull { it.isEnabled }
?.onBackPressed()
}
}
public object LocalOnBackPressedDispatcher {
private val LocalOnBackPressedDispatcher =
compositionLocalOf<OnBackPressedDispatcher?> { null }
public val current: OnBackPressedDispatcher?
@Composable
get() = LocalOnBackPressedDispatcher.current
public infix fun provides(dispatcherOwner: OnBackPressedDispatcher):
ProvidedValue<OnBackPressedDispatcher?> {
return LocalOnBackPressedDispatcher.provides(dispatcherOwner)
}
}
@Composable
fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
val currentOnBack by rememberUpdatedState(onBack)
val handler = remember {
object : OnBackPressedDispatcher.Handler {
override var isEnabled: Boolean = enabled
override fun onBackPressed() {
currentOnBack()
}
}
}
// On every successful composition, update the callback with the `enabled` value
SideEffect {
handler.isEnabled = enabled
}
val backPressDispatcher = checkNotNull(LocalOnBackPressedDispatcher.current) {
"No OnBackPressedDispatcher was provided via LocalOnBackPressedDispatcher"
}
DisposableEffect(backPressDispatcher) {
backPressDispatcher.addHandler(handler)
onDispose {
backPressDispatcher.removeHandler(handler)
}
}
}
BackPressManager
OnBackPressedDispatcher
would expose some way for platforms to dispatch a back press (custom in desktop, observing popstate
in web, etc...).
The interesting thing about web is that you only get notified that a back press happened after window.history
has already been mutated, so if you want to "swallow" the back event you have to have your topmost "component" call window.history.pushState
with the same info that it had before the popstate
event.Pablichjenkov
01/14/2024, 6:24 PMPablichjenkov
01/14/2024, 6:35 PMeygraber
01/14/2024, 6:40 PMpopstate
event) but there's no way to intercept it; you can only react to it.
I'm planning on building something small for desktop that handles mouse back and forward clicks (although my last experiment on this shows that at least in Compose the keys for those can vary by OS) and then behaves similarly to web.
I'm not too familiar with iOS so I'm hoping whatever I build will just work with whatever they have (even if it might not be as elegant as the other platforms).eygraber
01/14/2024, 6:41 PMeygraber
01/14/2024, 6:43 PMBackHandler
and the compose multiplatform implementations calls the BackHandler
posted above:
@Composable
public expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
eygraber
01/14/2024, 6:44 PMPablichjenkov
01/14/2024, 6:46 PMeygraber
01/14/2024, 6:54 PM/users/1/widgets
results in the stack being [users, users/1]
)
• Web - "up" pushes the logical parent of the current screen onto the stack (e.g. going "up" from /users/1/widgets
results in the stack being [users, users/1, users/1/widgets, users/1]
)eygraber
01/14/2024, 6:56 PMPablichjenkov
01/14/2024, 6:57 PMeygraber
01/14/2024, 6:59 PMPablichjenkov
01/14/2024, 7:03 PM