Sync events across multiple local device calendars on Android.
When an event appears in your source calendar, CalSync automatically creates a corresponding event in your target calendar using a custom name you provide — perfect for marking "Busy", "Out of Office", or any custom status across calendars.
- Cross-calendar sync — Mirror events from one local calendar to another
- Custom event names — Synced events use your chosen name (not the original title)
- Duplicate prevention — Local mapping table tracks
(source_event_id, source_calendar_id) → (target_event_id, target_calendar_id) - Reactive + periodic sync — Instant reaction to calendar changes via Android observer, with configurable fallback interval
- Background execution — Uses
WorkManagerfor reliable background sync - Permission handling — Runtime requests for
READ_CALENDAR,WRITE_CALENDAR,POST_NOTIFICATIONS
| Layer | Technology |
|---|---|
| Framework | Flutter (Dart) |
| Calendar Access | device_calendar |
| Permissions | permission_handler |
| Persistence | shared_preferences + sqflite |
| Background Work | workmanager |
| Build | Gradle (CLI) |
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Source Calendar│────▶│ Sync Engine │────▶│ Target Calendar │
└─────────────────┘ └────────┬─────────┘ └─────────────────┘
│
┌────────────▼────────────┐
│ Mapping Database │
│ (source_id, cal_id) → │
│ (target_id, cal_id) │
└─────────────────────────┘
│
┌────────────▼────────────┐
│ Background Workers │
│ • Reactive (observer) │
│ • Periodic (fallback) │
└─────────────────────────┘
Key components:
SyncEngine— Orchestrates reading source events, checking mapping DB, creating/updating/deleting target eventsMappingDatabase— SQLite table persisting sync relationships across app restartsCalendarService— Wrapper arounddevice_calendarpluginSettingsService— Persists user config (calendar IDs, sync name, interval)PermissionGate— Blocks UI until calendar permissions granted
- Flutter SDK ≥ 3.12.1
- Android SDK (API 33+ recommended)
- Physical device or emulator with Google Play Services
# Install dependencies
flutter pub get
# Run on connected device/emulator
flutter run
# Run tests
flutter test
# Static analysis
flutter analyzeOn first launch, CalSync requests calendar permissions. Then configure:
- Source Calendar — Where events originate
- Target Calendar — Where synced events are created
- Sync Event Name — Custom title for synced events (e.g., "Busy", "Focus Time")
- Fallback Interval — Periodic sync backup (default: 1 hour; set to 0 for manual only)
Changes are detected reactively within seconds. The interval is a fallback only.
CalSync does not rely on event ID equality across calendars (IDs are per-provider). Instead:
- On each sync, read all events from source calendar since last sync
- For each source event, check mapping DB:
(source_event_id, source_calendar_id) - If mapping exists → update target event
- If mapping missing → create target event with custom name, store mapping
- If target event exists but source deleted → delete target, remove mapping
- Reactive: Android calendar content observer →
WorkManagerone-off task (5s delay) - Periodic: Configurable
WorkManagerperiodic task (fallback)
# Debug APK
flutter build apk --debug
# Release APK
flutter build apk --release
# App Bundle (Play Store)
flutter build appbundle --releaseOutput: build/app/outputs/flutter-apk/ or build/app/outputs/bundle/
GPL-3.0 © Henrique Couto