fix: 시스템 권한 팝업에서 허용 시 카메라 프리뷰 안보이는 문제 수정#131
Conversation
Walkthrough카메라 권한 관리 및 라이프사이클 처리가 리액티브 상태 기반으로 리팩토링되었습니다. 권한 상태를 추적하는 방식이 변경되고, 권한이 허용된 경우에만 카메라 바인딩 및 이미지 분석기가 동작하도록 DisposableEffect로 분리되었습니다. 시스템 UI 컨트롤러 설정도 별도 블록으로 이동되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CameraPreview(Composable)
participant PermissionLauncher
participant CameraController
User->>CameraPreview: 진입
CameraPreview->>PermissionLauncher: 권한 요청
PermissionLauncher-->>CameraPreview: 권한 허용/거부 결과
alt 권한 허용됨
CameraPreview->>CameraController: 카메라 바인딩 및 이미지 분석기 설정
else 권한 거부됨
CameraPreview-->>User: 권한 필요 안내
end
User->>CameraPreview: 앱 재진입/Resume
CameraPreview->>PermissionLauncher: 권한 상태 동기화
CameraPreview->>CameraController: (필요시) 바인딩/해제
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 사항 없음) Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt (1)
124-138: ON_RESUME 동기화는 좋고, 키와 주석 개선 제안DisposableEffect 키를 Unit 대신 lifecycleOwner로 두면 NavBackStack 변경 등 라이프사이클 소유자 변경에도 안전합니다. 또한 주석 오타(‘북귀’ → ‘복귀’) 수정 권장.
적용 diff:
- // 앱이 포그라운드로 북귀할 때 OS 권한 동기화 - DisposableEffect(Unit) { + // 앱이 포그라운드로 복귀할 때 OS 권한 동기화 + DisposableEffect(lifecycleOwner) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt(4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#75
File: feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt:0-0
Timestamp: 2025-07-29T06:07:11.727Z
Learning: seoyoon513 팀에서는 UI 구현 단계에서 더미 데이터를 하드코딩하여 화면을 먼저 구현하고, 이후 서버 연동 시점에 실제 데이터로 교체하는 개발 방식을 사용합니다.
📚 Learning: 2025-07-28T18:08:47.298Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#72
File: feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/component/CameraFrame.kt:21-82
Timestamp: 2025-07-28T18:08:47.298Z
Learning: Jetpack Compose에서 scale() 변환은 시각적 변환만 적용하며 레이아웃 좌표계는 변경하지 않는다. 따라서 scale(scaleX = -1f, scaleY = -1f)로 반전된 아이콘에서 padding()은 원래 레이아웃 기준으로 동작하므로, 시각적으로 올바른 위치를 위해서는 변환 전 좌표계 기준으로 padding을 설정해야 한다.
Applied to files:
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (5)
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt (5)
37-41: Compose 상태 import 추가: OK반응형 권한 상태 관리를 위한 필요한 import 추가가 적절합니다.
95-103: 권한 상태를 mutableState로 관리 — PR 목적과 정확히 부합OS 권한을 초기값으로 한 상태 관리로 권한 허용 즉시 UI 반영 가능합니다. 설계 방향 맞습니다.
106-111: 권한 승인 콜백에서 상태 갱신 처리: OKisGranted 갱신 → DisposableEffect 재실행 → 카메라 즉시 바인딩 흐름이 잘 연결됩니다.
116-123: 최초 진입 시 권한 요청 흐름 적절권한 미허용 시 런처 호출 및 다이얼로그 숨김 처리로 UX 플로우가 자연스럽습니다.
165-185: System UI 컨트롤러 처리: OK진입 시 투명/라이트, 종료 시 투명/다크로 복원하는 DisposableEffect 구성 적절합니다.
| val imageAnalyzer = remember { | ||
| ImageAnalysis.Analyzer { imageProxy -> | ||
| state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy)) | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Analyzer에서 imageProxy.close() 보장 필요 + eventSink 참조 최신화
- close 누락 시 프레임 정체/메모리 누수 위험이 큽니다. 소비 측(이벤트 핸들러)에서 반드시 close를 보장하는지 확인해 주세요.
- 또한 Analyzer를 remember(state.eventSink)로 생성하면 sink 변경 시 최신 참조를 사용합니다.
권장 diff(참고: 비동기 처리라면 finally에서 close하지 말고, 소비 측에서 완료 시 close):
- val imageAnalyzer = remember {
- ImageAnalysis.Analyzer { imageProxy ->
- state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy))
- }
- }
+ val imageAnalyzer = remember(state.eventSink) {
+ ImageAnalysis.Analyzer { imageProxy ->
+ try {
+ state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy))
+ } finally {
+ // 동기 처리일 때만 사용. 비동기 처리라면 소비 측에서 close 책임.
+ // imageProxy.close()
+ }
+ }
+ }검증 스크립트(소비 측에서 close 호출 여부 확인):
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "OnFrameReceived 처리부 검색"
rg -n "OnFrameReceived\\(" -A 50
echo
echo "imageProxy.close() 호출 위치 검색"
rg -n "imageProxy\\.close\\(\\)" -A 2Length of output: 9435
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1. OcrPresenter.kt에서 OnFrameReceived 처리부 검색"
rg -n "OnFrameReceived" -A 20 feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
echo
echo "2. Presenter에서 imageProxy.close() 호출 여부 확인"
rg -n "close(" -A 2 feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
echo
echo "3. LiveTextAnalyzer, StillTextAnalyzer 사용 위치 검색"
rg -n "LiveTextAnalyzer" -A 10 feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
rg -n "StillTextAnalyzer" -A 10 feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
echo
echo "4. Analyzer.invoke/analyze 호출 검색"
rg -n "analyz" -A 5 feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.ktLength of output: 1372
Analyzer에서 close 처리 확인 및 remember(key) 반영 요청
- core/ocr 모듈의 StillTextAnalyzer/LiveTextAnalyzer에서 imageProxy.close()를 호출해 리소스를 해제하고 있으므로, UI 레이어의 Analyzer에서 별도 close 호출은 불필요합니다.
- 다만
remember블록에state.eventSink를 키로 포함하지 않으면,eventSink가 변경되어도 람다에 최신 참조가 반영되지 않습니다. 아래 예시처럼 수정해 주세요.
- val imageAnalyzer = remember {
+ val imageAnalyzer = remember(state.eventSink) {
ImageAnalysis.Analyzer { imageProxy ->
state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy))
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val imageAnalyzer = remember { | |
| ImageAnalysis.Analyzer { imageProxy -> | |
| state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy)) | |
| } | |
| } | |
| val imageAnalyzer = remember(state.eventSink) { | |
| ImageAnalysis.Analyzer { imageProxy -> | |
| state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy)) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt
around lines 144 to 148, the ImageAnalysis.Analyzer does not need to call
imageProxy.close() because resource management is handled in the core/ocr
module. However, the remember block should include state.eventSink as a key to
ensure the lambda captures the latest reference when eventSink changes. Update
the remember call to remember(state.eventSink) and remove any explicit
imageProxy.close() calls in this analyzer.
| DisposableEffect(isGranted, lifecycleOwner, cameraController) { | ||
| if (isGranted) { | ||
| cameraController.bindToLifecycle(lifecycleOwner) | ||
| cameraController.setImageAnalysisAnalyzer( | ||
| ContextCompat.getMainExecutor(context), | ||
| imageAnalyzer, | ||
| ) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이미지 분석을 메인 스레드가 아닌 전용 Executor에서 실행하세요
현재 MainExecutor로 분석하면 OCR 로직이 UI 스레드를 점유해 프레임 드랍 가능성이 큽니다. 전용 싱글 스레드 Executor 사용을 권장합니다. 이 변경으로 권한 허용 직후에도 안정적으로 프리뷰+분석이 동작합니다.
적용 diff(실행 스레드 전환):
@@
- DisposableEffect(isGranted, lifecycleOwner, cameraController) {
+ DisposableEffect(isGranted, lifecycleOwner, cameraController) {
if (isGranted) {
- cameraController.bindToLifecycle(lifecycleOwner)
- cameraController.setImageAnalysisAnalyzer(
- ContextCompat.getMainExecutor(context),
- imageAnalyzer,
- )
+ cameraController.bindToLifecycle(lifecycleOwner)
+ // 전용 Executor 사용 권장 (아래 cameraExecutor 참고)
+ cameraController.setImageAnalysisAnalyzer(
+ cameraExecutor,
+ imageAnalyzer,
+ )
}추가 코드(해당 블록 위/아래에 배치):
// 상단 import 추가
import java.util.concurrent.Executors
// controller 바로 아래에 배치
val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
// onDispose 블록 내 자원 정리
onDispose {
cameraController.unbind()
cameraController.clearImageAnalysisAnalyzer()
cameraExecutor.shutdown()
}원하시면 위 변경을 반영한 전체 패치를 준비해드리겠습니다.
🤖 Prompt for AI Agents
In
feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt
around lines 150 to 157, the image analysis is currently executed on the main
thread using ContextCompat.getMainExecutor, which can cause UI frame drops. To
fix this, create a dedicated single-threaded executor using
Executors.newSingleThreadExecutor() and use it instead of the main executor for
image analysis. Also, add proper resource cleanup by shutting down this executor
in the onDispose block along with unbinding the cameraController and clearing
the image analysis analyzer.
|
Good! |
🔗 관련 이슈
📙 작업 설명
🧪 테스트 내역
📸 스크린샷 또는 시연 영상
Reed_OCR_._.mp4
💬 추가 설명 or 리뷰 포인트
Summary by CodeRabbit