You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_Patient_App_New/package/device_calendar_plus
tahaalam 71ac91a076 calender package added 12 hours ago
..
example calender package added 12 hours ago
lib calender package added 12 hours ago
package calender package added 12 hours ago
test calender package added 12 hours ago
CHANGELOG.md calender package added 12 hours ago
LICENSE calender package added 12 hours ago
README.md calender package added 12 hours ago
analysis_options.yaml calender package added 12 hours ago
pubspec.lock calender package added 12 hours ago
pubspec.yaml calender package added 12 hours ago

README.md

device_calendar_plus

A modern, maintained Flutter plugin for reading and writing device calendar events on Android and iOS. Modern replacement for the unmaintained device_calendar plugin — rebuilt for 2025 Flutter standards, working towards feature parity with a cleaner API, and no timezone package dependency.

pub package pub points platforms MIT license

Overview

device_calendar_plus lets Flutter apps read and write native calendar data using:

  • Android Calendar Provider
  • iOS EventKit

It provides a clean Dart API, proper time-zone handling, and an actively maintained federated structure.

Created by Bullet — a personal task + notes + calendar app using this plugin in production.

Supported versions

Platform Min OS / SDK Target / Compile
Android minSdk 24+ target/compile 35
iOS iOS 13+ Latest Xcode / iOS SDK

🚀 Features

  • Permissions: Request and check calendar permissions
  • Calendars: Create, read, update, and delete calendars
  • Events: Create, read, update, and delete events
  • Query: Retrieve events by date range or specific event IDs
  • Native UI: Open native event modal for viewing/editing in both android and iOS
  • All-Day Events: Proper handling of floating calendar dates
  • Timezones: Correct timezone behavior for timed events
  • Recurring Events: Read recurring event instances; update/delete entire series

🧩 Installation

Add the dependency to your project:

dependencies:
  device_calendar_plus: <latest version>

iOS

Add usage descriptions to your apps Info.plist:

<!-- iOS 1016 (legacy key, still valid) -->
<key>NSCalendarsUsageDescription</key>
<string>Access your calendar to view and manage events.</string>

<!-- iOS 17+ (choose as appropriate) -->
<key>NSCalendarsFullAccessUsageDescription</key>
<string>Full access to view and edit your calendar events.</string>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>Add events without reading existing events.</string>

Android

Add calendar permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />

ProGuard / R8: ProGuard rules are automatically applied by the plugin. No manual configuration needed.

DateTime and Timezone Behavior

All DateTimes returned by this plugin are in local time.

All-Day Events (Floating Dates)

All-day events are treated as floating calendar dates, not specific instants in time. This means:

  • An all-day event for "January 15, 2024" will always display as January 15, regardless of what timezone your device is in
  • The date components (year, month, day) are preserved across timezone changes
  • Do NOT convert all-day event DateTimes to UTC — they represent calendar dates, not moments in time
  • Example: A birthday on "January 15" should always show as January 15, whether you're in New York or Tokyo

Non-All-Day Events (Instants in Time)

Regular timed events represent specific moments in time and can be converted to UTC as needed:

  • These events have specific start/end times in a timezone (e.g., "3:00 PM New York time")
  • They represent absolute instants that correspond to different local times across timezones
  • You can freely convert these DateTimes to UTC for storage, comparison, or API calls
  • Example: A meeting at "3:00 PM EST" is the same instant as "12:00 PM PST"

Summary

// All-day event - treat as a calendar date, NOT a UTC instant
final birthdayEvent = await plugin.getEvent(birthdayId);
if (birthdayEvent.isAllDay) {
  // ✅ Use the date components directly
  print('Birthday: ${birthdayEvent.startDate.year}-${birthdayEvent.startDate.month}-${birthdayEvent.startDate.day}');
  
  // ❌ Don't convert to UTC - it's a calendar date, not a moment in time
  // final utcDate = birthdayEvent.startDate.toUtc(); // DON'T DO THIS
}

// Regular timed event - this IS an instant in time
final meetingEvent = await plugin.getEvent(meetingId);
if (!meetingEvent.isAllDay) {
  // ✅ Convert to UTC for storage/comparison
  final utcTime = meetingEvent.startDate.toUtc();
  
  // ✅ Format in local time for display
  print('Meeting at: ${meetingEvent.startDate}');
}

🧱 Exception model

Each DeviceCalendarException uses an enum code to describe the error type:

enum DeviceCalendarError {
  permissionDenied,
  ...
}

This enum provides stable, descriptive error codes for all exceptions thrown by the plugin.

Note on error codes: DeviceCalendarError exists for developer ergonomics and clearer switch handling. We may introduce new enum values in future minor versions as new error cases appear. We do not consider this a breaking change.

🛠️ Usage Examples

Request Permissions

import 'package:device_calendar_plus/device_calendar_plus.dart';

// Get the singleton instance
final plugin = DeviceCalendar.instance;

// Request calendar permissions
final status = await plugin.requestPermissions();
if (status != CalendarPermissionStatus.granted) {
  // Handle permission denied
  return;
}

Check Permissions

Use hasPermissions() to check the current permission status without prompting the user:

final plugin = DeviceCalendar.instance;

// Check current permission status (doesn't prompt)
final status = await plugin.hasPermissions();

