Are there any multiplatform implementations of And...
# compose-desktop
e
Are there any multiplatform implementations of Android's
BackHandler
?
To be specific, not the underlying back handling mechanism from Android, but interfaces for it that can have platform specific behavior (e.g.
LocalOnBackPressedDispatcherOwner
,
OnBackPressedDispatcher
, et...)
p
I kinda did my own but is highly tied to my project architecture. Basically doing what you mentioned above. I would really like official support for it. The same for the lifecycle runtime library, waiting for ir so bad.
e
A quick attempt at without too much thought going into naming yet:
Copy code
public 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.
p
That is basically it. I think you can check the code is open. I might end up using your class
The major challenge I see in other platforms is that they don't have this concept of a global back press like we do in Android. iOS Apps are more into handling per screen back events. The web perhaps aligns better since browser comes with built in back/forward buttons
e
Web does kind of have a global back press (through the
popstate
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).
I updated the code above to match the naming on Android and used a class with an implementation instead of an interface
The usage would be through something like this, where the Android implementation calls through to the androidx
BackHandler
and the compose multiplatform implementations calls the
BackHandler
posted above:
Copy code
@Composable
public expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
Could probably work it around so that Android could be unified behind the expect/actual, but going to hold off on that for now
p
Got it, Ah man I thought you could intercept the back press from regular JavaScripts API. Sad is not the case, for Desktop I really think there are no UI standards for Desktop Apps yet but having a TopBar with a Global BackButton/BackPressHandler is pretty useful and handy in my opinion.
e
Yeah web is giving me a lot of headaches 😅 For desktop it really depends on each application's needs. My plan is to have a TopBar with an "up" action, and intercept back and forward key events to handle "back" and "forward" actions. Based on how web and Android work, "back" is straightforward. "up" has different behavior based on the platform (these aren't hard rules but I've observed them in some well architected apps and I'm going to follow them for my apps): • Mobile - "up" replaces the stack with the logical parent path of the current screen (e.g. going "up" from
/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]
)
What's important for me is to get out of the Android mindset of navigation only moving backwards one screen at a time. Web needs to be able to handle going forward, jumping multiple screens at once, etc... but at least it's a superset of Android, so designing for Web should also cover Android
p
Same opinion here, I tried to implement "up", noticed the different behavior per App and preferred just implementing "back" for the time being.
e
I'm working on this for an opinionated KMP app framework. Hopefully this will all abstract nicely in it. I'll share when it's ready (hopefully this week)
❤️ 1
p
Great! I really like the concept of Global Back/Forward. Hopefully all platforms engage it.
902 Views