Flutter+Rails 실시간 인프라 4대 단절 고치기 — FCM 딥링크, ActionCable JWT, 익명→회원 연결

토너먼트 앱 하나를 Flutter로 전환하고 있는데, 사용자 입장에서 제일 중요한 시나리오가 동작을 안 했다. “로그인 → 알림 수신 → 알림 탭 → 내 코트로 이동 → 대기/경기/결과 확인”. 이게 끊기면 앱이 있으나 마나다. 처음에는 코드 조각은 대부분 있는 것 같았다. FCM 토큰 등록 로직도 있고, ActionCable 클라이언트 클래스도 있고, 스코어 입력 화면도 있다. 그런데 막상 실제로 알림을 탭해도 앱이 홈으로만 열린다. 실시간 업데이트도 안 온다. 왜? 탐색해보니 연결된 것처럼 보이는 부품 사이에 네 군데가 끊겨 있었다. 이 포스트는 그 네 군데를 하루 세션에서 다 고친 기록이다. 각 단절마다 왜 그게 2026년에도 여전히 함정인지 근거도 같이 정리한다. ...

2026-04-16 09:00 · 9분 소요 · Seunghan

Flutter TestFlight 업로드 삽질 — exportArchive Failed to Use Accounts 해결

Flutter로 앱을 만들고 TestFlight에 올리려는데, 아카이브 빌드는 성공하고 IPA export에서 멈췄다. exportArchive Failed to Use Accounts라는 에러가 뜨는데, 구글링해도 명쾌한 답이 없었다. 결국 3가지 다른 에러를 연달아 만나면서 해결했고, 그 과정을 정리한다. 에러 1: exportArchive Failed to Use Accounts flutter build ipa --release --dart-define=ENV=prod --export-options-plist=ios/ExportOptions.plist 아카이브 빌드는 잘 된다: ✓ Built build/ios/archive/Runner.xcarchive (197.5MB) [✓] App Settings Validation • Version Number: 1.0.1 • Build Number: 9 • Display Name: My App • Bundle Identifier: com.example.app 근데 바로 다음 줄에서: ...

2026-03-31 00:00 · 4분 소요 · Seunghan

Hotwire Native에서 Flutter BLoC으로 — 네이티브 앱 전환 실전기

Rails 웹앱에 Hotwire Native로 iOS 앱을 감싸서 출시했는데, 접속 오류로 심사가 반복 반려됐다. WebView 기반의 한계를 느끼고 Flutter + BLoC 패턴으로 순수 네이티브 전환을 결정했다. 이 글은 실제로 웹앱을 Flutter 앱으로 전환하면서 겪은 설계, 삽질, 해결 과정을 정리한 것이다. Hotwire Native가 안 된 이유 Hotwire Native는 WKWebView 위에 얇은 네이티브 셸을 씌우는 구조다. Turbo Navigator가 URL 기반으로 네비게이션을 처리하고, Bridge Component로 네이티브 UI를 부분적으로 제어한다. 웹 개발자 입장에서는 최소 비용으로 앱을 만들 수 있어서 매력적이다. ...

2026-03-31 00:00 · 9분 소요 · Seunghan

Hotwire Native에서 Flutter로 — Rails 앱의 모바일 네이티브 전환 실전기

Rails 8 + Hotwire로 웹을 만들고, Hotwire Native로 iOS/Android를 감싸면 꽤 그럴듯한 앱이 나온다. WebView가 서버 렌더링 HTML을 그대로 보여주니 코드 한 벌로 3플랫폼을 커버할 수 있다. 실제로 이 방식으로 프로덕션에서 잘 돌아가는 앱을 운영하고 있었다. 그런데 점점 한계가 보이기 시작했다. 오프라인 지원이 안 되고, 네이티브 애니메이션도 못 쓰고, WebView 특유의 뚝뚝 끊기는 느낌이 있었다. 결국 Flutter로 풀 네이티브 전환을 결정했고, 설계부터 프로덕션 배포까지 약 2주 만에 끝냈다. 그 과정을 정리한다. ...

2026-03-31 00:00 · 9분 소요 · Seunghan

Flutter AAB 빌드 서명 오류 해결 — Play Store 업로드 시 wrong key 에러 완벽 가이드

Flutter AAB를 Play Console에 드래그했더니 서명 에러 Flutter로 만든 앱을 Google Play Console에 업로드하려고 flutter build appbundle --release로 AAB 파일을 빌드했다. 빌드 자체는 성공했고, 51MB짜리 app-release.aab가 잘 생성됐다. 자신만만하게 Play Console에 드래그 앤 드롭했는데, 이런 에러가 떴다. Android App Bundle이 잘못된 키로 서명되었습니다. 제대로 된 서명 키로 App Bundle에 서명한 다음 다시 시도해 보세요. SHA1: 5A:2A:F8:A4:71:76:3B:CC:35:78:33:B1:98:65:8F:24:85:72:AB:87 지문이 포함된 인증서로 App Bundle에 서명해야 하지만, 업로드한 App Bundle 서명에 사용된 인증서의 지문은 SHA1: A8:E9:B6:3C:C6:9A:E9:FE:06:AA:BB:2E:E3:43:85:1A:74:96:16:48 입니다. 두 개의 SHA1 지문이 달랐다. Play Console이 기대하는 키와, 실제 AAB에 서명된 키가 다르다는 뜻이다. ...

2026-03-26 00:00 · 7분 소요 · Seunghan

Flutter에서 Gemini, OpenAI, Claude 직접 연동하기 — 멀티 AI 프로바이더 패턴 구현

