Women's Health · Fertility
마미플랜 momiplan
난임 · 임신 준비 여성을 위한 주기 트래킹 · IVF 기록 · 심박 운동 코칭 앱
- 클라이언트
- 헬스케어 스타트업 (B2B2C · 비공개)
- 기간
- 2026.01 — 2026.07 · 약 7개월
- 역할
- Flutter 앱 · Firebase 백엔드 · 관리자 함수 단독 개발
- 팀 구성
- 개발자 1인(본인) + 클라이언트 기획 · 디자인
- 플랫폼
- iOS · Android · Web(Admin)
- 상태
- v1.0.0 (빌드 16) 납품, iOS · Android 릴리스 빌드
110 files
Dart
1.5만 줄
코드
34 단계
온보딩 설문
1.0.0+16
릴리스 빌드
Overview
프로젝트 개요
마미플랜은 난임 치료 중이거나 임신을 준비하는 여성을 위한 동반 서비스입니다. 치료 단계 · 다니는 병원 · 시도 기간 · 복용 보조제 · 수면 문제 등을 묻는 34단계 온보딩 설문으로 사용자를 이해하고, 생리 주기와 시험관(IVF) 시술 과정을 세밀하게 기록합니다.
BLE 심박 밴드와 연동한 운동 코칭, 임신 성공 예측률 대시보드, 병원 · 컬럼 · 상담 예약 라운지까지 하나의 앱에 담았고, 민감한 의료 데이터를 다루는 서비스답게 '누가 무엇을 봤는지'를 추적할 수 있는 감사 구조로 설계했습니다.
My Role
담당 업무
- 01Flutter 앱 전체 설계 · 구현 (110개 Dart 파일, 약 1.5만 줄)
- 02Google · Apple 소셜 로그인, 약관 4종 동의, 34단계 온보딩 설문 플로우
- 03커스텀 달력 · 주기 분석 · 레이더 차트 · 하트 게이지 등 CustomPainter 기반 시각화
- 04flutter_reactive_ble 기반 심박 밴드 스캔 · 연결 · 재연결 · 운동 세션 상태 머신
- 05Firestore 데이터 모델 · 보안 규칙, Cloud Functions(서울 리전) 관리자 API · 감사 로그
- 06iOS · Android 순차 빌드 자동화 스크립트, 빌드 번호 자동 증가
Features
핵심 기능
생리 주기 트래킹
시작 · 종료일 기록, 주기 길이 계산, 월 그리드 · 데이 셀 · 툴팁 · 마커를 갖춘 커스텀 달력, 주기 길이 차트.
IVF 시술 기록
과배란 유도(난포 크기 슬라이더, 복부 불편감 스코어, 증상), 난자 채취, 신선 · 동결 배아 이식까지 시술 단계별 전용 화면.
증상 기록
출혈량 4단계, 분비물 유형 4종 등 SVG 아이콘 기반 직관적 기록 UI.
임신 성공 예측률 대시보드
하트 게이지 + 퍼센타일 + 5축 레이더 차트를 CustomPainter로 직접 구현.
BLE 심박 운동 코칭
밴드 스캔 → 연결 → 카운트다운 → 모니터링 → 종료. 처방(목표 40분, HR 100~140) 대비 실시간 '좋아요 / 조절하세요' 피드백.
라운지 · 상담 · 구독
정보 아티클, 병원 · 컬럼 · 커머스 카드, 코디네이터 · 심리 상담 예약, 4개 요금제 비교 화면(결제 연동은 후속 범위).
Architecture
설계 원칙
프라이버시 우선 데이터 모델
모든 사용자 데이터는 users/{uid} 하위에만 존재하고 Firestore 규칙으로 본인만 읽기 · 쓰기. 관리자 명단과 감사 로그는 클라이언트 접근을 완전 차단.
감사 로그 경유 관리자 API
관리자 대시보드는 Firestore를 직접 읽지 않고 Callable Functions를 경유하며, 조회 1건마다 access_logs에 기록.
시계열 심박 저장 설계
초당 BPM을 1분 단위 chunk 문서(3자리 패딩 ID)로 매분 autosave. 신호 없는 초는 null 보존, 서버에서 chunk를 이어 붙여 CSV로 내려받기.
비정상 종료 복구
앱 재실행 시 inProgress로 남은 세션을 abnormal로 마킹해 데이터 정합성 유지.
Challenges
기술 과제와 해결
BLE 권한 모델이 다른 두 OS를 하나의 상태로
Problem
Android 12+는 BLUETOOTH_SCAN / CONNECT를 명시 요청해야 하고, iOS는 권한 요청 API 자체가 없어 스캔을 시작해야 시스템이 묻습니다.
Solution
Android는 manifest neverForLocation으로 위치 권한을 회피하며 명시 요청, iOS는 즉시 스캔 후 BleStatus.unauthorized를 수신하는 방식으로, 두 흐름을 하나의 Provider 상태로 정규화했습니다.
await 중 화면이 닫히면 크래시
Problem
스캔 · 연결을 기다리는 동안 화면이 dispose되면 이미 해제된 Provider에 notify하며 크래시가 발생했습니다.
Solution
_disposed 플래그와 _safeNotify() 패턴으로 수명주기를 방어했습니다.
끊긴 센서가 스캔에 안 잡히는 재연결 문제
Problem
방금 끊긴 심박 밴드는 아직 advertising을 재개하지 않아 재스캔으로는 찾을 수 없었습니다.
Solution
마지막 device id로 직접 connect를 먼저 시도하고, 실패 시 스캔으로 폴백하도록 재연결 순서를 바꿨습니다.
Apple 로그인은 처음 한 번만 이름을 준다
Problem
Sign in with Apple은 최초 인증에서만 이름 · 이메일을 내려주기 때문에 이후 로그인에서는 프로필이 비어 버립니다.
Solution
최초 인증 시점에 즉시 저장하고 한국어 기준 성+이름 순으로 결합, nonce는 SHA-256으로 처리했습니다.
시스템 글꼴 배율에도 깨지지 않는 달력
Problem
접근성 글꼴 배율을 키우면 7칸 데이 셀렉터 그리드와 카드 정렬이 무너졌습니다.
Solution
셀 크기 상한을 계산식으로 고정하고 카드 제목 줄 수를 고정해 요소 정렬을 유지했습니다. 계산 근거는 코드 주석으로 남겼습니다.
iOS · Android 병렬 빌드 충돌
Problem
두 플랫폼을 동시에 빌드하면 flutter startup lock과 ios/Flutter/ephemeral 경합으로 'Unable to delete .packages'가 발생했습니다.
Solution
순차 빌드 스크립트로 고정하고, Xcode 실행 감지 경고, 성공 시에만 빌드 번호 증가, APK 복사, workspace 자동 열기까지 자동화했습니다.
Tech Stack
기술 스택
App
- Flutter · Dart 3.10
- Material 3 · 자체 컬러 토큰
- Provider
- flutter_reactive_ble
- lottie · flutter_svg
- permission_handler · app_links
Auth · Backend
- Firebase Auth (Google · Apple)
- Cloud Firestore · Storage
- Cloud Functions v2 (Node 20, asia-northeast3)
- Firebase Hosting (관리자 대시보드)
Quality
- flutter_lints + 커스텀 analysis_options
- 도메인 로직 단위 테스트 (주기 기록 · HR 구간 · BLE UUID)
- iOS · Android 빌드 자동화 스크립트
Outcomes
성과
- ✓약 7개월 만에 iOS · Android 릴리스 빌드 납품, 클라이언트 시안을 그대로 구현하며 후속 확장(결제 · 병원 연동) 지점을 코드로 명시
- ✓난임 의료 데이터를 다루는 서비스에 맞는 감사 가능한 관리자 접근 구조 구축
- ✓대부분의 클래스 · 상수에 '왜 이 값인지'를 설명하는 한국어 dartdoc을 남겨 유지보수성 확보
Next Project
HAIM HAIM