SavePhotoScreen에서_무한_로딩_recomposition_및_오버레이_포커스 - boostcampwm-2024/and04-Nature-Album GitHub Wiki

💥 SavePhotoScreen에서 무한 로딩 recomposition 및 오버레이 포커스/터치 처리 개선

문제 정의

https://github.com/boostcampwm-2024/and04-Nature-Album/pull/128/files

@@ -76,20 +75,6 @@ fun SavePhotoScreen(
    }

    viewModel.setPhotoLoadingUiSate(UiState.Idle)
    val photoLoadingUiState = viewModel.photoLoadingUiState.collectAsStateWithLifecycle()

    when (photoLoadingUiState.value) {
        UiState.Idle, UiState.Loading -> {
            RotatingImageLoading(
                drawalbeRes = R.drawable.fish_loading_image,
                stringRes = R.string.save_photo_screen_loading
            )
        }

        UiState.Success -> {
            // TODO:  
        }
    }

image

  • SavePhotoScreen에서 로딩 상태를 표시하기 위해 RotatingImageLoading 컴포저블을 사용하고자 했다. 처음에 정호님께서 무한 리컴포지션 문제를 발견하여, 이를 이미지 로딩을 삭제하는 방향으로 해결했으나, 이미 만들어둔 로딩 이미지를 유지하면서 로딩 상태를 제어하는 것이 더 좋을 것 같아 문제 분석을 진행했다. 그 후 정호님의 제안으로 내가 뒤를 맡게 되었고, 이 문제를 해결하게 되었다.

문제 요약

  1. 무한 로딩 문제

https://github.com/user-attachments/assets/03a8ba48-32ad-4791-98e8-d150ae3cc35f

- `SavePhotoScreen`에서 `viewModel.setPhotoLoadingUiSate(UiState.Idle)`가 컴포저블 재구성 시마다 호출되어 `_photoLoadingUiState`가 항상 `Idle` 상태로 유지됨.
- `collectAsStateWithLifecycle()`가 상태 변화를 감지하여 로딩 컴포넌트가 계속 재구성되면서 무한 로딩 발생.
  1. 오버레이 포커스 문제
    • 올바르게 로딩 화면을 처리하고서 발견한 이슈
    • 로딩 중 Scaffold에 포커스가 여전히 잡혀 뒤의 화면이 클릭되는 이슈 발생.

1. 무한 로딩 문제

1-1. 문제 원인 분석

기존 상태 관리 문제

  • 초기 코드에서는 photoLoadingUiStatephotoSaveState라는 두 개의 상태를 관리하고 있었음.
  • photoLoadingUiState는 로딩 상태를 트리거하기 위한 용도로만 사용되고, photoSaveState는 저장 상태를 관리하고 있었음.
  • 로딩과 저장에 관한 상태 관리를 별도로 하면서 상태가 불필요하게 겹치는 문제 발생.
  • 상태가 겹치면서 SavePhotoScreen 컴포저블이 매번 재구성되고, _photoLoadingUiState는 항상 Idle 상태로 유지되어 무한 로딩 상태에 빠짐.

흐름 분석

  1. SavePhotoScreen이 실행될 때 viewModel.setPhotoLoadingUiSate(UiState.Idle)이 실행되어 상태가 초기화됨.
  2. _photoLoadingUiStateIdle로 설정되고, photoLoadingUiState.collectAsStateWithLifecycle()가 이 변화를 감지하여 재구성을 유도함.
  3. 재구성 시 viewModel.setPhotoLoadingUiSate(UiState.Idle)가 다시 호출되며, 무한 루프가 발생함.

1-2. 문제 해결

무한로딩 해결.webm

1. 단일 상태 관리로 변경 (photoSaveState만 사용)

  • photoSaveState 하나로 로딩 및 저장 완료 상태를 모두 관리하여 불필요한 상태 중복을 제거.
  • Loading, Success, Idle 상태를 photoSaveState로 통합하고 로딩 및 저장 완료 후 상태 전환을 관리.

