PopupWindow에서 Jetpack Compose를 사용하는 방법개발/Android2022. 11. 4. 11:30
Table of Contents
Jetpack Compose에서 PopupWindow를 왜 사용하죠?
사실 처음부터 Compose로 개발하였다면 PopupWindow를 사용할 경우는 거의 없다고 생각된다. 그렇지만 그 이전, 우리는 커스텀 팝업을 만들 때 PopupWindow를 사용하여 XML을 Inflate 시켰기에 보다 편하게 만들어 왔었다.
그러다 점차 신규 프로젝트가 Compose로 개발이 시작되면서, 구 프로젝트(XML)에서도 독립적인(연계되지 않는) 신규 화면에 한해 Compose로 개발을 하였다.
이미 전반적인 커스텀 팝업을 PopupWindow를 사용하고 있으며 해당 팝업을 사용하는 곳도 Compose가 아니기에 기존 구조를 그대로 사용하면서 내용물만 Compose로 만든 다음 AbstractComposeView로 wrapping하면 되지 않을까 싶었다.
그러나 그 결과는...
Error Log
2022-04-06 19:53:08.366 25182-25182/com.autocrypt.mi.scjeju E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.autocrypt.mi.scjeju, PID: 25182
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView{b13a7f6 V.E...... R.....I. 0,0-0,0}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:244)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$LifecycleAware$1.createRecomposer(WindowRecomposer.android.kt:99)
at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:155)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:230)
at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
at android.view.View.dispatchAttachedToWindow(View.java:20753)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2630)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2143)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8665)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:780)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
시나리오
- Jetpack Compose로 만든 컴포저블을 AbstractComposeView로 Wrapping
- 기존 PopupManager를 통해 팝업을 만드는 것 처럼 PopupWindow로 팝업을 만들면 사망
분석
- 기능이 과해서 발생한게 아닐까?
- Text 하나만 배치하고 실행해도 사망
- 혹시 Main Thread가 아니라 그럴까?
- runOnUiThread, Dispatchers.Main 모두 적용 안됨
- 왜 안되는 것 인가!!!!
원인 추측
- Jetpack Compose를 사용하는 경우 ViewTreeLifecycleOwner을 사용해야한다.
- 실제로 androidx.appcompat:appcompat:1.3.0 부터 지원을 하기에 미만 에서는 위의 오류가 발생할 수 있다.
- 그런데 우리는 이미 1.3.0 이상 임에도 아래와 같은 오류가 발생하였다.
- ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView
- PopupWindow의 PopupDecoreView에서는 ViewTreeLifecycleOwner를 가지고 있지 않다.
- 이는 Compose에서 지속적인 변경을 감지하는 snapshot을 위한 라이프사이클을 찾지 못해 죽은 것이 아닌가 싶다.
해결
아래와 같이 parentView를 만든 다음 해당 View에 composeView를 추가하면 된다.
// 액티비티에서 라이프사이클을 가져옴
val owner = activity.window.decorView.findViewTreeLifecycleOwner()
val saveOwner = activity.window.decorView.findViewTreeSavedStateRegistryOwner()
// 컴포즈뷰를 만듦
val composeView = CustomerCenterPopup(activity)
// 부모뷰를 만든 다음 해당 뷰에 activity의 lifecycle을 넣음
val parentView = FrameLayout(activity).apply {
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, owner)
ViewTreeSavedStateRegistryOwner.set(this, saveOwner)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
background = ColorDrawable(Color.parseColor("#AA000000"))
// composeView는 이제 parentView의 lifecycle을 가져오기에 문제 해결
addView(composeView)
}
알쓸신잡
반대로 Jetpack Compose 내에서 기존 PopupWindow를 사용하는 경우 아래의 꼼수를 통해 사용이 가능하다
val composeView = ComposeView(LocalContext.current).apply {
setContent {
// Compose
}
}
val parentView = FrameLayout(LocalContext.current).apply {
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, LocalLifecycleOwner.current)
ViewTreeSavedStateRegistryOwner.set(this, LocalSavedStateRegistryOwner.current)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
addView(composeView)
}
val popupWindow = PopupWindow(
parentView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
popupWindow.showAtLocation(LocalView.current, Gravity.CENTER, 0, 0)
DisposableEffect(Unit) {
onDispose {
popupWindow.dismiss()
}
}
최초 작성일 : 2022. 04. 07
'개발 > Android' 카테고리의 다른 글
컴포즈 내비게이션 CreationExtras must have a value by `SAVED_STATE_REGISTRY_OWNER_KEY` 오류 (0) | 2022.12.07 |
---|---|
[Room DB] Room Database 마이그레이션 시 주의점 2가지 (0) | 2022.12.05 |
안드로이드 gRPC 개념 및 사용법 (2) | 2022.10.12 |
MQTT 안드로이드 코틀린 클라이언트 + 서버 만들기 (2) | 2022.09.27 |
안드로이드12 대응 - PendingIntent 빌드 오류 (0) | 2022.07.11 |
@귀염둥이 팡무 :: HolyKisa
상상하는 것을 소프트웨어로 구현하는 것을 좋아하는 청년
게시글이 마음에 드시나요? [ 공감❤️ ] 눌러주시면 큰 힘이 됩니다!