TL;DR: CareLog is a cross-platform Flutter app that helps home-visit doctors and nurses track patients, schedule visits, manage payments, and view practice analytics — all offline-first with optional cloud sync. Built with Flutter + Riverpod + Drift (SQLite) + Supabase, deployable as an Android APK, a PWA on iOS, or a web app.
The Problem: Home-Visit Doctors Have No Good Tool
Home-visit healthcare is common in India. A doctor or physiotherapist travels to patients' homes on a daily or weekly schedule, collects fees in cash, and keeps everything in a notebook or a generic spreadsheet.
That system breaks down fast:
- Who did I visit today and who did I miss?
- Which patients haven't paid yet this month?
- Am I earning more or less than last month?
- What was the address for that patient on the outskirts?
Existing apps either target hospital workflows (too complex), require internet access, or don't handle the visit-frequency + payment-tracking combination at all.
CareLog fills that gap: a lean, offline-first app purpose-built for the home-visit workflow.
What CareLog Does
Schedule view is the home screen. It shows a horizontally scrollable date strip spanning one year back and one month forward. Each day shows dot indicators for dates that have scheduled visits. Tapping a date shows a timeline of patients due that day, with one-tap actions to mark a visit complete or skip it.
Patient profiles store name, phone, address, default consultation fee, visit frequency (Daily, Alternate, Weekly, or Custom weekdays), visit start date, and notes. The frequency and weekday data drives the automatic schedule generation — no manual entry needed per day.
Patient details screen shows the full visit history sorted newest-first, running totals for paid and pending fees, and a bulk-selection mode (long-press to enter) for marking multiple visits paid/unpaid or deleting them in one action.
Dashboard provides a snapshot of today's visits and earnings, period-aware earnings cards (Week / Month / Year / All Time) with trend indicators versus the prior period, a 6-month earnings comparison strip, a day-of-week activity bar chart for the current month, pending payment breakdown per patient, and a most-visited patients leaderboard.
Settings cover daily reminder notifications (with a custom time picker), cloud backup via Google Sign-In, local backup/restore to a file, import from the previous version of the app, and CSV export of all visit data.
Tech Stack and Why
Flutter
The app targets Android natively and iOS/web as a PWA. Flutter makes that possible from a single codebase. Dart's strong typing and hot reload speed made iteration fast. The Material 3 widget library covered 95% of UI needs without custom widgets.
The project uses v1.1.0 (SDK ^3.5.3), deployed with flutter build apk --release for Android and flutter build web --release for the PWA.
Riverpod for State Management
Riverpod was chosen over Provider and BLoC for several reasons:
- Compile-time safety — providers are regular Dart objects, not context lookups, so typos are caught at build time.
-
Async first —
AsyncNotifierProviderand.when()handle loading/error/data states without boilerplate. - Testability — providers can be overridden in tests without a widget tree.
The app's provider graph: databaseProvider (Drift DB singleton) → patientsProvider / visitsProvider (stream-based) → derived providers like todayVisitsProvider, dashboardProvider, scheduledDatesProvider.
Drift (SQLite) for Offline-First Storage
Drift is a type-safe SQLite wrapper for Flutter. The schema is two tables:
class Patients extends Table {
// id, name, phone, address, defaultFee
// visitFrequency: 'Daily' | 'Alternate' | 'Weekly' | 'Custom'
// visitWeekdays: "1,3,5" (comma-separated ISO weekday ints)
// visitStartDate, isActive, remoteId, isSynced
}
class Visits extends Table {
// id, patientId (FK), visitDate, visitType: 'Home' | 'Clinic'
// fee, isPaid, notes, remoteId, isSynced
}
The schema is at version 5 with a hand-written migration strategy. Each onUpgrade block is guarded with if (from < N) so migrations compose correctly regardless of which version a user upgrades from.
One non-obvious issue: drift_dev code-gen doesn't handle nullable DateTimeColumn additions via addColumn because of Dart generic invariance. The workaround was dropping down to raw SQL:
await customStatement('ALTER TABLE patients ADD COLUMN visit_started_at INTEGER');
Drift stores DateTime as an INTEGER (Unix ms), so the raw SQL type matches.
Supabase for Cloud Sync
Each row carries a remoteId (UUID from Supabase) and an isSynced boolean. The sync engine:
- Fetches rows where
isSynced == falsefrom the local DB. - Upserts them to Supabase using the
remoteIdas the conflict key. - Pulls down any rows created/updated on other devices since the last sync timestamp.
- Merges local and remote, preferring the most recent
updatedAt.
Google Sign-In is the only auth method — no email/password form to maintain, and most users already have a Google account on their Android phone.
PWA Strategy for iOS
Building for iOS normally requires a Mac and an Apple Developer account ($99/year). The workaround: flutter build web produces a standard web app. Hosted on Vercel, it works as a Progressive Web App — Safari on iPhone prompts the user to "Add to Home Screen," after which it launches fullscreen and behaves like a native app.
A custom PwaInstallGuide widget detects when the app is running in a browser (not as an installed PWA) and displays a step-by-step overlay showing the Safari share icon and "Add to Home Screen" instructions. This eliminated the biggest friction point for iOS users.
Architecture Decisions Worth Calling Out
Schedule Generation Is Computed, Not Stored
Visits are only stored when a doctor takes an action (marks complete, skips, logs a note). The upcoming schedule is computed on the fly from each patient's visitFrequency, visitWeekdays, and visitStartDate. The todayVisitsProvider joins the computed schedule for the selected date with any stored visit records to produce a VisitSchedule object with upcomingCount, completedCount, and skippedCount.
This keeps the database small and avoids the problem of "stale future visits" if a patient's schedule changes.
Platform-Conditional Services
Notifications and file downloads have different implementations on mobile vs web. Each service has three files:
notification_service.dart ← abstract interface
notification_service_mobile.dart ← flutter_local_notifications
notification_service_web.dart ← no-op / Web Notifications API
A factory function in notification_service.dart returns the right implementation based on kIsWeb. This pattern kept platform-specific code contained and made testing each implementation in isolation straightforward.
CI/CD: GitHub Actions to Vercel
The .github/workflows/main.yml pipeline:
- Runs
flutter teston every push. - If tests pass, runs
flutter build web --release. - Deploys the
build/weboutput to Vercel using the Vercel CLI with three repository secrets:VERCEL_TOKEN,VERCEL_ORG_ID,VERCEL_PROJECT_ID.
The result: every green push to main auto-deploys a new web build in under three minutes. Android APKs are still built and distributed manually (direct APK share via WhatsApp/Telegram for early users, with Play Store planned for v2).
UX Details That Mattered
Haptic feedback on actions. HapticFeedback.mediumImpact() fires on "Mark Complete" and "Skip". On a busy home-visit day, a doctor often taps through the list quickly. The haptic confirms the action registered without requiring the user to look at the screen.
"All caught up" empty state. When all visits for the day are done, the Upcoming filter shows a check-circle icon and "You have completed all visits for today. Great job!" — a small thing, but it closes the feedback loop rather than just showing a blank list.
Backup nudge banner. If the user hasn't signed in for cloud sync, a dismissible orange banner appears on the home screen: "Your data is only on this device." It links directly to the Settings screen. This surfaces a real risk (data loss if the phone is lost) without being intrusive.
Month markers in the date strip. The horizontal date picker shows the month abbreviation on the 1st of every month instead of the day name. This solves the orientation problem when scrolling back through history — a doctor checking records from 3 months ago can find the right month at a glance.
What's Next
- Recurring visit notes templates — many patients have the same condition; reusable note templates would save typing.
- Patient photo — a profile photo makes it faster to match the patient card to the face when the visit list is long.
- Multiple currencies — the ₹ symbol is hardcoded. Parameterizing it would open the app to users outside India.
- Export to PDF — a printable monthly earnings report for accounting purposes.
Top comments (0)