if (status == CalendarPermissionStatus.granted) {
  // Permissions already granted
  final calendars = await plugin.listCalendars();
} else if (status == CalendarPermissionStatus.notDetermined) {
  // User hasn't been asked yet - now we can prompt
  final newStatus = await plugin.requestPermissions();
} else {
  // Denied or restricted - show appropriate UI
  print('Permissions: $status');
}

List Calendars

final plugin = DeviceCalendar.instance;

// List all calendars
final calendars = await plugin.listCalendars();
for (final calendar in calendars) {
  print('${calendar.name} (${calendar.readOnly ? "read-only" : "writable"})');
  if (calendar.isPrimary) {
    print('  ⭐ Primary calendar');
  }
  if (calendar.colorHex != null) {
    print('  Color: ${calendar.colorHex}');
  }
}

// Find a writable calendar
final writableCalendar = calendars.firstWhere(
  (cal) => !cal.readOnly,
  orElse: () => calendars.first,
);

Retrieve Events

final plugin = DeviceCalendar.instance;

// Get events for the next 30 days
final now = DateTime.now();
final startDate = now;
final endDate = now.add(const Duration(days: 30));

// Get events from all calendars
final allEvents = await plugin.listEvents(
  startDate,
  endDate,
);
print('Found ${allEvents.length} events');

// Get events from specific calendars only
final calendarIds = ['calendar-id-1', 'calendar-id-2'];
final filteredEvents = await plugin.listEvents(
  startDate,
  endDate,
  calendarIds: calendarIds,
);

Get Single Event

final plugin = DeviceCalendar.instance;

// Get a specific event by instanceId
final event = await plugin.getEvent(event.instanceId);
if (event != null) {
  print('Event: ${event.title}');
}

// For recurring events, get a specific occurrence
final instance = await plugin.getEvent(event.instanceId);

// For recurring events, get the master event definition
final masterEvent = await plugin.getEvent(event.eventId);

Show Event in Modal

final plugin = DeviceCalendar.instance;

// Show a specific event in a modal dialog
await plugin.showEventModal(event.instanceId);

// For recurring events, show a specific occurrence
await plugin.showEventModal(event.instanceId);

// For recurring events, show the master event
await plugin.showEventModal(event.eventId);

Create Event

final plugin = DeviceCalendar.instance;

// Create a basic event
final eventId = await plugin.createEvent(
  calendarId: 'your-calendar-id',
  title: 'Team Meeting',
  startDate: DateTime(2024, 3, 20, 14, 0),
  endDate: DateTime(2024, 3, 20, 15, 0),
);

// Create an all-day event
final allDayEventId = await plugin.createEvent(
  calendarId: 'your-calendar-id',
  title: 'Conference',
  startDate: DateTime(2024, 3, 20),
  endDate: DateTime(2024, 3, 21),
  isAllDay: true,
);

// Create event with all optional parameters
final detailedEventId = await plugin.createEvent(
  calendarId: 'your-calendar-id',
  title: 'Project Kickoff',
  startDate: DateTime(2024, 3, 20, 10, 0),
  endDate: DateTime(2024, 3, 20, 12, 0),
  description: 'Quarterly project kickoff meeting',
  location: 'Conference Room A',
  timeZone: 'America/New_York',
  availability: EventAvailability.busy,
);

Update Event

final plugin = DeviceCalendar.instance;

// Update event title
await plugin.updateEvent(
  instanceId: event.instanceId,
  title: 'Updated Meeting Title',
);

// Update multiple fields
await plugin.updateEvent(
  instanceId: event.instanceId,
  title: 'Team Sync',
  startDate: DateTime(2024, 3, 21, 15, 0),
  endDate: DateTime(2024, 3, 21, 16, 0),
  location: 'Conference Room B',
  description: 'Updated description',
);

// Change a timed event to all-day
await plugin.updateEvent(
  instanceId: event.instanceId,
  isAllDay: true,
);

// Change an all-day event to timed
await plugin.updateEvent(
  instanceId: event.instanceId,
  isAllDay: false,
  startDate: DateTime(2024, 3, 21, 10, 0),
  endDate: DateTime(2024, 3, 21, 11, 0),
);

// Update timezone (reinterprets local time)
// Note: "3 PM EST" becomes "3 PM PST" (different instant in time)
await plugin.updateEvent(
  instanceId: event.instanceId,
  timeZone: 'America/Los_Angeles',
);

Note on Recurring Events: For recurring events, updateEvent will always update the ENTIRE series (all past and future occurrences). Single-instance updates are not supported to maintain consistent behavior across platforms.

Delete Event

final plugin = DeviceCalendar.instance;

// Delete a single event
await plugin.deleteEvent(event.instanceId);

// For recurring events, this deletes the ENTIRE series (all occurrences)
await plugin.deleteEvent(event.instanceId);

🤝 Contributing

Contributions, PRs and issue reports welcome. Open an issue first for larger features or breaking changes.

  • Code style: dart format .
  • Run tests: flutter test
  • Federated layout: platform code lives in /packages/device_calendar_plus_android and /packages/device_calendar_plus_ios; shared contracts in /packages/device_calendar_plus_platform_interface.

🧪 Testing Status

This plugin includes both unit tests and integration tests to ensure reliability.

📄 License

MIT © 2025 Bullet See LICENSE for details.


Maintained by Bullet — a cross-platform task + notes + calendar app built with Flutter.