Mcp Flutter Rails System Category Debug

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

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

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

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

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

2025-12-02 · 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 · 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 · 4분 소요 · Seunghan
Flutter Bloc Infinite Scroll Pagination

Flutter BLoC 무한스크롤 구현 — 외부 패키지 없이 레이어별로 설계하기

목록을 처음에 전부 로드하면 느리다. 사용자가 스크롤할수록 자연스럽게 다음 데이터를 불러오는 무한스크롤이 필요했다. 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 · 5분 소요 · Seunghan
Flutter Clean Architecture Multi Feature

Flutter Clean Architecture 실전 - Feature 여러 개 한 번에 추가하기

Flutter 앱에 기능을 한 번에 여러 개 추가할 때 가장 먼저 고민되는 건 폴더 구조다. 기능 하나하나는 단순해 보여도, 여러 개가 동시에 들어오면 금방 엉킨다. Feature별 폴더 구조 Clean Architecture를 기반으로 각 Feature를 아래 구조로 만든다. lib/features/{feature_name}/ ├── data/ │ ├── datasources/ # API 호출 │ └── repositories/ # 인터페이스 구현체 ├── domain/ │ ├── entities/ # 순수 데이터 모델 │ └── repositories/ # 인터페이스 정의 └── presentation/ ├── bloc/ # BLoC (이벤트/상태) └── pages/ # UI 이걸 따르면 기능이 몇 개가 늘어도 구조는 동일하다. 새 기능 추가 = 폴더 복사 + 내용 채우기 수준이 된다. ...

2025-07-09 · 3분 소요 · Seunghan
Flutter Bloc Complex State Management

Flutter BLoC - Q&A 세션처럼 상태가 복잡할 때 설계하기

목록을 불러오고 보여주는 수준의 BLoC는 어렵지 않다. 문제는 세션 기반의 흐름, 예를 들어 “세션을 만들고 → 질문을 추가하고 → 답변을 받고 → 완료” 같은 단계적 워크플로우를 BLoC 하나로 관리할 때다. 상태를 먼저 그려라 BLoC를 코딩하기 전에 상태부터 정의하는 게 순서다. 이 워크플로우에서 UI가 보여줘야 하는 상태를 나열하면: 초기 (아무것도 없음) 세션 목록 로딩 중 세션 목록 표시 새 세션 생성 중 세션 상세 로딩 중 세션 상세 표시 (질문 목록 포함) 질문 추가 중 답변 입력 중 오류 abstract class ReviewQaState {} class ReviewQaInitial extends ReviewQaState {} class ReviewQaLoading extends ReviewQaState {} class ReviewQaSessionListLoaded extends ReviewQaState { final List<QaSession> sessions; ReviewQaSessionListLoaded(this.sessions); } class ReviewQaSessionLoaded extends ReviewQaState { final QaSession session; final List<ReviewQuestion> questions; ReviewQaSessionLoaded({required this.session, required this.questions}); } class ReviewQaQuestionAdded extends ReviewQaState { final ReviewQuestion question; ReviewQaQuestionAdded(this.question); } class ReviewQaError extends ReviewQaState { final String message; ReviewQaError(this.message); } 상태 클래스를 이렇게 구체적으로 나눠야 UI에서 if (state is ReviewQaSessionLoaded) 처럼 명확하게 분기할 수 있다. ...

2025-07-06 · 3분 소요 · Seunghan
개인정보처리방침 문의