스마트폰이 곧 카트의 화면 — 창의적종합설계대회 인기상 스마트카트 앱 개발 회고

Do Young·5일 전

contest

목록 보기
1/1
post-thumbnail

📷 실제 카트로 바코드를 찍으면 앱 장바구니에 바로 반영됩니다 07-5_시연영상_실제카트스캔_앱반영.gif

인천대학교 제22회 창의적종합설계경진대회에서 저희 팀 카트라이더(CartrAIder)가 인기상을 받았습니다. 저는 이 프로젝트에서 고객용 모바일 앱과 관리자 화면, 프론트엔드를 맡았습니다.

5월 아이디어 발표부터 10월 1일 최종 부스 발표까지, 어떤 흐름으로 무엇을 만들었고 어디서 막혔으며 어떻게 풀었는지 정리해 보려고 합니다.


1. 어떤 대회였나요

📷 최종 발표자료 표지 01-1_최종발표_표지.png

  • 대회: 제22회 창의적종합설계경진대회(창종설). 인천대학교에서 가장 큰 공대 대회입니다.
  • 작품명: 스마트폰 연동을 지원하는 스마트 AI 카트
  • 팀: 컴퓨터공학부·전자공학부 9명(프론트엔드, 백엔드, AI, 하드웨어)
  • 지도교수: 컴퓨터공학부 이장호 교수님
시기단계
2026년 5월아이디어 발표, 파트별 개발 계획서 작성
7월 20일중간발표
7월 ~ 9월본격 개발
9월 말실제 카트·서버·앱을 붙인 리허설
10월 1일(목)최종 발표. 하루 종일 부스 운영 → 인기상

저희 팀은 정보기술대학 컴퓨터공학부 중심이었습니다. 공대 동아리에서 나온 팀도 아니었고 사회공헌 아이디어도 아니어서 가산점을 받지 못했는데, 그런 조건에서 상을 받아 정말 만족스러운 결과였습니다.


2. 우리가 풀려던 문제

📷 문제 제기 슬라이드

마트 계산대 줄은 셀프 계산대가 늘어도 여전히 깁니다. 긴 줄 때문에 구매를 포기한 경험이 있다는 사람이 39%, 더 빠른 계산이면 매장을 바꿀 수 있다는 사람이 56%라는 조사도 있었습니다. 셀프 계산이 늘면서 스캔 누락으로 인한 상품 손실이라는 새 문제도 생겼습니다.

그렇다고 스마트카트가 처음 나온 아이디어는 아닙니다. 이마트 일라이, Caper Cart, Shopic 같은 사례가 이미 있었는데, 공통적으로 화면·카메라·결제 기능을 전부 카트 안에 넣다 보니 단가와 유지보수 비용이 커져서 널리 보급되지 못했습니다.

그래서 저희는 반대로 갔습니다.

  • 기존 카트를 그대로 쓰고, 작은 모듈만 붙인다.
  • 카트에는 화면을 달지 않는다. 고객의 스마트폰이 곧 카트의 화면이다.

📷 솔루션: 두 개의 모듈 02-2_솔루션_두개의모듈.png

모듈하는 일
카트 모듈(ESP32)바코드 리더로 상품을 찍으면 MQTT로 서버에 보내고, 앱 장바구니에 즉시 반영합니다. 바닥 경계선을 넘으면 서보모터가 바퀴를 잠급니다.
AI 출구 게이트(Raspberry Pi)파인튜닝한 YOLO11s(정확도 91%)로 카트 속 상품을 인식해 결제 내역과 대조합니다.
모바일 앱(제 담당)카트 QR 연결, 실시간 장바구니, 토스 결제, 출구 QR, 상품·매장 지도, 관리자 기능

📷 카트 모듈 실물 02-3_카트모듈_실물.jpg

📷 바닥선을 넘으면 카트가 멈춥니다 02-8_바닥선_정지_시연.gif

📷 AI 출구 게이트 02-10_AI게이트_실물.jpg


3. 설계가 크게 한 번 바뀌었습니다

📷 5월 초기 설계 → 9월 최종 설계 03-4_아키텍처_피벗_비교.png

