leehyunhong
All projects
momiplan

Women's Health · Fertility

momiplan 마미플랜

Cycle tracking, IVF records and heart-rate workout coaching for women preparing for pregnancy

Client
Healthcare startup (B2B2C · undisclosed)
Period
Jan 2026 — Jul 2026 · About 7 months
Role
Sole developer — Flutter app, Firebase backend, admin functions
Team
1 engineer (me) + client product & design
Platforms
iOS · Android · Web (Admin)
Status
v1.0.0 (build 16) delivered, iOS & Android release builds

110 files

Dart

15K lines

Code

34 steps

Onboarding

1.0.0+16

Release build

Overview

Project overview

momiplan is a companion service for women undergoing fertility treatment or preparing for pregnancy. A 34-step onboarding survey — treatment stage, clinic, time trying, supplements, sleep issues — builds an understanding of each user, and menstrual cycles and IVF procedures are recorded in detail.

It combines BLE heart-rate-band workout coaching, a pregnancy-success dashboard and a lounge with clinics, columns and counselling bookings in one app — designed with an audit structure that shows who viewed what, as a service handling sensitive medical data should.

My Role

Responsibilities

  • 01Designed and built the entire Flutter app (110 Dart files, ~15K lines)
  • 02Google and Apple sign-in, four consent agreements, 34-step onboarding survey flow
  • 03CustomPainter visualisations: custom calendar, cycle analysis, radar chart, heart gauge
  • 04flutter_reactive_ble heart-rate-band scan / connect / reconnect and the workout-session state machine
  • 05Firestore data model and security rules, Cloud Functions (Seoul region) admin API with audit logging
  • 06Sequential iOS/Android build automation with automatic build-number bumping

Features

Key features

Cycle tracking

Start/end dates, cycle-length calculation, a custom calendar with month grid, day cells, tooltips and markers, and a cycle-length chart.

IVF procedure records

Dedicated screens per stage: ovarian stimulation (follicle-size slider, abdominal-discomfort score, symptoms), egg retrieval, fresh vs frozen embryo transfer.

Symptom logging

Intuitive SVG-icon logging of four bleeding levels, four discharge types and more.

Pregnancy-success dashboard

Heart gauge, percentile and a five-axis radar chart implemented directly with CustomPainter.

BLE heart-rate workout coaching

Scan → connect → countdown → monitoring → finish. Real-time 'good / adjust' feedback against the prescription (40-minute target, HR 100–140).

Lounge, counselling and plans

Articles, clinic / column / commerce cards, coordinator and psychological counselling bookings, a four-plan comparison screen (payments in a later phase).

Architecture

Design principles

Privacy-first data model

All user data lives only under users/{uid}, readable and writable by the owner alone via Firestore rules. Admin lists and audit logs are fully blocked from clients.

Admin API through audit logs

The admin dashboard never reads Firestore directly; it goes through callable Functions that write an access_logs entry for every query.

Time-series heart-rate storage

Per-second BPM is auto-saved every minute as one-minute chunk documents with zero-padded IDs. Seconds without signal are kept as null; the server stitches chunks into a CSV download.

Abnormal-exit recovery

On relaunch, sessions still marked inProgress are set to abnormal to keep data consistent.

Challenges

Engineering challenges

Two BLE permission models, one state

Problem

Android 12+ requires explicit BLUETOOTH_SCAN / CONNECT requests, while iOS has no permission API at all — the system prompts only once scanning starts.

Solution

Android requests explicitly with neverForLocation in the manifest to avoid location permission; iOS starts scanning immediately and listens for BleStatus.unauthorized. Both flows normalise into one Provider state.

Crashes when a screen closes mid-await

Problem

If a screen was disposed while awaiting scan or connect, notify was called on an already-disposed Provider and crashed.

Solution

A _disposed flag with a _safeNotify() pattern guards the lifecycle.

Reconnecting to a sensor that isn't advertising yet

Problem

A just-disconnected band has not resumed advertising, so a rescan cannot find it.

Solution

Try a direct connect with the last device id first, then fall back to scanning.

Apple only sends the name once

Problem

Sign in with Apple returns name and email only on the first authentication, leaving later profiles empty.

Solution

Persist at first authentication, combine surname + given name in Korean order, and hash the nonce with SHA-256.

A calendar that survives large font scaling

Problem

Accessibility font scaling broke the seven-column day selector and card alignment.

Solution

Cell size is capped by formula and card title line counts are fixed; the reasoning is kept in code comments.

Parallel iOS/Android builds collide

Problem

Building both platforms at once triggered 'Unable to delete .packages' from the flutter startup lock and ios/Flutter/ephemeral contention.

Solution

A sequential build script that warns when Xcode is running, bumps the build number only on success, copies the APK and opens the workspace automatically.

Tech Stack

Technology

App

  • Flutter · Dart 3.10
  • Material 3 · custom colour tokens
  • 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 (admin dashboard)

Quality

  • flutter_lints + custom analysis_options
  • Domain-logic unit tests (cycle logging · HR zones · BLE UUID)
  • iOS · Android build automation

Outcomes

Outcomes

  • ✓iOS and Android release builds delivered in about seven months, implementing the client's designs faithfully with follow-up extension points (payments, clinic integration) marked in code
  • ✓Auditable admin-access structure appropriate for a service handling fertility medical data
  • ✓Korean dartdoc on most classes and constants explaining 'why this value', keeping the codebase maintainable