BACK
4 min read

ShiftWise: switching frameworks mid-build, and why

A real framework migration on a live project — Flutter/Dart to React Native/Expo — and the tradeoffs that came with it.

The switch

ShiftWise started in Flutter/Dart. Partway through, it moved to React Native with Expo instead. [PLACEHOLDER — the real reason for the switch belongs here: what specifically wasn't working in Flutter for this project, what tipped the decision. I don't have that detail and won't invent one.]

What got locked in after the switch

Several architecture decisions were made deliberately rather than defaulted into: `@react-native-firebase` for offline data persistence, `lucide-react-native` for icons, `@gorhom/bottom-sheet` and `react-native-reanimated` for the interaction layer, and a glassmorphism, dark-first visual design (`#0A0A0B` base, translucent white surfaces at 6% opacity, a `#3B6EF8` primary accent).

The project also runs on Firebase's free Spark plan rather than the paid Blaze tier. That's a real, documented tradeoff — Spark has a known limitation around atomic write guards, meaning certain race conditions aren't fully protected against at the database level yet. That gap is deferred, not ignored: it's tracked as a known issue to resolve on a Blaze upgrade, not ignored.

Getting Firestore security rules right

The security rules follow a deny-by-default model: nothing is readable or writable unless explicitly allowed, access is matched against the authenticated user's UID, and entitlement data is server-owned rather than client-writable. That last point matters specifically for a shift-tracking app — if a client could write its own entitlement status, nothing would stop someone from granting themselves premium features by editing a local request.