Android Tutorial Working with Calendars using ContentResolver (Query, Insert, Update, Delete)

Hello,

i´m playing around with the Calendars on my Device and want to Query them. I found out how it works using ContentResolver querying the ContentProvider available in Android.

You can find it documented here.

I started using java code and a java-librarywrapper for this but then i realized that it is probably better to use the B4A ContentResolver instead of building a new one just for the Calendars. I then decided to do the same with the ContentResolver.
As the CalendarProvider is using different Tables internally there are a lot of Constants used (for each table a Content-Uti is needed, and Constants for the Fields in the Table). All of them can be created in B4A easily too but i decided to have them in a small Library (just a small wrapper just for the Constants).

- From now i will use "CR" for a short version of ContentResolver.

The constants make it easy to Build a Query for CR, get the right ContentUri.

Ok, let´s start with the basics to use Android CalendarProvider.

- It depends on the Permission "android.permission.READ_CALENDAR" and "android.permission.WRITE_CALENDAR". As both of them are dangerous permissions you need to use Runtimepermission to request Permission for them and they need to be defined in the manifest.

B4X:
AddManifestText(
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
)
.
B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim ccon As CalendarConstants
    Dim econ As EventConstants
End Sub
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim gcal As GoogleCalendar
    Private cr As ContentResolver
    Private canAccessCal As Boolean
End Sub
Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    canAccessCal = False
    Activity.LoadLayout("Layout1")
    Starter.rp.CheckAndRequest("android.permission.READ_CALENDAR")
    wait for Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result = False Then
        Log("NO Permission READ Calendar")
    Else
        Starter.rp.CheckAndRequest("android.permission.WRITE_CALENDAR")
        wait for Activity_PermissionResult (Permission As String, Result As Boolean)
        If Result Then
            canAccessCal = True
            cr.Initialize("CR")
        End If
    End If
    If canAccessCal Then
               ' We are working from here now...
        END IF
END sub

If you want to Query something your need to define a "Projection". In fact ou are defining the Fields you want to query.

We want to get a list of ID, Name, DisplayNAme, AccountType and the OwnerAccount from the Calendars.
B4X:
        Dim projection() As String = Array As String(ccon.ID,ccon.NAME,ccon.CALENDAR_DISPLAY_NAME,ccon.ACCOUNT_NAME,ccon.ACCOUNT_TYPE,ccon.OWNER_ACCOUNT)

The ID you get is the ID from Androids Calendar.
as we are requesting Calendars we need to use the ContentUri from the CalendarsConstants object when Querying with CR.

ccon, econ are two Objects which only contains the Constant. Each of them do have a CONTENT_URI and some other Values. ccon have Constants for Calendars. econ have Constants for CalendarEvents.

We set the "Selection" to match all visible Calendars.
We order them by ID Ascending.
B4X:
        cr.QueryAsync(ccon.CONTENT_URI,projection, ccon.VISIBLE&"=1",Null,ccon.ID&" ASC")
        wait for CR_QueryCompleted(Success As Boolean, Crsr As Cursor)
        Log($"QueryCompleted(${Success})"$)
        If Crsr.IsInitialized Then
            If Crsr.RowCount > 0 Then
                Dim Cursor As Cursor
                Cursor = Crsr
                For i = 0 To Cursor.RowCount - 1
                    Cursor.Position = i
                    'calcon.ACCOUNT_TYPE,calcon.OWNER_ACCOUNT)
                    Log($"CR --------------------------------------"$)
                    Log(Cursor.GetString(ccon.ID)) ' You can use the constants you used for the projection. But remember: only the ones you defined in the projection are available in the Result.
                    Log(Cursor.GetString(ccon.NAME))
                    Log(Cursor.GetString(ccon.CALENDAR_DISPLAY_NAME))
                    Log(Cursor.GetString(ccon.ACCOUNT_NAME))
                    Log(Cursor.GetString(ccon.ACCOUNT_TYPE))
                    Log(Cursor.GetString(ccon.OWNER_ACCOUNT))
                Next
                Cursor.Close
            Else
                ' No rows
            End If
        Else
            '
        End If

