Skip to content

Desktop -> 창을 다른 모니터로 이동시 죽는 케이스 수정#486

Merged
workspace merged 1 commit into
droidknights:2025/compose-multiplatformfrom
easternkite:fix/#470-fix_crash_on_desktop
Jun 4, 2025
Merged

Desktop -> 창을 다른 모니터로 이동시 죽는 케이스 수정#486
workspace merged 1 commit into
droidknights:2025/compose-multiplatformfrom
easternkite:fix/#470-fix_crash_on_desktop

Conversation

@easternkite

@easternkite easternkite commented Jun 3, 2025

Copy link
Copy Markdown

Issue

Overview (Required)

해당 pr은 Desktop 창을 다른 모니터, 정확히는 다른 해상도를 가진 모니터로 이동하면 내부적으로 KoinContext를 불러올 수 없는 이슈를 수정합니다. 결론적으로 recomposition에 의한 side-effect이며, knightsAppDeclaration()을 전역 멤버로 선언하여 해결했습니다.

Issue Summary

  • 발생 조건: 듀얼 모니터 환경에서 창을 다른 해상도를 가진 모니터로 이동

발생 원인

  1. density 변경 → 전체 Recomposition
  2. remember(koinAppDeclaration) 재실행
  3. CompositionKoinApplicationLoaderonAbandoned 호출 → Koin 종료
  4. 이때, Wrapper의 koin 값은 null 리턴
  5. 결과적으로 rememberKoinApplication에서 Exception 발생

간단 분석

  1. 창을 다른 해상도를 가진 모니터로 이동하였을 때, density가 바뀌게 되면서 모든 구성요소에 대해 Recomposition이 발생합니다.
  2. Recomposition이 발생하면서 knightsAppDeclaration()이 다시 생성되며, 이를 key값으로 기억하고있는 koin 래퍼 클래스 또한 재생성됩니다.
@Composable
@KoinInternalApi
inline fun rememberKoinApplication(noinline koinAppDeclaration: KoinAppDeclaration): Koin {
    val wrapper = remember(koinAppDeclaration) {
        CompositionKoinApplicationLoader(koinApplication(koinAppDeclaration)) // <- 요거
    }
    return wrapper.koin ?: error("Koin context has not been initialized in rememberKoinApplication")
}
  1. CompositionKoinApplicationLoaderRememberObserver를 상속받고있으며, 기존 wrapper가 버려지고 재생성될 때onAbandoned 를 호출하여 koin context를 null로 초기화합니다.
@KoinInternalApi
class CompositionKoinApplicationLoader(
    val koinApplication: KoinApplication,
) : RememberObserver {

    var koin : Koin? = null

    init {
        start()
    }

    override fun onAbandoned() {
        stop()
    }

    override fun onForgotten() {
        stop()
    }

    override fun onRemembered() {
        start()
    }

    private fun start() {
        if (KoinPlatform.getKoinOrNull() == null){
            try {
                koin = startKoin(koinApplication).koin
                koin!!.logger.debug("$this -> started Koin Application $koinApplication")
            } catch (e: Exception) {
                error("Can't start Koin from Compose context - $e")
            }
        }
    }

    private fun stop() {
        koin = null
        KoinPlatform.getKoinOrNull()?.logger?.debug("$this -> stop Koin Application $koinApplication")
        stopKoin()
    }
}
  1. 이 타이밍에 wrapper.koin이 null을 반환하게 되어 앱에서 Exception이 발생하게됩니다.

@l2hyunwoo l2hyunwoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.....좋은 발견 감사합니다

@taehwandev taehwandev added 2025 / CMP bug Something isn't working labels Jun 4, 2025

@workspace workspace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@easternkite 좋은 발견과 멋진 해결 감사합니다! 👍

@workspace workspace merged commit f36e2c1 into droidknights:2025/compose-multiplatform Jun 4, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2025 / CMP bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants