
Strike Log 제작기 3번째 글입니다. 지난 글에서는 볼링 점수를 입력하고 10프레임 규칙에 맞게 계산하는 과정을 다뤘습니다. 점수 계산이 끝나면 자연스럽게 다음 단계는 저장입니다. 그런데 실제 앱에서 저장은 단순히 서버에 POST 요청을 보내는 일로 끝나지 않았습니다.
로그인한 사용자의 기록이어야 하고, 인증 토큰이 만료될 수도 있습니다. 네트워크가 잠깐 끊길 수도 있고, 서버가 응답하지 않을 수도 있습니다. 클럽 기능에서는 체험판 만료처럼 일반 실패와 다르게 처리해야 하는 상황도 생깁니다. 그래서 Strike Log에서는 게임 저장을 화면 코드에 직접 넣지 않고, ApiClient와 GameSaveService로 책임을 나눴습니다.
Dio 기반 ApiClient로 인증 처리를 모으기
Strike Log의 Flutter 앱은 서버 통신에 Dio를 사용합니다. 그리고 앱 전체에서 공통으로 쓰는 ApiClient를 만들어 API 요청의 입구를 하나로 모았습니다. 각 화면에서 토큰을 꺼내 헤더를 붙이는 방식은 처음에는 쉬워 보이지만, 기능이 늘어날수록 같은 코드가 반복됩니다.
그래서 ApiClient의 요청 인터셉터에서 Authorization: Bearer <token> 헤더를 자동으로 붙이도록 했습니다. 게임 저장 화면, 기록 화면, 홈 대시보드, 클럽 화면은 어떤 API를 호출할지만 신경 쓰고, 인증 헤더 처리는 공통 클라이언트가 담당합니다.
401 응답 처리도 같은 이유로 공통화했습니다. 서버가 401 Unauthorized를 반환하면 토큰이 없거나 만료된 상태일 가능성이 높습니다. Strike Log에서는 이 상황을 감지해 세션을 정리하고 로그인 화면으로 이동할 수 있도록 onUnauthorized 콜백을 연결했습니다.
GameSaveService가 저장 흐름을 담당한다
게임이 끝난 뒤 요약 화면에서는 GameSaveService를 통해 /games API로 저장 요청을 보냅니다. 이 서비스는 단순히 dio.post()를 감싼 코드가 아닙니다. 저장 실패가 발생했을 때 어떤 실패인지 분류하고, 다시 시도할 수 있는 상황인지 판단합니다.
연결 오류나 타임아웃은 일시적인 문제일 수 있습니다. 이런 경우에는 바로 실패 처리하지 않고 최대 3회까지 저장을 다시 시도합니다. 반대로 4xx 응답은 대부분 요청 자체가 잘못되었거나 권한 문제가 있는 경우라서 무리하게 반복하지 않습니다.
특히 클럽 체험판 만료는 410 Gone으로 따로 분류했습니다. 일반 네트워크 실패와 다르게 사용자에게 왜 저장이 안 되는지 설명해야 하는 상황이기 때문입니다. 같은 실패처럼 보여도 UX에서는 서로 다른 안내가 필요합니다.

저장 성공 후 신규 배지까지 연결하기
Strike Log에는 배지 기능이 있습니다. 사용자가 특정 점수를 넘기거나, 게임 수를 채우거나, 클럽 활동을 하면 배지를 받을 수 있습니다. 이 배지 경험은 게임 저장 흐름과 연결되어 있습니다.
서버는 /games 저장 요청을 처리한 뒤 새로 획득한 배지가 있으면 응답에 newly_earned_badges 배열을 함께 내려줍니다. 앱은 이 값을 받아 신규 배지 모달을 띄웁니다. 사용자는 게임을 저장한 직후 이번 게임으로 어떤 배지를 얻었는지 바로 확인할 수 있습니다.
이 흐름이 중요한 이유는 보상 경험이 끊기지 않기 때문입니다. 저장은 내부적으로는 서버 통신이지만, 사용자에게는 게임을 마친 직후의 피드백입니다. 저장 성공과 배지 노출을 한 흐름으로 묶으니 앱을 계속 쓰고 싶은 이유가 조금 더 생깁니다.
화면 코드는 결과에 집중하게 만들기
저장 로직을 화면 안에 모두 넣으면 코드가 빠르게 복잡해집니다. 토큰 처리, 재시도, 에러 분류, 배지 추출, 실패 UI 표시가 한 파일에 섞이기 때문입니다.
Strike Log에서는 화면이 GameSaveService.saveGame()을 호출하고, 그 결과에 따라 성공 화면이나 재시도 안내를 보여주는 식으로 정리했습니다. 저장 로직은 서비스가 맡고, 화면은 사용자 경험에 집중합니다.
이 구조는 이후 클럽 게임 저장이나 내기 게임 저장처럼 비슷하지만 조금 다른 흐름을 붙일 때도 도움이 됩니다. 공통 저장 정책을 유지하면서 화면별 차이만 처리할 수 있기 때문입니다.
마무리
Strike Log의 3번째 제작기에서 다룬 핵심은 게임 기록 저장을 어떻게 안정적으로 만들 것인가였습니다. Dio 기반 ApiClient는 JWT 인증과 401 처리를 공통화했고, GameSaveService는 /games 저장 요청, 재시도, 실패 분류, 신규 배지 응답 추출을 담당했습니다.
기록 앱에서 저장은 가장 기본적인 기능이지만, 가장 신뢰를 잃기 쉬운 기능이기도 합니다. 사용자가 열심히 입력한 점수가 사라지지 않도록 만드는 것. Strike Log의 저장 구조는 그 문제를 해결하기 위한 첫 번째 안정장치였습니다.
'APP' 카테고리의 다른 글
| 클럽 운영 기능 만들기: 가입 승인제와 1인 1클럽 정책 (0) | 2026.07.03 |
|---|---|
| Socket.IO로 클럽 실시간 볼링 경기방 만들기 (0) | 2026.07.01 |
| NestJS와 TypeORM으로 볼링 기록 API 설계하기 (0) | 2026.06.29 |
| Flutter 볼링 점수 입력 화면: 10프레임 계산 로직 (0) | 2026.06.25 |
| 볼링 점수 기록 앱 Strike Log 제작기: Flutter와 NestJS로 시작하기 (0) | 2026.06.24 |