The constants used in the Example here can be found in the Attached small Library.

I´ll add more Examples to this Thread. As of now i´m ivestigating/play around with the Calendars. I do not know how to delete or edit a Event but i´ll find out and Update here.

I just want to share my findings and i think they may be useful for you too.

Related Examples:
- Reading Events from a Calendar
- More Advanced reading from a Calendar
- Adding an Event in a Calendar
- Some notes about Calendars AccountType.
- Deleting an Event.
- Updating an Event.
- Adding an Attendee.
- Get all Reminders for an specific Event.
- Add a Reminder to a Specific Event.
 

Attachments

  • GoogleCalendarClientV0.12.zip
    20.4 KB · Views: 858
  • GoogleCalendarClientV0.13.zip
    20.4 KB · Views: 880
  • GoogleCalendarClientV0.2.zip
    21.7 KB · Views: 833
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Reading Events from a Calendar.

Event constants can be found in the
B4X:
Dim econ As EventConstants

Here we are reading all visible Events which blongs to the Calendar ID 1 (we got the ID in the Step above).

B4X:
    Dim projection() As String = Array As String(econ.ID,econ.ALL_DAY,econ.AVAILABILITY,econ.CALENDAR_ACCESS_LEVEL,econ.CALENDAR_ID,econ.DESCRIPTION,econ.DTEND,econ.DTSTART,econ.DURATION,econ.LOCATION,econ.ORGANIZER,econ.TITLE)
    cr.QueryAsync(econ.CONTENT_URI,projection, econ.VISIBLE&"=1 AND "&econ.CALENDAR_ID=1",Null,econ.ID&" ASC")
    wait for CR_QueryCompleted(Success As Boolean, Crsr As Cursor)
    Log($"QueryCompleted(${Success})"$)
    If Crsr.IsInitialized Then
        If Crsr.RowCount > 0 Then
            Dim Cursor As Cursor
            Cursor = Crsr
            For i = 0 To Cursor.RowCount - 1
                Cursor.Position = i
                Log($"CR --------------------------------------"$)
               Log($"${Cursor.GetString(econ.ID)}: ${Cursor.GetString(econ.TITLE)} / ${Cursor.GetString(econ.LOCATION)} / ${Cursor.GetString(econ.DESCRIPTION)}"$)
            Next
            Cursor.Close
        Else
            ' No rows
        End If
    Else
        '
    End If
 

DonManfred

Expert
Licensed User
Longtime User
More advanced query for Events from a given Calendar

B4X:
    Dim projection() As String = Array As String(econ.ID,econ.ALL_DAY,econ.AVAILABILITY,econ.CALENDAR_ACCESS_LEVEL,econ.CALENDAR_ID,econ.DESCRIPTION,econ.DTEND,econ.DTSTART,econ.DURATION,econ.LOCATION,econ.ORGANIZER,econ.TITLE)
    Dim selection As String = $"${econ.VISIBLE}=? AND ${econ.CALENDAR_ID}=?"$
    selection = $"((${econ.CALENDAR_ID} = ?) AND (((${econ.DTSTART}>= ?) AND (${econ.DTSTART}<= ?) AND (${econ.ALL_DAY}= ?) ) OR ((${econ.DTSTART}= ?) AND (${econ.ALL_DAY}= ?))))"$

    Dim selectionArgs() As String = Array As String(1,FromTime,EndTime,0,FromTime,1)
    cr.QueryAsync(econ.CONTENT_URI,projection, selection,selectionArgs,econ.ID&" ASC")
    wait for CR_QueryCompleted(Success As Boolean, Crsr As Cursor)
    Log($"QueryCompleted(${Success})"$)
    If Crsr.IsInitialized Then
        If Crsr.RowCount > 0 Then
            Dim Cursor As Cursor
            Cursor = Crsr
            For i = 0 To Cursor.RowCount - 1
                Cursor.Position = i
                'calcon.ACCOUNT_TYPE,calcon.OWNER_ACCOUNT)
                Log($"CR --------------------------------------"$)
                ',econ.ALL_DAY,econ.AVAILABILITY,econ.CALENDAR_ACCESS_LEVEL,econ.CALENDAR_ID,econ.DESCRIPTION,econ.DTEND,econ.DTSTART,econ.DURATION,econ.LOCATION,econ.ORGANIZER
                Log($"${Cursor.GetString(econ.ID)}: $date{Cursor.GetString(econ.DTSTART)} $time{Cursor.GetString(econ.DTSTART)}  ${Cursor.GetString(econ.TITLE)} / ${Cursor.GetString(econ.LOCATION)} / ${Cursor.GetString(econ.DESCRIPTION)}"$)
            Next
            Cursor.Close
        Else
            ' No rows
        End If
    Else
        '
    End If
 
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Creating an Event in a specific Calendar:
Please note that the Calendar used here has an AccountType of "com.google".

B4X:
    Dim FromTime As Long = DateTime.DateTimeParse("12/19/2018","08:00:00")
    Dim EndTime As Long = DateTime.DateTimeParse("12/19/2018","09:30:00")

    Dim val As ContentValues
    val.Initialize
    val.PutLong(econ.DTSTART,FromTime)
    val.PutLong(econ.DTEND,EndTime)
    val.PutString(econ.TITLE,"SomeTitle")
    val.PutString(econ.LOCATION,"Düren")
    val.PutLong(econ.CALENDAR_ID,18)
    val.PutString(econ.EVENT_TIMEZONE,"Europe/Berlin")
    val.PutString(econ.DESCRIPTION,"Some description")
    Dim resUri As Uri = cr.Insert(econ.CONTENT_URI,val)
    Dim eventID As Int = resUri.ParseId
    Log($"EventID of created Event: ${eventID}"$)

Here a screenshot from the Google Calendar Website (the event is automatically synched with google servers. The Calendar must be subscribed in the Calendar app and enabled for Sync. And the Calendar must have a AccountType of com.google!
tv_080.png
 
Last edited:

BillMeyer

Well-Known Member
Licensed User
Longtime User
Keep going Manfred. I like, I like, I like....
 

DonManfred

Expert
Licensed User
Longtime User
Some notes about Calendars.

Each Calendar - we have access to - has an Property AccountType. The AccountType gives infos about where does the Calendar belongs to.

AccountType(s) i found so far on my Device:
- LOCAL
Changes in a Local Calendar only affects the Devices Calendar. Events in this Calendar are not Synced.

- COM.GOOGLE
Account is linked to Google. Changes in Calendars of this Type are Synced with Google. The Calendar i used above is a com.google Calendar.
Note that the Calendar must be defined and activated for Sync in the Google Calendar App.

- COM.SAMSUNG.ANDROID.EXCHANGE
Account is linked to Samsung Account (and Samsung Calendar). It is probably syned with Samsung (don´t know).
I can not say anything about this CalendarType. I do own a Samsung Account (and probably a Calendar) but i do not use it. I only use Google Calendars.
 
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Updating an Event.
Using the EventID you just can fill a ContentValues with all Values you want to change.

B4X:
        dim Value as int = 1334 ' EventID
    Dim selectionArgs() As String = Array As String(Value)
    Dim val As ContentValues
    val.Initialize
    val.PutString(econ.TITLE,"Some new Title")
    val.PutString(econ.LOCATION,"New Location")
    Dim updated As Int =cr.Update(econ.CONTENT_URI,val,econ.ID&"=?",selectionArgs)
    Log($"${updated} Events were updated"$)
 
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Adding an Attendee:

Adding a Attendee is using the Attendeesconstants.
B4X:
Dim acon As AttendeesConstants


B4X:
    Dim val As ContentValues
    Dim event As Int = 1234 ' EventID
    val.Initialize
    val.PutString(acon.EVENT_ID, event)
    val.PutInteger(acon.ATTENDEE_TYPE, acon.TYPE_REQUIRED)
    val.PutString(acon.ATTENDEE_NAME, "Zaphod Beeblebrox")
    val.PutString(acon.ATTENDEE_EMAIL, "[email protected]")
    Dim attendee As Uri =cr.Insert(acon.CONTENT_URI,val)
    Dim attendeeID As Int = attendee.ParseId
    Log($"attendeeID: ${attendeeID}"$)

Note that Google send out a Invitationemail automatically to the given Attendee in which he can confirm the Event.

Also note that this only applies to Calendars which AccountType is of com.google.
It may not happen in a Local or a Samsung Calendar.
 
Last edited:

DonManfred

Expert
Licensed User
Longtime User
Getting all Reminders for a specific Event.

B4X:
    Dim rcon As RemindersConstants ' Library from the Tutorial needed
    Dim event As Int = 12345 ' Eventid of which you want to get the Reminders
 
    Dim projection() As String = Array As String(rcon.ID, rcon.EVENT_ID ,rcon.METHOD, rcon.MINUTES)
    Dim selection As String = $"${rcon.EVENT_ID}=?"$
    Dim selectionArgs() As String = Array As String(event)
    cr.QueryAsync(rcon.CONTENT_URI,projection, selection,selectionArgs,rcon.ID&" ASC")
    wait for CR_QueryCompleted(Success As Boolean, Crsr As Cursor)
    Log($"QueryCompleted(${Success})"$)
    If Success = False Then
        Log(LastException)
    End If
    If Crsr.IsInitialized Then
        If Crsr.RowCount > 0 Then
            Dim crsrreminder As Cursor
            crsrreminder = Crsr
            For i = 0 To crsrreminder.RowCount - 1
                crsrreminder.Position = i
                Dim methodid As Int = crsrreminder.GetInt(rcon.METHOD)
                Dim method As String = "unknown"
                Select methodid
                    Case 1
                        method = "Notify"
                    Case 2
                        method = "eMail"
                End Select
                Log($"ID: ${crsrreminder.GetString(rcon.ID)} / Method: ${method} / Minutes: ${crsrreminder.GetString(rcon.MINUTES)}"$)
            Next
            crsrreminder.Close
        Else
            ' No rows
        End If
    Else
        '
    End If

*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
4689: 26.01.2019 11:30:00 test with reminder / /
lvEvents_ItemClick(25, 4689) ' Eventid 4689
QueryCompleted(true)
ID: 51 / Method: eMail / Minutes: 60
ID: 52 / Method: Notify / Minutes: 10
** Activity (main) Pause, UserClosed = false **
 

DonManfred

Expert
Licensed User
Longtime User
Posting to existing threads is a mistake. You should always create a new thread for any question you have.

For example, please see this example
This library is not relevant here.

I never tried to setup a Reminder. You need to check the Google Documentantion on how it works.
I guess you need to create a entry in the Reminderstable setting all relevant info for this Reminder.

You can check "Adding an Attendee" example above. Adapt it to set all relevant Reminder data.
You need to use
B4X:
Dim rcon As RemindersConstants
as basis to work with the Remindertable.
 

DonManfred

Expert
Licensed User
Longtime User
How can I creating an Event in a specific Calendar
see above. it is listed above how to insert an Calendaritem.

Once you have the EventID you can add a Reminder like this


Adding a Reminder using the RemindersConstants
B4X:
Dim rcon As RemindersConstants

B4X:
Sub addreminder(eventID As Int)
    Dim val As ContentValues
    val.Initialize
    val.PutString(rcon.EVENT_ID, eventID)
    val.PutInteger(rcon.METHOD, rcon.METHOD_EMAIL)
    val.PutString(rcon.MINUTES, "60")
    Dim reminderuri As Uri =cr.Insert(rcon.CONTENT_URI,val)
    If reminderuri<> Null And reminderuri.IsInitialized Then
        Dim reminderID As Int = reminderuri.ParseId
        Log($"reminderID: ${reminderID}"$)
    End If
End Sub
 
Last edited:

Colin Evans

Active Member
Licensed User
Longtime User
DonManfred
Hi, just wondered if it was possible to check against a calendar (ID) for a specific date, i.e. if you select a date can you specify a 'from to date' on just the selected date, and like wise I assume you'd be able to update, delete, enter on a specific date (and time). I've been having a look but can only get the selection in your example where its -x days to + x days
 
Top