Wotter is a hydration tracking app for iOS and Android. Users log water intake, set reminders, and track their daily progress. Here's how I built it.
Tech Stack
| Category | Technology |
|---|---|
| Framework | React Native |
| Language | TypeScript |
| State | Redux Toolkit |
| Navigation | React Navigation |
| Animations | Reanimated + Skia |
| Storage | MMKV |
| Notifications | Local push notifications |
Multi-Step Onboarding
First-time users go through an onboarding flow to calculate their recommended daily intake:
- Weight input
- Activity level
- Climate/environment
- Reminder preferences
- Goal confirmation
Each step uses Reanimated for smooth page transitions. State persists across steps using Redux.
// Calculate recommended intake const calculateGoal = (weight: number, activity: ActivityLevel) => { const base = weight * 0.033; // 33ml per kg const multiplier = activityMultipliers[activity]; return Math.round(base * multiplier * 1000); // Return in ml };
Dashboard with Metrics
The main screen shows:
- Current intake vs goal (animated progress ring)
- Quick-add drink buttons
- Today's drink log
- Weekly streak
The progress ring uses Shopify's React Native Skia for smooth 60fps animations:
<Canvas style={styles.canvas}> <Path path={progressPath} style="stroke" strokeWidth={12} color={progressColor} /> </Canvas>
Local Push Notifications
Reminder system built with local notifications:
- User sets reminder intervals (every 2 hours, etc.)
- Notifications scheduled on app open
- Tapping notification opens drink logging
- Rescheduled after each log
Key challenge: iOS limits scheduled notifications to 64. Solution: only schedule next 24 hours, refresh on app open.
MMKV for Storage
React Native's AsyncStorage is slow. MMKV is synchronous and fast:
import { MMKV } from "react-native-mmkv"; const storage = new MMKV(); // Sync read/write storage.set("dailyGoal", 2500); const goal = storage.getNumber("dailyGoal");
Used for:
- User preferences
- Daily logs
- Streak data
- Onboarding completion flag
Animations with Rive and Lottie
Mix of animation libraries:
- Rive - Interactive animations (drink splash effects)
- Lottie - Achievement celebrations
- Reanimated - UI transitions and gestures
Rive files are smaller and support state machines, perfect for interactive feedback.
Subscription Management
In-app subscriptions handled via RevenueCat:
- Product display from RevenueCat
- Purchase flow
- Entitlement checking
- Restore purchases
const { isPro } = useSubscription(); // Gate premium features { isPro ? <UnlimitedReminders /> : <BasicReminders />; }
Testing Strategy
- Jest - Unit tests for calculations and utilities
- Component tests - Critical UI flows
- Manual testing - Both platforms before release
Water intake calculations are heavily tested - wrong recommendations would break user trust.
Key Learnings
- MMKV over AsyncStorage - The performance difference is noticeable
- Skia for custom graphics - Native-level performance in RN
- Schedule notifications conservatively - Platform limits are real
- RevenueCat simplifies subscriptions - Don't build your own IAP system
Wotter is live on the App Store.