2. 로딩 완료 후에만 Idle 상태로 설정

  • SavePhotoScreen에서 로딩 상태를 직접 설정하지 않고, 저장 완료 후에만 ViewModel에서 Idle 상태로 설정하도록 변경.
  • 상태 관리를 ViewModel에서 수행하여 불필요한 재구성을 방지함.

2. 오버레이 포커스 및 터치 이벤트 처리

문제 상황2.webm

2-1. 문제 원인

  • 로딩 화면(RotatingImageLoading)이 Scaffold 위에 겹쳐져 표시되었으나, Scaffold의 터치와 입력이 여전히 가능한 상태였다.
  • Scaffold에 있는 다른 UI 요소와의 상호작용이 방지되지 않아, 사용자가 로딩 중에도 다른 UI 요소를 터치할 수 있는 문제가 있었다.

2-2. 해결 방법: 포커스 및 터치 이벤트 소비 설정

  • RotatingImageLoadingpointerInputfocusRequester를 추가하여, 포커스와 터치 이벤트를 모두 차단하였다.
  • pointerInput을 사용하여 모든 터치 이벤트를 Box 내부에서 소비하도록 설정하여, 하위 Scaffold로 터치 이벤트가 전달되지 않도록 하였다.
val focusRequester = remember { FocusRequester() }
Box(
    modifier = Modifier
        .fillMaxSize()
        .background(MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.5f))
        .focusRequester(focusRequester)
        .focusable()
        .pointerInput(Unit) {
            awaitPointerEventScope {
                while (true) {
                    awaitPointerEvent(PointerEventPass.Initial) // 모든 터치 이벤트를 소비하여 Scaffold에 전달되지 않음
                }
            }
        },
    contentAlignment = Alignment.Center
) {
    // 로딩 이미지 및 텍스트 표시
}

  • val focusRequester = remember { FocusRequester() }포커스 요청을 관리하는 FocusRequester 객체를 생성하고, 컴포지션 동안 유지되도록 remember를 사용해 저장하는 역할
  • focusRequester(focusRequester)focusable()
    • focusRequesterfocusable()을 사용하여 이 Box가 포커스를 받을 수 있도록 설정
    • 포커스를 갖게 되면 키보드 입력이나 포커스 관련 이벤트를 다른 UI 요소들이 아닌 이 Box가 우선적으로 처리
  • pointerInput(Unit)
    • pointerInput을 사용하여 사용자 터치 이벤트를 Box가 가로채도록 설정
    • awaitPointerEventScopeawaitPointerEvent(PointerEventPass.Initial)을 사용하여, 모든 터치 이벤트가 Box 내에서 소모되도록 만든다.이로 인해 아래쪽에 있는 Scaffold와의 상호작용이 차단된다.
  • awaitPointerEvent(PointerEventPass.Initial)로 초기 전달 단계에서 모든 터치 이벤트를 가로채고 소비하여 이후 단계로 이벤트가 전달되지 않도록 막는다.
LaunchedEffect(Unit) {
    focusRequester.requestFocus()
}
  • Unit은 변경되지 않는 키 값으로 설정되어 있어, 이 LaunchedEffect 블록이 최초 컴포지션 시에만 한 번 실행되도록 보장
  • LaunchedEffect 블록은 focusRequesterBox에 포커스를 요청하도록 하여, 로딩 화면이 활성화되면 Box가 포커스를 우선적으로 가지게 하는 용도

해결.webm


결과

위와 같은 수정 사항을 통해 SavePhotoScreen에서 발생한 무한 로딩 문제오버레이 터치 및 포커스 차단 문제를 모두 해결할 수 있었다.

  1. SavePhotoScreen의 상태 관리 로직을 ViewModel에 위임하여 무한 로딩 문제를 해결하였다.
  2. RotatingImageLoading에 포커스 및 터치 이벤트 소비 기능을 추가하여, 로딩 중에도 Scaffold와의 상호작용을 차단하였다.