5월 첫 발표 때의 설계는 지금과 꽤 달랐습니다. 카트 안에 카메라를 달고 영상을 클라우드 AI로 보내 상품을 인식하는 구조였고, 서버 쪽도 FastAPI → Kafka → Spring Boot로 이어지는 파이프라인에 앱과는 WebSocket으로 통신할 계획이었습니다.

최종적으로는 이렇게 바뀌었습니다.

항목초기(5월)최종(9월)
상품 인식카트 카메라 + 클라우드 AI카트의 바코드 리더
카트 하드웨어Raspberry Pi + DC 모터ESP32 + 서보 브레이크
AI 위치카트출구 게이트 한 곳
메시징KafkaMQTT
서버 → 앱WebSocketSSE

바코드는 인식 오류가 거의 없고 저렴합니다. 비싼 비전 AI는 출구 한 곳에만 두면 비용이 매장당 한 번으로 끝납니다. 비용과 정확도라는 현실에 맞춘 단순화였습니다.

중간발표(7월 20일)에서 받은 질문들

중간발표에서는 날카로운 질문과 피드백을 많이 받았습니다.

  • "바코드 리더기를 없애고 그냥 스마트폰 카메라를 쓰면 안 되나?"

  • "마트에서는 카트를 험하게 다루는데 내구성이 괜찮을까?"

  • "이런 주제는 기존에도 있는 걸로 아는데 진부하지 않나?" (심사위원들 사이에서도 의견이 갈렸습니다)

  • "이것보다 더 심플하게 하는 게 좋지 않나? 차라리 핸드폰을 카트에 붙이는 게 낫지 않나?"

이 피드백을 최종 발표까지 반영했습니다. 기존 사례는 회사별로 나눠 각각 왜 실패했는지 정리했고, "고객의 스마트폰이 곧 카트의 화면"이라는 구조를 전면에 내세웠습니다.

📷 기존 사례 슬라이드: 5월 vs 피드백 반영 후 03-5_기존사례_피드백반영_전후.png

다행히 앱 입장에서는 이 피벗이 큰 타격이 되지 않았습니다. 처음부터 "앱은 Spring Boot 서버 하나만 알고, 카트·AI·브로커는 모른다"는 경계를 정해 두었기 때문입니다. 카메라가 바코드로, Kafka가 MQTT로 바뀌어도 앱이 받는 것은 똑같이 "서버가 보내는 장바구니"였습니다.


4. 3년 만의 React Native, 공식 문서와 친해지기

저는 사실 React Native는 3년 전에 잠깐 다뤄 본 것이 전부였습니다. 이번처럼 앱 하나를 처음부터 끝까지 책임지고 적극적으로 써 본 것은 처음이었습니다.

3년이면 React Native 생태계가 많이 바뀌기에 충분한 시간이었습니다. 기억 속의 방식이 지금도 맞는지부터 의심해야 했고, 그래서 블로그 글보다 공식 문서를 먼저 확인하는 습관을 들였습니다.

  • Expo Router: 예전에는 React Navigation으로 화면을 직접 등록했는데, 이제는 파일 구조가 곧 라우팅이었습니다. 문서를 보며 src/app 폴더 구조로 화면 23개를 정리했습니다.
  • New Architecture(Fabric): 지금은 New Architecture가 기본입니다. 뒤에서 이야기할 지도 버벅임 문제는 결국 이 렌더링 구조를 이해해야 풀 수 있었습니다.
  • react-native-reanimated: 애니메이션을 UI 스레드에서 돌리는 원리와, 동기 UI 업데이트 같은 정적 플래그 설정을 문서에서 찾아 적용했습니다.
  • Expo SDK 업그레이드: SDK 54에서 57로 올릴 때 변경 사항을 하나씩 확인했습니다. StyleSheet.absoluteFillObject가 사라져 오버레이가 조용히 깨지던 문제, expo-media-library의 API가 바뀌어 결제 완료 화면이 크래시 나던 문제가 대표적이었습니다.
  • EAS Build / Update: APK 빌드 프로필, 환경변수, OTA 업데이트의 runtimeVersion 정책을 문서를 보며 설정했습니다. eas update가 빌드 프로필의 환경변수를 읽지 않는다는 함정도 문서를 읽다가 발견했습니다.
  • 그 외 라이브러리: expo-image(캐시·다운스케일), react-native-sse(RN에는 EventSource가 없음), react-native-keyboard-controller(안드로이드 키보드 회피), react-native-webview(토스 결제창)도 문서를 기준으로 붙였습니다.

