Cheondi
개발 · Unity

빈 목록에서 끝나지 않은 로딩

빈 결과를 실패나 로딩 중으로 잘못 판단해 스크롤이 계속 요청을 보내던 문제를 정리한 기록입니다.

  • #unity
  • #pagination
  • #empty-state

목록 아래까지 스크롤하면 다음 내역을 불러오는 기능이 있었다. 데이터가 많을 때는 자연스럽게 동작했다. 문제는 내역이 하나도 없는 계좌였다. 빈 화면인데 아래쪽 로딩 표시가 계속 돌고, 잠시 뒤 같은 요청을 다시 보내고 있었다.

처음에는 응답을 받지 못했다고 생각했다. 로그를 보니 요청과 응답은 계속 성공했다. 응답 배열이 비어 있었고, 화면은 그걸 “아직 첫 페이지가 오지 않았다”로 해석하고 있었다.

빈 결과와 실패 상태의 구분

기존 조건은 리스트 개수만 보고 있었다.

if (rows.Count == 0)
    ShowLoading();

하지만 0은 아직 모른다는 뜻이 아니라 조회 결과가 정말 없다는 뜻일 수도 있다. 요청 상태와 결과 상태를 분리해야 했다.

if (state == RequestState.Loading)
    ShowLoading();
else if (state == RequestState.Succeeded && rows.Count == 0)
    ShowEmpty();

코드는 단순하지만 이 구분이 없으면 사용자는 기다려야 하는지, 볼 내용이 없는지 알 수 없다.

페이지 종료 정보

무한 스크롤은 현재 목록뿐 아니라 더 가져올 데이터가 있는지도 알아야 했다. 빈 페이지가 왔는데 hasMore를 그대로 두면 바닥에 닿을 때마다 같은 요청이 반복됐다.

void ApplyPage(PageResult result)
{
    rows.AddRange(result.items);
    hasMore = result.items.Count == pageSize;
    isLoading = false;
}

실제 API가 명시적인 hasNext를 준다면 그 값을 쓰는 편이 더 정확하다. 당시에는 기존 응답 규칙에 맞춰 마지막 페이지를 판단했다. 중요한 건 “빈 결과를 받았다”가 요청을 끝내는 상태 전환으로 이어져야 한다는 점이었다.

빈 화면의 사용자 행동

로딩을 끄고 나니 화면이 텅 비었다. 기능적으로는 맞지만 사용자는 오류인지 정상인지 알 수 없었다. 내역이 없다는 안내, 새로고침 버튼, 잘못 남아 있던 이전 선택 상태를 함께 정리했다.

빈 상태는 아무것도 하지 않는 상태가 아니라 별도로 설계해야 하는 화면이었다. 로딩, 실패와 같은 레벨로 다뤄야 했다.

경계값 우선 검증

이후 목록 기능을 보면 많은 데이터보다 먼저 0개, 1개, 페이지 크기와 정확히 같은 개수를 시험한다. 평범한 데이터는 대부분 잘 동작하지만 경계값에서 상태 구분이 드러나기 때문이다.

데이터가 없는데 로딩이 끝나지 않았던 버그는 배열 처리가 어려워서 생긴 게 아니었다. 없음을 하나의 정상 결과로 정의하지 않았기 때문에 생겼다. 없는 데이터도 분명한 정보라는 걸 배운 작업이었다.

추가 구현 기준

작업 당시에는 눈앞의 오류를 먼저 고쳤지만, 이후 기술 문서와 맞춰 보며 확인 순서를 조금 더 구체화했다.

Unity 실행 순서 문서는 Awake, OnEnable, Start, Update 같은 엔진 이벤트가 어느 구간에서 호출되는지 설명한다. 하지만 로딩·성공·빈 결과·실패 같은 업무 상태의 순서까지 대신 정해 주지는 않는다. 두 종류의 순서를 섞어 생각하면 콜백이 한 프레임 늦게 왔다는 이유만으로 이전 화면 상태가 다시 살아날 수 있다.

그래서 수명주기 메서드는 상태 전이를 직접 흩어 놓는 장소보다, 한 곳에 모인 전이 함수를 호출하는 경계로 쓰는 편이 추적하기 쉬웠다.

다음에 빈 목록에서 끝나지 않은 로딩 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • 요청 시작과 종료가 정확히 한 번 짝을 이루는지
  • 빈 결과와 오류의 표시 차이
  • 취소된 요청이 로딩을 다시 켜는지
  • 타임아웃 뒤 사용자 재시도 경로

새 응답을 적용하기 전에 요청 식별자와 현재 선택 조건이 여전히 같은지 확인해야 오래된 응답을 버릴 수 있다.

공식 참고 자료