Skip to content

Instantly share code, notes, and snippets.

A [Ambient provided with value: X]
└── B ...
└── C [Ambient overridden with value: Y]
└── D ...
└── E [Ambient read: Y]
A [root back handler]
└── B ...
└── C [back handler with fallback to last ambient (A)]
└── D ...
└── E [back handler with fallback to last ambient (C)]
class ScopedBackPressHandler {
var children = mutableListOf<() -> Boolean>()
fun handle(): Boolean =
children.reversed().any { it() }
}
val backPressHandler: Ambient<ScopedBackPressHandler> =
Ambient.of { throw IllegalStateException("backPressHandler is not initialized") }
@Composable
fun <T> BackHandler(routing: T, children: @Composable() (BackStack<T>) -> Unit) {
// grab the parent handler, wherever it's up the tree
val upstream = +ambient(backPressHandler)
// to keep track of children down the tree
val downstream = +memo { ScopedBackPressHandler() }
// let's have a default back stack on this level
val backStack = BackStack(routing)
// our lambda for handling back press: asking all children,
val handleBackPressHere: () -> Boolean = { downstream.handle() || backStack.pop() }
val handleBackPressHere: () -> Boolean = { downstream.handle() || backStack.pop() }
@Composable
fun Content() {
BackHandler(Routing.AlbumList as Routing) { backStack ->
when (val currentRouting = backStack.last()) {
// all Content() on the right hand side are @Composable
is Routing.AlbumList -> AlbumList.Content()
is Routing.PhotosOfAlbum -> PhotosOfAlbum.Content(currentRouting.album)
is Routing.FullScreenPhoto -> FullScreenPhoto.Content(currentRouting..photo)
}
@Composable
fun RootBackHandler(rootHandler: ScopedBackPressHandler, children: @Composable() () -> Unit) {
val downstream = +memo { ScopedBackPressHandler() }
val handleBackPressHere: () -> Boolean = { downstream.handle() }
rootHandler.handlers.add(handleBackPressHere)
+onDispose { rootHandler.handlers.remove(handleBackPressHere) }
backPressHandler.Provider(value = downstream) {
children()
}
class MainActivity : AppCompatActivity() {
private val rootHandler = ScopedBackPressHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
RootBackHandler(rootHandler) {
Root.Content(LoggedOut)
}
A [back stack of 1 element]
└── B [back stack of 2 elements]
└── C [back stack of 4 elements]
└── D [back stack of 1 element]