I'm trying to update calendar events programmatically but I have some issues.
I'm using updating code from Google Android Documentation: https://developer.android.com/guide/topics/providers/calendar-provider.html#update-event
So here's my code :
ContentValues values = new ContentValues();
Uri updateUri = null;
// New end for event
values.put(CalendarContract.Events.DTEND, endMillis);
updateUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID);
int rows = c.getContentResolver().update(updateUri, values, null, null);
Log.i("DEBUG_TAG", "Rows updated: " + rows);
But rows variable always returns 0 except when I go into apps settings, remove calendar storage, add a new calendar and a new event and then when I try to update it, it works only once. If I try to add another event and update it afterward, rows return 0 again.
Any idea?
Thanks
Please note that my app is min API level 17 (on which it doesn't work) but I tried on API level 25 and it works, so do you have any idea how to add support to my code for previous Android versions? I've also found that after some tests, it works from API level 21. Under, it doesn't.
EDIT :
I've found a solution, check my answer below.
Here's the solution that worked for me :
The eventID that I used was the result of the query with column CalendarContract.Events._ID but I had to used the ID provided by CalendarContract.Instances.EVENT_ID.
Indeed if I create a new event (the first one) it has _id = 1 and event_id = 1, and if I delete it from Android Calendar App and I create a new one with my app, it has also _id = 1 but event_id = 2.
So the update query failed if I use _id instead of event_id with withAppendedID().
Here's the code that worked for me. startMillis and endMillis are respectively your meeting start time and end time in milliseconds.
Uri.Builder eventsUriBuilder = CalendarContract.Instances.CONTENT_URI
.buildUpon();
ContentUris.appendId(eventsUriBuilder, startMillis);
ContentUris.appendId(eventsUriBuilder, endMillis);
Uri eventsUri = eventsUriBuilder.build();
String[] column = {CalendarContract.Instances.EVENT_ID, CalendarContract.Events.DTSTART, CalendarContract.Events.DTEND, CalendarContract.Events.TITLE};
Cursor cursor;
cursor = c.getContentResolver().query(eventsUri, column, CalendarContract.Events.CALENDAR_ID+"="+Constant.ID_CALENDAR, null, CalendarContract.Instances.DTSTART + " ASC");
if (cursor.moveToFirst()) {
do {
uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI,cursor.getLong(0));
} while (cursor.moveToNext());
}
cursor.close();
c.getContentResolver().delete(uri, null, null);
Related
I'm struggling with the implementation of dates in SQLite3 (for android). According to the documentation (https://www.sqlite.org/datatype3.html and http://www.sqlite.org/lang_datefunc.html), SQLite doesn't have datatypes for date and time specifically and therefore it can be either stored as a TEXT or as an INTEGER. I've tried both, but they give the same erroneous results. After reading a lot on the internet and trying everything I can think of, I come here as a last resort.
So, now for the problem. The idea is actually very simple. I have a table containing items, with a date column. Now I want to select all items from this database that have a date of today, or before (i.e. today or in the past). In my current implementation I store dates as integers, since most people seem to agree that that is the way to go.
Below is the (simplified) code for inserting an item.
Calendar calendar = GregorianCalendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0); // Might not be necessary
calendar.add(Calendar.DAY_OF_YEAR, 4); // Today plus 4 days
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_DATE, calendar.getTimeInMillis()); // Today as integer/long
long id = db.insert(TABLE_ITEMS, null, values); // Add to db
And next I want to select the items where the date is equal to or lower than today:
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
String[] columns = new String[]{COLUMN_ID, COLUMN_TEXT, COLUMN_DATE};
String[] selectArg = new String[]{"date('now')"};
Cursor cItems = db.query(TABLE_ITEMS, columns, COLUMN_DATE + "<= ?", selectArg, null, null, null);
//Cursor cItems = db.query(TABLE_ITEMS, columns, "strftime('%Y-%m-%d'," + COLUMN_DATE + ")" + select, selectArg, null, null, null);
//Cursor cItems = db.query(TABLE_ITEMS, columns, "date(" + COLUMN_DATE + ")" + select, selectArg, null, null, null);
Now all rows are selected, also the rows with a date in the future. Could anyone tell me what I need to do differently?
Thanks in advance!
Edit
I found out that sqlite stores date integers in seconds (is that correct?). So that would mean that value I put in the database should be Math.round(calendar.getTimeInMillis()/1000, to get it in seconds right? But then it makes even less sence, since a date in milliseconds should always be larger than a date in seconds. Anyhow, I tried that, but it doesn't work either.
Thanks to the insights CL. gave me, I fixed some trivial errors I made.
Looking back at the SQLite documentation, I figured that there are two ways of storing a date without a time. A Julian day number or a date string YYYY-MM-DD. There is no readily available function to get a Julian day number in java/android, so therefore I chose to go back to the string format again.
A second, mistake I figured out just now is that the selectArgs are considered text and therefore, putting the date('now', 'localtime') in there won't work.
Below are the final scripts that do work.
Calendar calendar = GregorianCalendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0); // Might not be necessary
calendar.add(Calendar.DAY_OF_YEAR, 4); // Today plus 4 days
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
ContentValues values = new ContentValues();
values.put(COLUMN_DATE, dateFormat.format(calendar.getTime())); // Date as string
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
long id = db.insert(TABLE_ITEMS, null, values); // Add to db
And to get the data back:
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
String[] columns = new String[]{COLUMN_ID, COLUMN_TEXT, COLUMN_DATE};
String[] selectArg = new String[]{};
Cursor cItems = db.query(TABLE_ITEMS, columns, COLUMN_DATE + "<= date('now', 'localtime')", selectArg, null, null, null);
CL., many thanks for your help! In the end it was so simple...
Neither date() nor strftime('%Y-%m-%d') returns a date in your format.
To convert a date into the seconds format, you would have to use strftime('%s', ...), and convert the resulting string into a number.
This is how I create calendar events from within my app:
for(CalendarEventDescriptor calendarEventDescriptor : calendarEventDescriptors.values()) {
if(calendarEventDescriptor.startMilliseconds>now){
values = new ContentValues();
values.put(CalendarContract.Events.DTSTART, calendarEventDescriptor.startMilliseconds);
values.put(CalendarContract.Events.DTEND, calendarEventDescriptor.endMilliseconds);
values.put(CalendarContract.Events.TITLE, calendarEventDescriptor.title);
values.put(CalendarContract.Events.DESCRIPTION, calendarEventDescriptor.description);
values.put(CalendarContract.Events.CALENDAR_ID, 1);
values.put(CalendarContract.Events.EVENT_TIMEZONE, timeZone);
uri = cr.insert(CalendarContract.Events.CONTENT_URI, values);
calendarEventDescriptor.eventId = Long.parseLong(uri.getLastPathSegment());
}
}
At the time or writing, I store an array of all the event Ids that I've created, so that when the user flicks a switch, I loop through them and delete them from the Calendar.
for(long eventId : eventIds) {
if(eventId>0){
Uri deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId);
rowsDeleted += application.getContentResolver().delete(deleteUri, null, null);
}
}
It occurred to me that it might be possible to put a custom value for one of the CalendarContract.Events. columns so that I can do the deletion for all of the events at once, and that I don't have to remember their ids (I always delete them all, never delete certain ones)
Is that possible and which CalendarContract.Events. column should I use and how do I do the deletion then?
Nice question! I agree, that extra-property in ContentValues is the way to go in this case.
I've done it by re-using CalendarContract.Events.CUSTOM_APP_PACKAGE, as something invisible for the user and, so far, no side effects found:
So I'm creating Events, like you do:
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(CalendarContract.Events.CALENDAR_ID, 1);
values.put(CalendarContract.Events.DTSTART, ...);
values.put(CalendarContract.Events.DTEND, ...);
values.put(CalendarContract.Events.TITLE, ...);
values.put(CalendarContract.Events.DESCRIPTION, ....);
values.put(CalendarContract.Events.CUSTOM_APP_PACKAGE, getApplicationContext().getPackageName());
values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());
cr.insert(CalendarContract.Events.CONTENT_URI, values);
And then once I need to delete all of them, I call:
Cursor cursor = cr
.query(CalendarContract.Events.CONTENT_URI,
new String[] { CalendarContract.Events._ID, CalendarContract.Events.CUSTOM_APP_PACKAGE },
null, null, null);
cursor.moveToFirst();
String idsToDelete = "";
for (int i = 0; i < cursor.getCount(); i++) {
// it might be also smart to check CALENDAR_ID here
if (getApplicationContext().getPackageName().equals(cursor.getString(1))) {
idsToDelete += String.format("_ID = %s OR ", cursor.getString(0));
}
cursor.moveToNext();
}
if (idsToDelete.endsWith(" OR ")) {
idsToDelete = idsToDelete.substring(0, idsToDelete.length()-4);
}
cr.delete(CalendarContract.Events.CONTENT_URI, idsToDelete, null);
I hope, it helps
You can use ContentProviderOperation with applyBatch to perform multiple deletes/inserts in a single transaction.
Check this SO answer for code sample.
after a long long research, I've got a sort of new doc of google calendar
you can add multiple args to a selection allowing you to find all desired rows and delete them all at once
val operationList = ArrayList<ContentProviderOperation>()
var contentProviderOperation: ContentProviderOperation
appointments.forEach {
contentProviderOperation = ContentProviderOperation
.newDelete(CalendarContract.Events.CONTENT_URI)
.withSelection(CalendarContract.Events.ORIGINAL_SYNC_ID + " =?", arrayOf(it.id))
.build()
operationList.add(contentProviderOperation)
}
contentResolver.applyBatch(CalendarContract.AUTHORITY, operationList)
I made a mistake on testing insert Events using CalendarContract.
I set my own _ID in a Events insert.
values.put(Events._ID, "156498713465");
Now, all my new events are created with a bad id (for exemple -535191590).
When I click to the event in the Google Calendar Application, it crash.
I have the same error as this thread :
Calendar corrupted in Android
I tried to delete all bad events :
activity.getContentResolver().delete(Events.CONTENT_URI, Events._ID + " > ? ",
new String[] { "10000" });
But when a new events are inserted, a bad id is generated.
My question is :
Where can I reset the Events Id sequence ?
Thanks,
Regards
Don't set the id; the following will do what you want:
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put (Events.CALENDAR_ID, Long.toString(newCalendarId));
values.put (Events.DTSTART, dtStart);
values.put (Events.DTEND, dtEnd);
values.put (Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());
values.put (Events.TITLE, title);
Uri uri = cr.insert (Events.CONTENT_URI, values);
// The returned uri will contain the eventId assigned by Events.
When I update the CalendarContract.Events DTEND column, why doesn't the change show up in the CalendarContract.Instances END column?
My app allows the user to view and change calendar events using the CalendarContract.Events APIs. The code performs an update to the Events table and then reads it back (later) using the Instances table. Changes to TITLE, for example, work fine (that is, I update Events and can read back the change in Instances). Changes to Events.DTEND do show up in Instances.DTEND, but how can I get that update to also show up in Instances.END?
This is important since, evidently, the Android calendar app (and my app, too) uses Instances.BEGIN and Instances.END to determine what to display in the calendar.
Here is my update code:
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put (Events.CALENDAR_ID, calendarId);
values.put (Events.TITLE, title);
values.put (Events.DTEND, eventEnd.getTimeInMillis());
String where = "_id =" + eventId +
" and " + CALENDAR_ID + "=" + calendarId;
int count = cr.update (Events.CONTENT_URI, values, where, null);
if (count != 1)
throw new IllegalStateException ("more than one row updated");
Thanks.
The solution turns out to be adding the start date:
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put (Events.CALENDAR_ID, calendarId);
values.put (Events.TITLE, title);
values.put (Events.DTSTART, eventStart.getTimeInMillis());
values.put (Events.DTEND, eventEnd.getTimeInMillis());
String where = "_id =" + eventId +
" and " + CALENDAR_ID + "=" + calendarId;
int count = cr.update (Events.CONTENT_URI, values, where, null);
if (count != 1)
throw new IllegalStateException ("more than one row updated");
A word of caution: this case only shows how to update nonrecurring events. Nonrecurring events have a null RRULE.
I suspect what the provider code is doing is using only the values you provide instead of refetching the start date itself (obviously if the user changes the start date you would have to provide it anyway). This makes sense from the perspective of reducing db accesses. Too bad Google didn't document any of this.
I'm trying to implement my first android Program. It should write calendar entries (I know, not the best task to begin programming Andorid).
I've tried:
Uri CALENDAR_URI = Uri.parse("content://calendar/events");
ContentResolver cr = getContentResolver();
cr.delete(CALENDAR_URI, null, null); // Delete all
cr.delete(CALENDAR_URI, "calendar_id=1", null); // Delete all in default calendar
cr.delete(CALENDAR_URI, "_id=1", null); // Delete specific entry
Nothing worked. I allays get a "cannot delete that URL".
Inserting an Calendar Entry was simple:
ContentValues values = new ContentValues();
values.put("calendar_id", 1);
values.put("title", this.title);
values.put("allDay", this.allDay);
values.put("dtstart", this.dtstart.toMillis(false));
values.put("dtend", this.dtend.toMillis(false));
values.put("description", this.description);
values.put("eventLocation", this.eventLocation);
values.put("visibility", this.visibility);
values.put("hasAlarm", this.hasAlarm);
cr.insert(CALENDAR_URI, values);
According to my insert method accessing the calendar worked.
Thanks, Arthur!
OK, one thing I didn't try:
Uri CALENDAR_URI = Uri.parse("content://calendar/events");
int id = 1; // calendar entry ID
Uri uri = ContentUris.withAppendedId(CALENDAR_URI, id);
cr.delete(uri, null, null);
This is what I was missing:
Uri uri = ContentUris.withAppendedId(CALENDAR_URI, id);
should lead to content://calendar/events/1
Now my Calendar is empty :-)
The right way to delete things out of a user's calendar is to use the appropriate GData APIs and delete it from their Google Calendar. Manipulating the Calendar application's content provider -- as you are trying to do -- is not part of the public API.