JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.21k stars 1.17k forks source link

Quit menu / Command-q terminate compose app with no "compose" way of reacting to it #754

Closed ypujante closed 1 month ago

ypujante commented 3 years ago

On macOS, when you start a compose desktop app, there is a menu that is automatically created containing the "Quit" menu item. There is also a shortcut key (Command-q) associated to it. Selecting this action, terminates the app immediately with no current obvious way to be able to react to the event to do some cleanup like shutting down a database...

From my understanding on the slack channel, this is due to the fact compose desktop uses Swing. I was told to add a swing listener and directed to this stack overflow question.

I do not believe this is the right way to handle it because it surfaces what is an implementation detail. All of my application code is using compose concepts and at no point in time, I am aware that I am writing a Swing application. I am not sure why it should be surfaced.

I think compose desktop should handle this use case in a proper manner. I could imagine that using something like:

application(onQuit = { ... }) {
//  ...
}

would be a very natural way to handle this use case which is not a rare use case but pretty much what every developer who writes an actual app will face. For example, the notepad tutorial app which was just added is pretty careful to ask the user to save their work if you close the window by using the close icon of the window. But if you hit the quit menu, all the work is lost.

I also think that at the very minimum, the tutorial about window management should talk about this use case and explain how to do it in the compose world, whether being a compose built-in mechanism or a direct swing call.

ypujante commented 3 years ago

This is a potential implementation:

@ExperimentalComposeUiApi
fun application(
    context: CoroutineContext = EmptyCoroutineContext,
    onQuit: (() -> Boolean)? = null,
    content: @Composable () -> Unit
) {
    check(!SwingUtilities.isEventDispatchThread()) {
        "application can't run inside UI thread (Event Dispatch Thread)"
    }

    if(onQuit != null) {
        if(Desktop.isDesktopSupported()) {
          val desktop = Desktop.getDesktop()
          desktop.setQuitHandler { _, response ->
            if(onQuit())
                response.performQuit()
              else
                response.cancelQuit()
          }
        }
    }

    runBlocking(context) {
        awaitApplication(content = content)
    }
}
okushnikov commented 2 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.