"예전엔 이렇게 했던 것 같은데"로 시작하면 거의 항상 틀렸습니다. 공식 문서와 실제 동작을 확인하고 나서 코드를 쓰는 것이 결국 가장 빠른 길이었습니다.


5. 개발 방식: 설계를 먼저 끝내고, AI와 함께 구현하기

이번 프로젝트에서는 Claude를 적극적으로 활용했습니다. 다만 코드부터 짜지 않고 아래 순서를 지켰습니다.

  1. 디자인 시스템: 색·글자 크기·간격·그림자 토큰, 일반/노약자 두 모드의 기준값, 공용 컴포넌트
  2. 유저 플로우: 로그인 → 홈 → 카트 QR 연결 → 장바구니 → 결제 → 완료, 그리고 관리자 흐름
  3. 시스템 아키텍처: 앱은 REST(능동 액션)와 SSE(실시간 수신) 두 갈래로만 서버와 통신
  4. 와이어프레임: 화면별 구성과 배치
  5. 퍼블리싱: 백엔드 API가 없어도 돌아가도록 mock 데이터로 전체 흐름을 먼저 완성
  6. 서버 연동: mock을 실제 API 호출로 하나씩 교체

📷 7월 초 유저 플로우 메모

설계 결과는 레포의 CLAUDE.md, AGENTS.md(코딩 규칙, 폴더 구조, 쓰지 않을 라이브러리)와 스프린트 문서로 남겨 두었습니다. 덕분에 AI와 작업할 때도 같은 규칙 안에서 일관된 결과물이 나왔습니다.

라이브러리는 일부러 최소한으로 썼습니다.

쓰지 않은 것이유
Axiosfetch 래퍼 한 파일로 토큰 재발급·타임아웃까지 처리할 수 있었습니다.
TanStack Query실시간 데이터는 SSE가 맡고, 나머지는 요청 몇 개뿐이었습니다.
Redux / Zustand전역 상태가 5덩어리로 나뉘고 서로 거의 섞이지 않아 Context로 충분했습니다.
WebSocket서버 → 앱 단방향이면 충분했습니다.

📷 초기 UI 시안 → 최종 앱 04-4_UI변화_초기시안vs최종앱.png


6. 만든 것

📷 카트 연결부터 출구까지 05-0_고객앱_핵심흐름_모음.png

고객 화면 17개와 관리자 화면 6개, 총 23개 화면을 만들었습니다. 처음 계획한 화면은 5개였습니다.

  • 고객: 로그인/회원가입(이메일 인증), 홈, 카트 QR 연결, 실시간 장바구니, 결제, 결제 완료(출구 QR·영수증 저장), 상품 목록·상세, 매장 지도, 구매 내역, 마이페이지, 비밀번호 찾기·변경, 탈퇴
  • 관리자: 대시보드, 주문 관리, 상품 관리·등록, 매장 지도 편집

📷 실시간 장바구니 05-1_실시간장바구니_앱.gif

📷 관리자 화면
05-0b_관리자화면_모음.png

📷 매장 지도 길 안내 05-18_매장지도_길안내_애니메이션.gif

일반 모드와 노약자 모드

📷 화면은 한 벌, 모드 토큰만 바꾼다 05-19_일반vs노약자모드_비교.png

처음 문제의식 중 하나가 접근성이었기 때문에, 앱에는 노약자 모드가 있습니다. 중요하게 생각한 원칙은 화면은 한 벌만 만들고, 모드 토큰만 바꾼다는 것이었습니다. 화면을 두 벌로 만들면 한쪽만 고쳐지는 순간 "노약자 모드는 기능이 다른 앱"이 되어 버리기 때문입니다.

요소일반노약자
본문/버튼/금액15/18/20pt18/24/30pt
터치 영역44px56px
색 대비표준WCAG AA
상품 그리드2열1열
음성 안내없음상품 담김·결제 완료 안내

7. 기술적으로 고민한 것들

📷 최종 시스템 구성 06-1_최종_아키텍처.png

7-1. 실시간 장바구니(SSE)는 "놓치는 경우"를 전제로

