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.27k stars 1.18k forks source link

[Question] Is it possible to create a LifecycleRegistry on Desktop using `kotlinx-coroutines-swing` dependency but without having to call the `createUnsafe` function? #4667

Closed luanrodrigsr closed 6 months ago

luanrodrigsr commented 6 months ago

Describe the bug Hi!

I'm trying to achieve something like this:

abstract class Application : ViewModelStoreOwner(), LifecycleOwner {
     private lateinit var lifecycleRegistry: LifecycleRegistry

     // ...

     private fun createLifecycle() {
          lifecycleRegistry = LifecycleRegistry(this)
     }

     private fun create() {
          if (lifecycle.currentState == Lifecycle.State.INITIALIZED) {
               lifecycleRegistry.currentState = Lifecycle.State.CREATED

               log.d { "Application created!" }

               onCreate()
        }
    }

     // ...

     companion object {
          internal inline fun <reified T : Application> create(): T {
               val instance = T::class.createInstance()

               instance.createLifecycle()
               instance.create()

               return instance
          } 
     }
}

The code works pretty well, but then when i add the org.jetbrains.kotlinx:kotlinx-coroutines-swing dependency to the commonMain, the line where i need to update the lifecycle state doesn't work (lifecycleRegistry.currentState = Lifecycle.State.CREATED). It only works if i call the function LifecycleRegistry.createUnsafe(this) on the createLifecycle method.

Maybe it got something to do with the thread, but i'm not sure. 😅

Is it possible to create a LifecycleRegistry on Desktop using kotlinx-coroutines-swing dependency but without having to call the createUnsafe function?

Thank you!

Affected platforms

Versions

Additional context Lifecycle ViewModel Compose version: 2.8.0-alpha01 Navigation Compose version: 2.8.0-alpha01

pablichjenkov commented 6 months ago

kotlinx-coroutines-swing should go in jvmMain/desktopMain dependecies block.

luanrodrigsr commented 6 months ago

Hey @pablichjenkov, thanks! I updated my build.gradle.kts files, but still couldn't get the code to work.

Captura de tela 2024-04-20 235522 Captura de tela 2024-04-21 000051

pablichjenkov commented 6 months ago

I think you misunderstood my previous comment. You have either jvmMain or desktopMain not both. Need to add swing dependecy in one of them. Are you using jvmMain or desktopMain?

luanrodrigsr commented 6 months ago

Oh. I tried both of them, separately. Not at the same time.

I am using desktopMain right now, by the way.

pablichjenkov commented 6 months ago

Oh. I tried both of them, separately. Not at the same time.

I am using desktopMain right now, by the way.

Ah ok sorry I misunderstood you. Can you post the errors you are getting

luanrodrigsr commented 6 months ago

The code after the lifecycleRegistry.currentState = Lifecycle.State.CREATED line is not being executed. Which is preventing the "Application" for being created. It only happens when i use the kotlinx-coroutines-swing dependency.

Captura de tela 2024-04-21 204421

But, when i updated the code on line 75 to LifecycleRegistry.createUnsafe(this), it works fine. (Pay attention to the debug panel which shows the enforceMainThread field set to false. Looking at the source code, this parameter is present in the private constructor of the LifecycleRegistry, that is (i believe) supposed to be always true).

Captura de tela 2024-04-21 204704

The documentation says that the method createUnsafe:

Creates a new LifecycleRegistry for the given provider, that doesn't check that its methods are called on the threads other than main. LifecycleRegistry is not synchronized: if multiple threads access this LifecycleRegistry, it must be synchronized externally.

Considering all this, I may not be fully aware of the implications that using the createUnsafe method can bring. Except for the fact that "if multiple threads access this LifecycleRegistry, it must be synchronized externally".

luanrodrigsr commented 6 months ago

To add more context, what I'm trying to achieve is to create a custom implementation of the LifecycleOwner interface in an abstract Application class that will be responsible for controlling the lifecycle of the desktop app (outside of compose). I needed to add the kotlinx-coroutines-swing dependency because of the "Module with the Main dispatcher is missing" (#4653) error.

pablichjenkov commented 6 months ago

I see, Humm 🤔 seems like a problem inside LifecycleRegistry constructor. You are basically having a silent crash or so. It is hard to predict with logs or more information. I would start by checking the implementation of LifecycleRegistry.

luanrodrigsr commented 6 months ago

Thank you. I will try to dig into the implementation.

I just wanna note that the enforceMainThread being set to false on the example i gave is working as intended, I think, since it is the role of the createUnsafe method.

This makes me wonder if the right way to create a LifecycleRegistry, in my case, wasn't using the public constructor which sets this field to true . I am just too worried about the inability to do so. I mean, the lifecycle registry is created, but I'm unable to interact with it.

kropp commented 6 months ago

Hi @luanrodrigsr I confirm that this is a bug in Lifecycle implementation for desktop target. The good news is that it is already fixed, however, the fix is not yet published. I tried your snippet on the newer version and it now works. Currently, I would expect it to be available Lifecycle 2.8.0-rc01 release and the subsequent Compose 1.6.10-rc01 release.

luanrodrigsr commented 6 months ago

Thank you! @kropp

okushnikov commented 4 months ago

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