시작 — 하드코딩된 API 키 문제 Flutter 앱에서 AI 기능(영수증 OCR, 이미지 번역, 블로그 자동 생성)을 넣을 때, 처음에는 BizRouter라는 AI 프록시 서비스를 썼다. 모든 요청을 하나의 엔드포인트로 보내면 내부에서 Gemini, GPT, Claude 등으로 라우팅해주는 구조였다. 문제는 API 키가 소스 코드에 하드코딩되어 있다는 것이었다. class BizRouterService { static const _apiKey = 'sk-br-v1-d6872ae8e164...'; // 이게 코드에 그대로 static const _baseUrl = 'https://api.bizrouter.ai/v1'; 지인들에게 배포하는 MVP라 처음에는 괜찮았지만, 사용자가 자기 키를 입력해서 쓸 수 있게 만들어야 했다. Gemini 키가 있는 사람은 Gemini로, OpenAI 키가 있는 사람은 GPT로, Claude 키가 있는 사람은 Claude로 — 각자 가진 키를 쓸 수 있어야 했다. ...

2026-03-26 00:00 · 9분 소요 · Seunghan

Flutter에서 iOS Live Activity 잠금화면 미리보기 만들기 — CustomPainter로 네이티브 위젯 재현

왜 Live Activity 미리보기가 필요했나 여행 앱에서 iOS Live Activity를 구현하고 나면, 사용자에게 표시 모드를 선택하게 하는 설정 화면이 필요하다. 일정 모드, 예산 모드, 자동 모드, 결합 모드 — 이렇게 네 가지 옵션이 있는데, 문제는 이 모드를 바꿨을 때 잠금화면에서 실제로 어떻게 보이는지 사용자가 알 수 없다는 점이었다. 설정 화면 하단에 Dynamic Island compact 형태의 간단한 미리보기는 있었다. 하지만 사용자가 Live Activity를 가장 많이 보는 곳은 잠금화면이다. 작은 알약 모양 미리보기로는 “이 모드를 선택하면 잠금화면이 이렇게 바뀝니다"를 전달하기 어려웠다. ...

2026-03-26 00:00 · 8분 소요 · Seunghan

앱스토어 마케팅 스크린샷 자동화 — Puppeteer + features.json 분석 주도 파이프라인

앱 출시를 앞두고 스토어 스크린샷을 만들어야 하는 상황이 됐다. Figma로 하나씩 만드는 건 너무 비효율적이고, 10가지 디자인 시안에 5개 기능 화면을 조합하면 50장인데 수작업으로 한다는 건 말이 안 된다. 그래서 HTML/CSS로 마케팅 프레임을 만들고 Puppeteer로 PNG를 뽑아내는 파이프라인을 짰다. 처음엔 대충 만들었다가 구조적으로 틀린 부분이 있다는 걸 나중에 깨달았는데, 그 과정이 의외로 중요한 교훈을 남겼다. 처음 접근 방식의 문제 처음엔 이런 식으로 기능 배열을 하드코딩했다. const FEATURES = [ { id: 'expense', title: '지출을 한번에', sub: 'AI가 영수증을 읽어드립니다', screen: '02_expense_detail' }, { id: 'camera', title: '사진 찍으면 끝', sub: 'OCR로 즉시 기록', screen: '03_camera_hub' }, // ... ]; 문제는 이 파일명들(02_expense_detail.png, 03_camera_hub.png)이 실제로 존재하지 않았다는 것이다. 캡처 스크립트가 자동으로 화면을 이동하다가 실패해서 홈 화면만 5번 찍혔는데, 파일명은 다 달랐다. 결과적으로 모든 슬롯이 placeholder(검은 화면)였다. ...

2026-03-26 00:00 · 6분 소요 · Seunghan

Flutter GoRouter ShellRoute로 바텀 네비게이션 전역 유지하기 — 삽질부터 Liquid Glass 인터랙션까지

바텀 네비가 사라지는 순간 Flutter 앱을 만들다 보면 어느 순간 이런 상황을 만난다. 메인 화면에 탭 5개짜리 바텀 네비게이션 바가 있고, 할일 수정이나 새 메모 생성 버튼을 누르면 context.push('/tasks/new')로 화면을 전환한다. 그런데 화면이 전환되는 순간 바텀 네비가 통째로 사라진다. iOS 네이티브 앱에서는 탭 안에서 push 하면 탭 바가 유지된다. Apple의 메모 앱에서 메모를 열어도, 미리 알림에서 항목을 수정해도 하단 탭 바는 그대로 있다. 그런데 Flutter에서는 기본적으로 이렇게 동작하지 않는다. 원인을 찾아보니 라우터 구조 자체의 문제였다. ...

2026-03-24 00:00 · 9분 소요 · Seunghan

Flutter Material SnackBar 걷어내기 --- Overlay 기반 Glassmorphism Toast + Apple HIG 시스템 색상

Material SnackBar가 거슬리기 시작한 순간 Flutter 앱에서 Glass 디자인 시스템을 구축하면서 Material Design 컴포넌트를 하나씩 걷어내고 있었다. AlertDialog는 GlassDialog로, Card는 GlassCard로, AppBar는 GlassAppBar로. 하나씩 바꿔가니 앱 전체가 반투명 블러 기반의 통일된 느낌이 잡혔다. 그런데 문제가 하나 남았다. 토스트 알림이었다. ScaffoldMessenger.of(context).showSnackBar()로 띄우는 Material SnackBar가 화면 하단에 불투명한 초록/파랑/주황 배경으로 뜨는데, Glass로 바뀐 나머지 UI와 전혀 어울리지 않았다. 하단 고정 위치도 마음에 안 들었다. iOS 네이티브 앱들처럼 상단에서 슬라이딩으로 내려왔다가 올라가는 토스트가 필요했다. ...

2026-03-24 00:00 · 7분 소요 · Seunghan

Flutter 노트 앱 디자인 리뉴얼 — Bear 스타일 에디터와 Glassmorphism 알림 적용기

Flutter로 노트 앱을 만들다 보면 어느 순간 기능은 다 있는데 “네이티브 앱 같지 않다"는 느낌이 든다. 버튼은 Glass 디자인 시스템으로 마이그레이션했는데, 정작 에디터 화면과 알림 히스토리는 초기 Material3 코드 그대로 방치돼 있었다. const Divider(), Colors.blue.shade50, OutlineInputBorder() — 이런 것들이 앱 전체 톤을 깨고 있었다. Bear 앱의 에디터 UX를 참고하고, iOS 26 Liquid Glass에서 영감 받은 Glassmorphism을 알림 카드에 적용해봤다. 이 글에서는 실제 삽질 과정과 구현 코드를 정리한다. Bear 앱은 왜 글쓰기가 편한가 Bear는 Apple Design Award를 받은 마크다운 노트 앱이다. “도구가 방해하지 않는다(Tools stay out of your way)“가 핵심 철학이다. 실제로 Bear 에디터를 열면 눈에 띄는 UI 요소가 거의 없다. 제목, 날짜, 본문 — 이 세 가지만 보인다. ...

2026-03-24 00:00 · 8분 소요 · Seunghan

모바일 앱 로그인이 자꾸 풀리는 진짜 이유 — JWT Refresh Token 아키텍처 완전 정리

모바일 앱을 운영하다 보면 가장 많이 받는 불만 중 하나가 “로그인이 자꾸 풀려요"다. 카카오톡이나 인스타그램은 한 번 로그인하면 직접 로그아웃하기 전까지 영원히 유지되는데, 내 앱은 왜 하루만 지나면 다시 로그인하라고 하는 걸까? 이 글은 Flutter 앱 + Rails 8 API 환경에서 로그인 세션이 반복적으로 풀리는 문제를 추적하고 해결한 과정을 기록한다. 단순히 “TTL을 늘려라"가 아니라, JWT 인증 시스템의 구조적 문제를 하나씩 찾아가는 과정이다. 증상: 1시간마다 로그인이 풀림 앱을 백그라운드에 두었다가 1시간 후에 열면 로그인 화면으로 돌아간다. 사용 중에는 문제가 없는데, 잠깐 앱을 닫았다 열면 세션이 사라진다. ...

2026-03-23 00:00 · 7분 소요 · Seunghan

App Store 스크린샷 리젝 2.3.3 해결기 — AI 생성 이미지에서 실제 앱 캡처로

App Store에 첫 앱을 제출했는데, 스크린샷 문제로 리젝당했다. 해결까지의 삽질 기록. 리젝 사유 Guideline 2.3.3 - Performance: Accurate Metadata The screenshots do not show the actual app in use in the majority of the screenshots. 심사 디바이스: iPad Air 11-inch (M3) 원인 Gemini Image Generation API로 Neo-Brutalism 스타일의 가짜 UI 마케팅 이미지를 만들어서 스크린샷으로 제출했다. 앱 화면과 전혀 다른 디자인이었으니 당연한 결과. Apple이 요구하는 건: 대다수(majority) 스크린샷이 실제 앱 사용 화면이어야 함 마케팅/프로모션 자료만으로는 부적절 스플래시/로그인 화면만으로도 부족 다만, 실제 앱 화면 + 텍스트 오버레이 조합은 허용된다. 대부분의 앱이 이 방식을 쓴다. ...

2026-03-11 00:00 · 4분 소요 · Seunghan

Flutter BottomSheet가 네비게이션 바를 덮는다면 — showDialog로 바꿔야 하는 이유

폼 입력이 필요한 화면에서 showModalBottomSheet를 쓰다 보면 자연스러운 UX처럼 느껴진다. 그런데 앱에 하단 네비게이션 바가 있으면 바텀시트가 올라오면서 네비게이션을 덮어버리는 문제가 생긴다. 기능적으로는 동작하지만, 시각적으로 답답하다. 세 가지 문제를 한 번에 해결했다. 바텀시트 → 중앙 모달 전환 TextButton 취소 버튼이 노란색으로 렌더링되어 안 보이는 가독성 문제 share_plus로 SQLite 파일 공유 시 발생하는 PlatformException 문제 1: BottomSheet가 네비게이션 바를 가린다 현상 showModalBottomSheet로 만든 입력 폼이 올라올 때 하단 네비게이션 바와 겹친다. isScrollControlled: true를 써도 시트가 네비게이션 위까지 올라와 버린다. ...

2026-03-09 00:00 · 4분 소요 · Seunghan

Flutter image_picker 카메라/갤러리 바텀시트 + Riverpod 빈도 기반 카테고리 자동 정렬 삽질기

Flutter로 시민 신고 앱을 만들면서 세 가지 UX 문제를 연달아 만났다. 사진 추가 버튼이 갤러리만 열어서 카메라 촬영이 불가능한 문제 카테고리가 늘어날수록 그리드가 길어져서 스크롤이 많아지는 문제 신고 대상(일반/긴급)이 바뀌어도 버튼 색상이 바뀌지 않아서 직관성이 떨어지는 문제 각각 어떻게 풀었는지 정리한다. 문제 1: image_picker가 갤러리만 열린다 현상 사진 추가 버튼이 pickImage(source: ImageSource.gallery)만 호출해서 카메라로 찍는 게 불가능했다. 앱 자체에 카메라 권한도 있고 NSCameraUsageDescription도 있는데 UI에서 선택지를 아예 안 줬던 것. ...

2026-03-09 00:00 · 6분 소요 · Seunghan

Flutter iOS TestFlight 업로드 실패: objective_c.framework 시뮬레이터 슬라이스 오류

Flutter 앱을 flutter build ipa --release로 빌드하고 TestFlight에 업로드했더니 altool이 거절했다. 원인, 삽질 과정, Makefile 자동화까지 정리한다. 오류 메시지 UPLOAD FAILED with 3 errors Invalid executable. The "Runner.app/Frameworks/objective_c.framework/objective_c" executable references an unsupported platform in the x86_64 slice. Simulator platforms aren't permitted. Invalid executable. The "Runner.app/Frameworks/objective_c.framework/objective_c" executable references an unsupported platform in the arm64 slice. Simulator platforms aren't permitted. Unsupported Architectures. The executable for Runner.app/Frameworks/objective_c.framework contains unsupported architectures '[x86_64]'. flutter build ipa는 성공했고 IPA 파일도 정상 생성됐다. 문제는 빌드 단계가 아니라 업로드 단계에서 발생했다. 즉, Xcode 설정이나 Flutter 프로젝트 구성 자체의 문제가 아니라 Flutter가 조용히 임베드하는 서드파티 프레임워크 바이너리 안에 문제가 있다는 뜻이다. ...

2026-03-09 00:00 · 6분 소요 · Seunghan

Flutter + Web 디자인 토큰 동기화 — Storybook 기반 디자인 시스템 구축기

Flutter 앱을 개발하다 보면 항상 부딪히는 문제가 있다. 디자이너는 Figma나 웹 기반 도구로 작업하는데, 개발자는 Dart 코드에 색상을 하드코딩한다. Color(0xFF10B981) 같은 값이 app_colors.dart에만 있고, 웹 쪽 CSS에는 #10B981로 따로 있다. 두 곳을 따로 관리하다 보면 어느 순간 서로 달라져 있다. 이번에 Svelte+Storybook 기반 웹 디자인 키트와 Flutter 앱의 토큰을 하나의 기준으로 맞추는 작업을 했다. 삽질한 내용 위주로 정리한다. 문제: 두 곳에 사는 디자인 토큰 기존 상태는 이랬다. 웹 (CSS) :root { --color-primary: #0000FF; /* 기본값 그대로 */ --radius: 0px; } Flutter (Dart) ...

2026-03-08 00:00 · 4분 소요 · Seunghan

Flutter 앱에 iOS 위젯 추가하기 — pbxproj 수동 편집부터 딥링크까지

Flutter 앱에 iOS 홈 화면 위젯을 붙이는 작업을 했다. 처음엔 단순해 보였는데 생각보다 손댈 곳이 많았다. Xcode GUI를 쓰면 간단하지만 CLI 환경에서 project.pbxproj를 직접 수정해야 하는 경우를 위해 전 과정을 정리한다. 목표 홈 화면 위젯 2종: 2×2(systemSmall), 2×1(systemMedium) 위젯에서 앱의 미처리 항목 수를 실시간으로 표시 위젯 버튼 탭 → 앱 특정 화면으로 이동 (딥링크) 1. 위젯 익스텐션 파일 구성 ios/ ├── Runner/ │ ├── AppDelegate.swift │ ├── Info.plist │ └── Runner.entitlements └── ReceiptWidget/ ← 새로 추가 ├── ReceiptWidget.swift ├── Info.plist └── ReceiptWidget.entitlements ReceiptWidget.swift 하나에 Provider, View, Widget, Bundle을 모두 담았다. 사이즈별로 View를 분리하고 @Environment(\.widgetFamily)로 분기하는 패턴이 깔끔하다. ...

2026-03-08 00:00 · 5분 소요 · Seunghan
Rails8 Cross Project Patterns And Improvements

Rails 8 프로젝트 간 패턴 교차 적용 — rack-attack, PWA 배너, Sentry, FCM 멀티디바이스

두 개의 Rails 8 프로젝트를 병렬로 운영하다 보면 한쪽에서 공들여 만든 패턴이 다른 쪽에는 빠져있는 경우가 자주 생긴다. 기능을 구현할 때는 당장의 요구사항에 집중하다 보니 다른 프로젝트의 좋은 구현을 챙기지 못하는 것이다. 시간이 지날수록 두 프로젝트 사이의 품질 격차가 벌어지고, 한쪽에서는 해결된 문제를 다른 쪽에서 다시 삽질하는 상황이 생긴다. 이번에 두 프로젝트를 나란히 놓고 비교하면서 빠진 부분을 서로 채워주는 작업을 했다. 주로 보안, PWA 경험, 에러 추적, 푸시 알림 인프라에 관한 내용이다. 여섯 가지 항목 모두 “한번 제대로 만들면 모든 프로젝트에 적용해야 하는” 기반 인프라 성격의 것들이다. ...

2026-02-20 00:00 · 10분 소요 · Seunghan
Rails Rfc3161 Tsa Blockchain Merkle Debugging

RFC 3161 TSA 타임스탬프 + 블록체인 Merkle 앵커링: Rails에서 삽질 기록

전자계약 보관 시스템에 법적 증거력을 부여하기 위해 두 가지를 동시에 구현해야 했다: 블록체인 Merkle Tree 앵커링 — 계약 해시들을 모아 Merkle Root를 L2 체인에 기록 RFC 3161 TSA 타임스탬프 — 신뢰할 수 있는 제3자 시간 증명 간단해 보였는데, 삽질의 연속이었다. 각 문제를 해결하는 데 예상보다 훨씬 많은 시간이 걸렸고, 특히 Ruby 4.0의 API 변경과 Rails 8의 멀티 데이터베이스 동작이 예상치 못한 방식으로 얽혔다. 1. RFC 3161 TSA란? RFC 3161은 Time-Stamp Authority(TSA) 프로토콜로, 특정 데이터가 특정 시점에 존재했음을 제3자가 증명해주는 국제 표준(RFC 3161, RFC 5816로 업데이트)이다. 법적 맥락에서는 “이 문서가 이 날짜에 존재했음"을 공인 제3자가 서명으로 보증한다는 의미다. ...

2026-02-06 00:00 · 8분 소요 · Seunghan
Rails Flutter Iap Unimplemented Features Audit

Rails + Flutter 앱 미구현 항목 점검 및 인앱 결제(IAP) 연동 기록

음성 메시지 기반 소셜 앱을 출시 준비하면서 미구현 항목을 전수 점검했다. route는 있는데 controller action이 없거나, Flutter UI는 완성됐는데 결제 로직이 // TODO 로 막혀 있는 경우들이 꽤 있었다. 정리하고 하나씩 구현한 기록. 미구현 항목 점검 방법 백엔드 점검 가장 빠른 방법은 routes.rb와 실제 controller를 비교하는 것이다. bundle exec rails routes | grep -v "^ #" route가 있는데 controller에 해당 action이 없으면 런타임에 ActionController::MethodNotImplemented 에러가 난다. 미리 찾아내는 게 낫다. 프론트엔드 점검 Flutter는 // TODO, SnackBar(content: Text('기능 준비 중')) 패턴을 검색하면 빠르다. ...

2026-01-30 00:00 · 5분 소요 · Seunghan
Mcp Flutter Rails System Category Debug

MCP 도구 연동부터 Flutter 설정 토글까지 — 삽질 기록

MCP 도구로 서버 사이드에 카테고리를 생성했다. 그런데 모바일 앱에서 새 카테고리가 보이지 않았다. 간단해 보이는 문제였는데, 파고들수록 여러 레이어가 얽혀 있었다. 문제의 시작: MCP로 만든 카테고리가 앱에 안 보인다 MCP 도구를 통해 dev/, memory 같은 시스템 카테고리를 서버에 생성했다. API를 직접 호출하면 데이터가 있다. 앱을 리프레시해도 나타나지 않는다. 첫 번째 가설: 앱이 캐시를 사용하는 건가? → 아니다. PapersLoadRequested + PaperCategoriesLoadRequested 이벤트를 순서대로 디스패치하고 있었고, 서버에서 정상 응답이 오고 있었다. 두 번째 가설: API가 필터링하고 있나? → Rails 컨트롤러를 봤다. 필터 없음. 전체 반환 중. ...

2025-12-13 00:00 · 5분 소요 · Seunghan
Api Response Wrapper Token Parsing Debug

로그인이 자꾸 풀린다 — API 래퍼 포맷 불일치가 만든 연쇄 버그

모바일 앱에서 로그인이 자꾸 풀린다. 로그인 직후는 정상인데, 앱을 잠깐 백그라운드로 내렸다가 다시 열면 로그인 화면이 뜬다. SecureStorage에 토큰 저장도 확인했고, Dio 인터셉터로 401 자동 갱신도 구현되어 있는데 왜? 증상 재현 앱 로그인 → 정상 동작 액세스 토큰 만료 시점 전후로 앱 재시작 → 세션 복원 실패, 강제 로그아웃 서버 로그에서 힌트를 찾았다. FormatException: "user" field is missing or null 토큰 갱신 응답을 파싱하다가 터지고 있었다. 구조 파악 서버는 모든 API 응답을 공통 래퍼로 감싼다. ...

2025-12-02 00:00 · 4분 소요 · Seunghan
Flutter Store Beta Mode Purchase Logic

Flutter IAP 스토어 베타 모드 설계와 구매 로직 보강 실전기

Flutter 앱에서 IAP(In-App Purchase)를 구현하고 오픈 베타를 운영하다 보면, “베타인데 스토어는 유료 가격이 그대로 보인다"거나 “Restore하면 크레딧이 중복 지급된다” 같은 허점들이 드러난다. 실제로 마주친 문제들과 해결 과정을 정리한다. 1. 베타 모드와 스토어의 모순 문제 // constants.dart static const bool isOpenBeta = true; isOpenBeta = true이면 spendCredits()에서 크레딧을 차감하지 않는다. AI 기능이 무료라는 뜻이다. // credit_repository.dart Future<bool> spendCredits(int amount, String reason) async { if (AppConstants.isOpenBeta) { // 크레딧 차감 안 함 — 무료 await _addTransaction(CreditTransaction( amount: 0, reason: '$reason (Beta - Free)', )); return true; } // ... 실제 차감 로직 } 그런데 스토어 화면은 ₩3,300, ₩11,000, ₩29,900 가격이 그대로 표시되고 구매 버튼도 활성화되어 있었다. 베타인데 돈을 받겠다는 건지, 무료인데 왜 가격이 보이는지 — 사용자 입장에서 혼란스럽다. ...

2025-11-08 00:00 · 4분 소요 · Seunghan
Flutter Ipa No Codesign Api Key Testflight

flutter build ipa가 갑자기 실패한다면 — Development 인증서 없이 TestFlight 배포하는 법

Flutter iOS 앱을 여러 Apple 계정으로 관리하다 보면 한 프로젝트에서는 make testflight가 잘 되는데 다른 프로젝트에서는 동일한 Makefile이 실패하는 상황이 생긴다. 오늘 겪은 케이스를 정리한다. 증상 ❌ Error (Xcode): No signing certificate "iOS Development" found: No "iOS Development" signing certificate matching team ID "XXXXXXXX" with a private key was found. flutter build ipa 실행 시 위 오류로 실패한다. Distribution 인증서는 키체인에 있는데 Development 인증서가 없다는 메시지다. 원인: flutter build ipa 내부에서 일어나는 일 flutter build ipa는 내부적으로 다음 순서로 동작한다. ...

2025-11-04 00:00 · 4분 소요 · Seunghan
Flutter Ios Build Dark Mode Logout Debugging

TestFlight 올리려다 터진 Flutter iOS 빌드 오류 5종 — 다크모드·로그아웃 버그까지 한 번에

빌드를 올리려는데 한꺼번에 여러 문제가 터졌다. 코드 생성기가 실패하고, 없어진 파일이 있고, 빌드 번호 규칙을 몰라서 거절당하고, UI는 다크모드가 하드코딩되어 있고, 로그아웃은 토큰을 안 지웠다. 하나씩 정리한다. 1. Retrofit 옵션 파라미터 문법 오류 → .g.dart 생성 실패 증상 dart run build_runner build 실행 시 일부 API 서비스 파일에서: Expected to find ')' 원인 Retrofit의 추상 메서드에서 옵션 파라미터({}) 위치를 잘못 씀. // ❌ 잘못된 문법 — 닫는 중괄호 뒤에 쉼표 Future<Response> getItems( @Path('id') String id, {@Query('type') String? type}, // ← 이렇게 쓰면 안 됨 ); // ✅ 올바른 문법 — 포지셔널 파라미터 뒤에 { 바로 열기 Future<Response> getItems( @Path('id') String id, { @Query('type') String? type, }); Dart 문법에서 옵션 파라미터는 마지막 포지셔널 파라미터 바로 뒤에 {를 열어야 한다. },로 닫은 뒤 쉼표를 찍으면 파서가 다음 인자로 인식하려다 실패한다. ...

2025-11-01 00:00 · 5분 소요 · Seunghan
Apple Sso 403 Email Verified Type Mismatch

Apple Sign-In 403 에러: email_verified 타입 불일치와 복붙 버그 3종 세트

Apple Sign-In이 403 Forbidden으로 실패하는데, Google Sign-In은 정상 동작하는 상황이었다. 동일한 스택(Rails 8 + Flutter)의 다른 프로젝트에서는 Apple 로그인이 잘 되고 있어서 비교 분석했다. 결론부터 말하면, 세 가지 독립적인 버그가 동시에 존재했고, 모두 “Google SSO 코드를 복붙해서 Apple SSO를 만든” 과정에서 생겨났다. 증상 Apple 로그인: 403 Forbidden Google 로그인: 정상 성공 에러 메시지: "Email not verified by Apple" 개발 환경에서는 재현되지 않고 프로덕션에서만 발생 (Apple 테스트 계정 이슈) 배경: Apple과 Google JWT는 다르다 OAuth 2.0 / OIDC 표준은 email_verified 필드가 boolean이어야 한다고 명시하고 있다. 하지만 현실에서 Apple은 이 필드를 문자열 "true"로 반환하는 경우가 있다. 이건 Apple의 공식 문서에도 명확히 나와 있지 않은 엣지 케이스다. ...

2025-10-25 00:00 · 6분 소요 · Seunghan
Rails Flutter Server Health Check 4 Issues

Rails + Flutter 앱 서버 점검기: 한 번에 터진 4가지 문제와 해결

앱 테스트 빌드를 올리고 직접 돌려보니 한꺼번에 4가지가 안 됐다. Google 로그인 실패, AI 일정 생성이 엉뚱한 결과, 알림 버튼 누르면 크래시, 인기 여행지 섹션이 텅 비어있음. 하나씩 원인을 찾고 고친 과정을 정리한다. 1. Google SSO는 실패하는데 Apple 로그인은 성공 증상 Apple Sign-In은 정상 동작하지만 Google Sign-In만 500 에러. 클라이언트에서는 로그인 실패 토스트만 보인다. 원인 컨트롤러는 이전 커밋에서 수정했지만, Model의 from_omniauth 메서드는 그대로였다. # User 모델 — 마이그레이션 후에도 옛날 컬럼명 참조 def self.from_omniauth(auth) user = find_or_initialize_by(provider: auth.provider, uid: auth.uid) # uid 컬럼 없음 user.image = auth.info.image # image 컬럼도 없음 end DB 스키마에서는 uid → provider_uid, image → avatar_url로 마이그레이션된 상태. 컨트롤러 쿼리는 수정했지만 모델 내부 메서드가 여전히 옛 컬럼을 참조하고 있었다. ...

2025-10-15 00:00 · 5분 소요 · Seunghan
Flutter Sync Queue Aggressive Error Handling

Flutter Sync Queue에서 불필요한 에러가 반복 노출되는 문제 해결

모바일 앱에서 오프라인 동기화를 위해 Transactional Outbox 패턴을 구현하던 중, 동기화가 실제로는 정상 완료되었는데도 “동기화 실패” 에러가 반복적으로 사용자에게 노출되는 문제를 발견했다. 현상 앱에서 다음과 같은 에러가 반복적으로 발생했다: AppException: Failed to push changes: AppException: Push completed with failures; retry count: 2, pending changes remain in queue. 서버 로그를 확인하면 동기화 pull은 정상 동작하고, 실제 데이터도 이미 동기화된 상태였다. 구조 파악: Transactional Outbox 패턴 앱의 동기화 구조는 다음과 같다: ┌────────────────┐ ┌──────────────┐ ┌────────────────┐ │ Local DB │────▶│ Sync Queue │────▶│ Remote API │ │ (Drift/SQLite)│ │ (Outbox) │ │ (Rails) │ └────────────────┘ └──────────────┘ └────────────────┘ 로컬에서 데이터 변경 → sync queue에 pending 아이템 추가 performFullSync() 호출 시 pull → push 순서로 동기화 push 단계에서 queue의 각 아이템을 서버에 전송 성공하면 queue에서 제거, 실패하면 retry count 증가 원인 분석 _pushChanges() 메서드의 에러 처리 로직에 문제가 있었다: ...

2025-10-04 00:00 · 4분 소요 · Seunghan
Flutter Rails Auth Session Persistence Debugging

Flutter + Rails 인증 세션이 계속 풀리는 문제 - 3가지 원인과 해결

Flutter BLoC 앱에서 로그인을 해도 세션이 자꾸 풀린다. 분명 SecureStorage에 토큰도 저장하고, Dio 인터셉터로 401 시 자동 갱신도 구현했는데 왜? 서버 로그부터 시작해서 원인 3개를 찾고 모두 고친 과정을 정리한다. 기술 스택 모바일: Flutter + BLoC 패턴 + Dio HTTP + SecureStorage 서버: Rails 8 API + ActionCable WebSocket 인증: SHA-256 digest 기반 access token + JTI refresh token (90일) 실시간: ActionCable WebSocket (토큰 기반 인증) 증상 로그인 직후는 정상 동작 시간이 지나면 API 요청이 401로 실패 토큰 갱신은 되는 것 같은데 WebSocket이 끊어짐 결국 앱이 미인증 상태로 전환 원인 1: 레거시 코드의 유령 - DTA 잔존 메서드 발견 서버 로그에서 토큰 갱신 시 user.tokens 관련 에러가 간헐적으로 보였다. 이전에 devise_token_auth(DTA)를 사용하다가 자체 토큰 시스템으로 마이그레이션했는데, token_refresh_service.rb에 DTA 시절 코드가 남아 있었다. ...

2025-09-27 00:00 · 4분 소요 · Seunghan
Flutter Glassappbar Tabbar Overflow Colors White Lightmode

Flutter UI 전수조사 — GlassAppBar TabBar overflow와 Colors.white 라이트모드 버그

Flutter 앱을 어느 정도 만들다 보면 꼭 한 번씩 마주치는 두 가지 버그가 있다. 하나는 bottom overflowed by N pixels 에러, 다른 하나는 라이트모드에서 텍스트가 배경에 묻혀 보이지 않는 현상이다. 둘 다 원인은 단순한데, 전체 화면을 대상으로 전수조사하기 전까지는 “일부 화면에서 이상하다” 수준으로만 인식하기 쉽다. 이번에 앱 전체 50개 페이지를 한 번 훑어보고 나서야 패턴이 보였다. GlassAppBar + TabBar overflow의 진짜 원인 커스텀 GlassAppBar를 만들어서 쓰고 있었다. bottom: TabBar(...) 를 붙이면 AppBar 아래에 탭이 생기는 구조다. ...

2025-09-24 00:00 · 3분 소요 · Seunghan
Flutter Bloc Infinite Scroll Pagination

Flutter BLoC 무한스크롤 — infinite_scroll_pagination 없이 직접 구현하는 법

목록을 처음에 전부 로드하면 느리다. 사용자가 스크롤할수록 자연스럽게 다음 데이터를 불러오는 무한스크롤이 필요했다. infinite_scroll_pagination 같은 패키지도 있지만, 기존 BLoC 구조에 그대로 얹으려면 상태 설계를 패키지 방식에 맞춰야 해서 오히려 복잡해지는 경우가 있다. 외부 의존 없이 ScrollController만으로도 충분히 만들 수 있어서 그 방향으로 구현했다. 왜 Offset 기반인가 페이지네이션 방식은 두 가지다. Offset 기반 (page 번호) GET /items?page=1&per_page=20 GET /items?page=2&per_page=20 Cursor 기반 (마지막 아이템 ID) GET /items?cursor=abc123&per_page=20 Cursor 방식이 “데이터가 중간에 삽입/삭제돼도 중복/누락 없다"는 점에서 이론적으로 더 우수하다. 하지만 대상 데이터가 법령/규정처럼 자주 바뀌지 않는 정적 문서라면 Offset 방식으로 충분하다. ...

2025-09-20 00:00 · 5분 소요 · Seunghan
Ios Sso Entitlements Testflight Errors

iOS TestFlight 배포 삽질 모음: SSO 에러부터 entitlements mismatch까지

Flutter 앱 여러 개를 TestFlight에 올리면서 반복적으로 마주친 에러들을 정리했다. 1. Apple Sign-In 에러 1000 SignInWithAppleAuthorizationException(AuthorizationErrorCode.unknown, The operation couldn't be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.)) 원인 Runner.entitlements에 Sign in with Apple capability가 없어서 발생한다. 해결 두 곳 모두 설정해야 한다. ① ios/Runner/Runner.entitlements <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> ② Apple Developer Console developer.apple.com → Identifiers → 앱 Bundle ID 선택 → Sign in with Apple 체크 → Save 프로비저닝 프로파일이 이미 있다면 재생성이 필요하다. ...

2025-08-30 00:00 · 4분 소요 · Seunghan
Flutter Deprecated Api Mass Fix

Flutter Deprecated API 대규모 수정 - withOpacity, DropdownButtonFormField, Switch 등

Flutter 프로젝트를 오래 유지하다 보면 flutter analyze가 수백 개의 deprecated 경고를 뱉는 시점이 온다. 기능은 잘 돌아가지만 Warning이 쌓이면 진짜 문제가 묻힌다. 이번에 한 번에 200개 넘는 deprecated 경고를 정리하면서 나온 패턴들을 정리한다. deprecated 경고를 방치하면 결국 세 가지 문제로 이어진다. 첫째, 다음 Flutter 메이저 업그레이드 때 deprecated가 removal로 전환되어 컴파일 에러가 터진다. 둘째, 실제 버그나 타입 에러가 경고 노이즈에 묻혀 코드 리뷰에서 놓치기 쉽다. 셋째, 팀 합류 시 “왜 경고가 이렇게 많아요?“라는 질문이 나오는 순간 기술 부채를 설명해야 하는 부담이 생긴다. 한 번 정리해두면 이후 유지 비용이 훨씬 줄어든다. ...

2025-07-20 00:00 · 6분 소요 · Seunghan
Flutter Dead Ui Fix Xcode26 Widget Bug

Flutter 미구현 UI 컴포넌트 연결 + Xcode 26 베타 WidgetKit 설치 버그 우회

Flutter 앱 작업 중 두 가지 문제를 연달아 처리했다. 하나는 UI 차원의 문제 — onTap: () {} 로 껍데기만 있는 컴포넌트들을 실제로 연결하는 작업. 다른 하나는 Xcode 26.2 베타에서 시뮬레이터에 앱을 설치하면 익스텐션 때문에 앱 자체가 설치되지 않는 문제다. 1. 동작하지 않는 UI 컴포넌트 연결 Flutter 개발 중 흔히 발생하는 상황: 화면은 다 만들어졌는데 버튼에 onPressed: () {}, 카드에 onTap: () {}만 달려 있고 실제 동작이 없는 상태. 패턴별 정리 알림 벨 아이콘 ...

2025-07-16 00:00 · 4분 소요 · Seunghan
Flutter Bloc Complex State Management

Flutter BLoC 상태 설계 — 단순 목록을 넘어 복잡한 단계 흐름 다루기

목록을 불러오고 보여주는 수준의 BLoC는 어렵지 않다. 문제는 세션 기반의 흐름, 예를 들어 “세션을 만들고 → 질문을 추가하고 → 답변을 받고 → 완료” 같은 단계적 워크플로우를 BLoC 하나로 관리할 때다. 이런 패턴은 리뷰 Q&A 시스템, 멀티스텝 설문, 온보딩 플로우 등 실무에서 자주 등장한다. 단순히 isLoading 불리언 하나로 버티다가 UX가 무너지는 경험을 해봤다면, 이 글이 도움이 될 것이다. 왜 BLoC가 복잡한 흐름에 적합한가 BLoC(Business Logic Component)의 핵심 가치는 UI와 비즈니스 로직의 완전한 분리다. 단순한 데이터 페칭에서는 이 장점이 와닿지 않는다. 하지만 상태가 여러 단계로 전이되고, 같은 화면에서 독립적인 로딩 피드백이 필요해질 때 BLoC의 구조적 이점이 드러난다. ...

2025-07-06 00:00 · 7분 소요 · Seunghan
Firebase Phone Auth Not Working Diagnosis

Flutter Firebase Phone Auth - SMS가 안 와요? 진단부터 코드 수정까지

Flutter 앱에 전화번호 인증을 붙이고 나서 “인증번호가 안 와요"라는 상황을 마주쳤다. 그리고 개발용 bypass 버튼을 눌러서 인증을 건너뛰고 회원가입을 시도하면 서버에서 “인증이 완료되지 않은 전화번호입니다"가 떴다. 두 문제를 같이 정리한다. Firebase Phone Auth는 처음 연동하면 단순해 보이지만, 실제로는 플랫폼(Android/iOS)별 설정, 토큰 검증 구조, 개발/프로덕션 환경 간의 동작 차이 등 여러 레이어에서 문제가 생길 수 있다. 이 글은 실제로 겪은 증상과 그 원인, 해결책을 단계별로 정리한 것이다. 구조부터 파악 Flutter Firebase Phone Auth의 흐름은 이렇다. ...

2025-07-02 00:00 · 6분 소요 · Seunghan
Ios Codesign Testflight Full Setup

iOS 코드 서명 처음부터 끝까지 — Distribution Cert에서 TestFlight 업로드까지 수동 세팅

Flutter 앱을 TestFlight에 올리는 과정에서 코드 서명 관련 설정을 처음부터 다시 잡으면서 정리한 내용이다. Xcode 자동 서명이 아닌 수동 + App Store Connect API Key 방식으로 진행했다. 전체 흐름 [1] Distribution Certificate 발급 [2] APNs Certificate 발급 (CSR 생성 필요) [3] App ID에 Push Notifications 활성화 [4] Provisioning Profile 생성 (App Store, Push 포함) [5] xcodebuild archive + export (API Key 인증) [6] xcrun altool로 TestFlight 업로드 1. Distribution Certificate Apple Developer → Certificates → + → Apple Distribution 선택. ...

2025-06-18 00:00 · 4분 소요 · Seunghan
Dart Api Integration

DART Open API 연동 삽질기 (Rails + Flutter)

DART Open API를 Rails 백엔드에 연동하면서 겪은 과정을 정리한다. 공시 모니터링, 감사의견, 지배구조, 재무지표, 지분공시 5개 영역을 구현했고 각 단계마다 삽질이 있었다. 구현 구조 각 데이터 유형마다 모델과 ActiveJob을 하나씩 만들었다. Job은 DART API를 호출해서 upsert_all로 DB에 넣는 단순한 구조다. DartCorpCodeSyncJob → dart_companies (기업 마스터) DartDisclosureSyncJob → dart_disclosures (공시 목록) DartMajorEventSyncJob → dart_major_events (상장폐지 트리거 이벤트 — DS001) DartAuditOpinionSyncJob → dart_audit_opinions (감사의견 — DS002/DS003) DartGovernanceSyncJob → dart_executives / dart_major_shareholders (DS004/DS005) DartFinancialIndexSyncJob → dart_financial_indexes (fnlttSinglAcntAll) DartEquityReportSyncJob → dart_equity_reports (지분공시) 삽질 1: upsert_all + update_only + updated_at 중복 가장 먼저 터진 오류. ...

2025-06-01 00:00 · 5분 소요 · Seunghan

Rails + Flutter 풀스택에서 기능 하나를 웹-API-앱까지 관통시키는 패턴

하나의 기능이 세 개의 레이어를 관통할 때 웹 서비스에 새 기능을 추가하면 끝이 아니다. 모바일 앱이 있으면 API serializer를 거쳐 Flutter 모델까지 맞춰야 한다. 이 과정에서 빠뜨리기 쉬운 게 한두 가지가 아니다. 테니스 대회 운영 서비스를 만들고 있는데, 선수 성별 토글 하나를 추가하는 작업이 결국 Rails enum 정의 → ERB 뷰 토글 버튼 → Controller 액션 → API v2 serializer → Flutter Freezed 모델 → Flutter UI 위젯까지 6단계를 거쳤다. 그 과정에서 만난 에러들과 해결 패턴을 정리했다. ...

2025-03-25 00:00 · 8분 소요 · Seunghan
개인정보처리방침 이용약관 면책조항 문의