📷 SSE 실시간 흐름
06-2_SSE_실시간_시퀀스.png

카트에서 바코드를 찍으면 서버가 SSE로 장바구니를 앱에 밀어줍니다. 단순해 보였지만 네 가지 문제가 있었습니다.

  1. 인증: SSE는 요청 헤더에 토큰을 실을 수 없습니다. 그래서 30초짜리 1회용 티켓을 먼저 받아 쿼리로 넘겨 구독했습니다.
  2. 재연결: 티켓이 1회용이라 EventSource의 자동 재연결(같은 주소로 재시도)을 쓸 수 없었습니다. 자동 재연결을 끄고, 끊기면 새 티켓을 받아 1초 → 2초 → 4초 … 최대 10초 간격으로 다시 붙게 했습니다.
  3. 스냅샷 교체: 변경분을 쌓으면 이벤트 하나만 놓쳐도 합계가 영영 틀어집니다. 그래서 매번 장바구니 전체를 받아 통째로 교체했습니다.
  4. 순서 방어: 늦게 도착한 옛 스냅샷이 최신 상태를 덮지 않도록 version 번호가 낮으면 버렸습니다.

여기에 더해, 다시 연결될 때마다 현재 장바구니를 REST로 한 번 더 받아서 놓친 변경을 채웠습니다. 모두 "이벤트를 놓쳐도 결국 맞는 상태로 돌아오게" 하려는 장치였습니다.

7-2. 지도 버벅임, 두 번 만에 잡다

📷 지도 버벅임 원인과 해결을 정리한 PR 06-8_PR36_지도버벅임_원인과해결.png

상품 상세와 매장 지도에는 길 안내 애니메이션(경로, 카트 주행, 현위치 펄스, 목적지 핀)이 있습니다. 그런데 이 화면이 눈에 띄게 버벅였습니다.

  • 1차 수정: 카트를 SVG transform 문자열로 움직이고 있었는데, react-native-svg가 transform을 JS에서 해석하기 때문이라고 판단했습니다. 숫자 prop(cx, cy)으로 바꾸고, 화면이 가려지면 애니메이션을 멈추게 했습니다.
  • 그런데 배포 APK에서는 여전히 버벅였습니다.
  • 2차 수정: 공식 문서와 라이브러리 동작을 다시 파고들어 보니, New Architecture(Fabric)에서는 SVG 속성을 애니메이션하면 숫자여도 매 프레임 Shadow Tree 커밋이 일어나고, 그때마다 SVG 캔버스 전체(바닥 패턴, 그라디언트, 매대)를 다시 그리고 있었습니다.
    • 평면도와 경로는 정적 SVG로 한 번만 그렸습니다.
    • 움직이는 카트·펄스·핀은 SVG 위에 겹친 View로 옮겨 transform과 opacity만 움직이게 했습니다.
    • Reanimated의 동기 UI 업데이트 플래그를 켜서 커밋 없이 네이티브 뷰에 바로 반영되게 했습니다.
    • 무거운 지도는 화면 진입 애니메이션이 끝난 뒤에 붙였습니다.

새 APK를 실기기에 설치해 버벅임이 사라진 것을 확인했습니다. "숫자 prop이니 빠르겠지"라는 1차 가설은 예전 아키텍처 기준의 지식이었습니다.

7-3. 토스 결제를 WebView로

토스페이먼츠 결제창은 WebView에 띄웠습니다. 결제 성공·실패 리다이렉트 주소를 가로채 결제 키를 얻고, 최종 승인은 서버가 하도록 했습니다. 시크릿 키는 앱에서 쓰지 않습니다. 간편결제나 카드사 앱으로 넘어가는 intent:// 주소는 WebView가 열 수 없어서, 직접 파싱해 OS로 넘기고 앱이 없으면 스토어로 안내했습니다.

7-4. 숫자로 본 개선

📷 개선 결과 수치 06-5_성과_수치카드.png

  • 상품 이미지 전송량 68.2MB → 1.43MB: 원본이 1920px PNG(평균 1.8MB)라 작은 카드에 쓰면서 홈 진입에만 37MB를 받고 있었습니다. WebP로 재인코딩하고 expo-image 캐시를 적용했습니다.
  • 로그인 첫 요청 1.0초 → 0.35초: 첫 요청의 TLS 연결 비용 때문이었습니다. 로그인 화면에서 공개 API를 미리 호출해 연결을 데워 두었습니다.
  • 그 밖에 시작 시 이미지 프리페치를 40장에서 12장으로 줄였고, 저장소 암복호화 접근을 요청마다에서 실행당 1회로, ESLint 경고를 35건에서 8건으로 줄였습니다.

