Integrating Calendar Provider (System Calendar) into Android Applications
The Android system calendar is accessible through CalendarProvider—a standard Content Provider available since API 14. Most applications use one of two scenarios: reading existing events or creating new ones. Both require runtime permissions and proper URI handling.
Permissions
For reading, use READ_CALENDAR; for writing, use WRITE_CALENDAR. Both are dangerous permissions, requested via ActivityCompat.requestPermissions() or ActivityResultContracts.RequestPermission(). Without explicit runtime requests on Android 6+, you get SecurityException.
Reading Events
Events are stored in the CalendarContract.Events table. Query via ContentResolver:
val projection = arrayOf(
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.DTEND,
CalendarContract.Events.CALENDAR_ID
)
val selection = "${CalendarContract.Events.DTSTART} >= ? AND ${CalendarContract.Events.DTEND} <= ?"
val selectionArgs = arrayOf(
startMillis.toString(),
endMillis.toString()
)
val cursor = context.contentResolver.query(
CalendarContract.Events.CONTENT_URI,
projection,
selection,
selectionArgs,
"${CalendarContract.Events.DTSTART} ASC"
)
cursor?.use {
while (it.moveToNext()) {
val title = it.getString(it.getColumnIndexOrThrow(CalendarContract.Events.TITLE))
val dtStart = it.getLong(it.getColumnIndexOrThrow(CalendarContract.Events.DTSTART))
// process
}
}
Important: use getColumnIndexOrThrow() instead of getColumnIndex()—if the column is missing from the projection, it fails immediately with a clear exception rather than ArrayIndexOutOfBoundsException somewhere in business logic.
Creating an Event
val values = ContentValues().apply {
put(CalendarContract.Events.CALENDAR_ID, calendarId)
put(CalendarContract.Events.TITLE, "Team Meeting")
put(CalendarContract.Events.DTSTART, startMillis)
put(CalendarContract.Events.DTEND, endMillis)
put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id)
put(CalendarContract.Events.DESCRIPTION, "Release v2.1 discussion")
}
val uri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, values)
val eventId = uri?.lastPathSegment?.toLong()
EVENT_TIMEZONE is a mandatory field. Without it, the event is created in UTC, and users see incorrect times after a timezone change. This is a classic error that reaches production and manifests in users from other regions.
Adding a Reminder
val reminderValues = ContentValues().apply {
put(CalendarContract.Reminders.EVENT_ID, eventId)
put(CalendarContract.Reminders.MINUTES, 15)
put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
}
context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues)
Opening System UI
If your app doesn't need direct data access but only wants to open the standard event-creation interface, use Intent without permissions:
val intent = Intent(Intent.ACTION_INSERT).apply {
data = CalendarContract.Events.CONTENT_URI
putExtra(CalendarContract.Events.TITLE, "Event Title")
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
}
startActivity(intent)
This is simpler, safer, and doesn't require permissions. It suits most cases where the app doesn't maintain its own event list.
Integrating CalendarProvider takes 1–3 days depending on feature scope: reading events, creating them, synchronizing multiple accounts. Cost is calculated individually.