8. 백엔드와 협업한 방식

mock 우선 개발

백엔드 API가 나오기 전에 모든 네트워크 함수를 mock 분기로 감싸고 전체 흐름을 먼저 완성했습니다. 덕분에 API가 없던 7월 초에도 앱 전체를 시연할 수 있었고, 실제 연동은 "mock을 진짜 호출로 바꾸는 작업"으로 진행할 수 있었습니다.

요청은 근거와 함께, 이슈로

백엔드에 필요한 것은 GitHub 이슈로 정리해 요청했습니다. 무엇이 왜 필요한지, 엔드포인트·필드·상태코드, 그동안의 임시 우회 방법까지 적었습니다. 개인 주문 내역은 응답 구조와 결제 상태를 고르는 규칙까지 직접 제안했습니다.

📷 백엔드에 API 스펙을 제안한 이슈 06-6_이슈23_주문내역_API_스펙제안.png

인증번호를 무제한으로 대입할 수 있는 보안 문제를 발견했을 때는, 서버 저장소에서 직접 재현한 결과를 정리해 공유했습니다.

📷 보안 이슈를 재현해 공유한 이슈 06-7_이슈29_인증번호_보안이슈_공유.png

앱 동작에 꼭 필요한 요청(모바일 토큰 방식, 회원 이름, 현재 장바구니 조회, 계정 관리, 내 정보, 주문 내역)은 같은 날부터 약 2주 안에 모두 반영되었습니다. 가장 빨랐던 것은 요청하고 약 1시간 만에 모바일 전용 인증 API가 생긴 경우였습니다.


9. 리허설, 그리고 10월 1일

리허설에서 터진 문제들

📷 리허설 중 MQTT로 카트 메시지를 확인하던 화면 07-1_리허설_MQTT_카트메시지.png

실제 카트, 운영 서버, APK로 빌드한 앱을 한꺼번에 붙인 리허설에서 문제가 많이 나왔습니다.

  • 카트 스캔이 앱에 안 뜸: 카트에서 바코드를 찍어 MQTT로 서버에 보내는 과정이 잘 되지 않았습니다. 서버 로그를 정말 많이 봤습니다. 앱을 껐다 켜도 서버 장바구니가 비어 있는 것으로 앱 문제가 아님을 먼저 가렸고, MQTT 클라이언트로 카트 메시지를 확인한 뒤 바코드를 서버 상품 데이터와 하나씩 대조하며 원인을 좁혔습니다.
  • 카트 반납이 안 됨: 리허설 중 카트 반납이 정상적으로 되지 않는 경우가 있었습니다. 앱을 다시 보니 장바구니가 비어 있으면 반납 버튼이 아예 보이지 않았습니다. 스캔이 안 들어와 장바구니가 빈, 가장 반납이 필요한 순간에 빠져나갈 출구가 없었던 셈입니다. 카트 반납 UX가 많이 부족했다고 느꼈습니다.
  • 계정 중복: 같은 계정으로 여러 기기에서 접속하면 서버가 계정당 실시간 연결을 하나만 유지해서, 나중에 연결한 기기만 장바구니를 받았습니다. "연결은 됐는데 장바구니가 안 뜨는" 혼란이 생겼습니다.
  • 일부 카드사 결제 실패: APK로 빌드한 앱에서 일부 카드사의 토스 테스트 결제가 되지 않았습니다. 그래서 실제 시연은 토스페이먼츠 결제창의 하나페이로 진행했습니다.

최종 발표

📷 실제 카트로 바코드를 찍는 장면 07-6_시연_실제카트_바코드스캔.png

10월 1일 목요일, 하루 종일 부스를 운영했습니다. 심사위원과 기업 대표들이 직접 와서 평가했고, 학생들도 많이 찾아와 구경했습니다.

📷 부스 현장 사진

앱에 대한 반응이 특히 좋았습니다. 그중에서도 토스페이먼츠 결제 연동을 마음에 들어 하셨는데, 학생 프로젝트에서 실제 결제창을 띄우고 승인까지 이어지는 흐름을 신기해하셨습니다. 공대 교수님들이라 더 그렇게 보신 것 같습니다.

📷 인기상 사진


10. 아쉬운 점

  • 예외 상황 UX를 늦게 챙겼습니다. 정상 흐름(담고, 결제하고, 나가기)만 기준으로 설계하다 보니, 스캔이 안 들어오거나 연결이 꼬였을 때 사용자가 스스로 빠져나올 방법이 부족했습니다.
  • 통합 테스트가 늦었습니다. 실제 카트·서버·앱을 한 번에 붙인 테스트가 리허설 무렵에야 이뤄지면서, 경계에서 생기는 문제가 늦게 드러났습니다.
  • 코드 리뷰가 없었습니다. 리뷰 규칙을 직접 만들었지만 파트당 개발자가 한 명이라 지켜지지 않았습니다. 대신 PR 본문에 원인·해결·검증을 자세히 적는 것으로 보완하려 했습니다.
  • 정량 근거가 부족했습니다. 카트 모듈 원가나 "스캔 → 앱 반영" 지연 시간 같은 측정값을 발표에 넣지 못했습니다.

11. 배운 점

  • 경계를 정하면 피벗에 강해진다. "앱은 서버 하나만 안다"는 격리하는 원칙 덕분에 하드웨어와 서버 구조가 크게 바뀌어도 앱을 다시 짤 필요가 없었습니다.
  • 기억보다 공식 문서가 먼저다. 3년 사이에 바뀐 것이 생각보다 많았습니다. 특히 성능 문제는 렌더링 구조까지 내려가 봐야 근본 원인이 보였습니다.
  • 설계를 먼저 끝내면 AI 활용도, 구현도 빨라진다. 디자인 시스템부터 와이어프레임까지 정해 두고 규칙으로 남겼기 때문에 혼자서도 23개 화면을 일관되게 만들 수 있었습니다.
  • 현장에서는 "어디서 끊겼는지"를 가르는 질문이 중요하다. 앱을 껐다 켜 보는 것만으로 앱 문제인지 서버·하드웨어 문제인지 30초 만에 나눌 수 있었습니다.

마치며

📷 결론 및 기대효과08-1_결론_기대효과.png

이번 글은 지금까지 제 velog에 써 온 글들과는 조금 다른, 대회 회고록입니다. 5월 아이디어 발표부터 10월 1일 부스 발표까지 길게 이어진 대회였는데, 무사히 잘 마무리할 수 있어서 다행이라는 생각이 가장 먼저 듭니다.

3년 만에 다시 잡은 React Native로 실제 카트와 실제 결제까지 이어지는 앱을 끝까지 만들어 보니, 대회용 APK에서 멈추지 않고 실제로 앱을 출시해 보고 싶다는 생각도 들었습니다. 굳이 대회에 출품했던 이 앱 말고도, 스토어 심사를 거치고, 진짜 사용자를 만나고, 그 피드백으로 앱을 고쳐 나가는 경험까지 해 보고 싶어졌습니다.

무엇보다 하드웨어와 결합한 임베디드 성격의 개발은 이번이 처음이었습니다. 화면 안에서만 끝나던 개발과 달리, 카트에서 바코드를 찍는 순간이 MQTT와 서버를 거쳐 제 앱 화면에 뜨기까지 모든 구간이 이어져야 비로소 동작했습니다. 리허설 날 서버 로그와 카트 메시지를 하나씩 대조하며 끊긴 곳을 찾던 시간은 힘들었지만, 소프트웨어 바깥의 세계와 맞물려 돌아가는 시스템을 만든다는 게 어떤 것인지 몸으로 배운 뜻깊은 경험이었습니다.

학부생에게 하드웨어·AI·백엔드·앱을 한 팀에서 처음부터 끝까지 만들어 볼 기회는 흔치 않습니다. 몇 안 되는 기회라는 걸 알았기에 감사한 마음으로 더 열심히 준비했던 것 같고, 그 결과가 인기상이라는 형태로 돌아와 더 기쁩니다.

함께한 카트라이더 팀원들 감사합니다.

profile
풀스택 개발자

0개의 댓글