Memory Card Android

Android – Wie bekomme ich Termine des Users in meine App?

Von am 25.09.2015

Prinzipiell gibt es im Internet eine offizielle Dokumentation (http://developer.android.com/guide/topics/providers/calendar-provider.html) an die auch wir (Theres-Sophie Scheucher und ich) uns gehalten haben. Doch leider sind wir auf Probleme und falsche oder veraltete Informationen die die Doku betreffen gestoßen, was bei der Android Developers Doku normalerweise bei anderen Themen in so großem Maße nicht auffällt. Da wir aber eine Art verbesserte Todos & Kalendar-App im Sinn hatten mussten wir uns dennoch durch die Doku beißen und präsentieren hier ein kleines Tutorial und ein paar Tipps.

Wichtig für uns im ersten Schritt war es die Daten richtig auszulesen und in der App anzuzeigen. Doch dafür ist sehr viel Logik notwendig, die eher dürftig dokumentiert ist. Auch gibt es selten bis gar nicht Tutorials im Internet dazu zu finden. Eine Art Library die einem solche Dinge überhaupt abnimmt findet sich leider überhaupt nicht.

Grundlegend gibt es verschiedene ContentProvider, man kann auch eigene schreiben. Neben dem CalendarProvider gibt es auch z.B. den ContactsProvider, mit dem man das Telefonbuch des Users auslesen kann. Um nun den CalendarProvider auszulesen braucht es einen sogenannten ContentResolver über den eine Query gemacht wird, nämlich mit der URI der internen Calendar Datenbank am Gerät (speziell hier die URI zu der Tabelle mit den verfügbaren Kalendern) und weiteren SQL Parametern.

Uri uri = Calendars.CONTENT_URI;
private static final String[] calendarsColumns = new String[] {
Calendars._ID,
Calendars.CALENDAR_DISPLAY_NAME,
Calendars.CALENDAR_COLOR,
Calendars.VISIBLE
};
Cursor cursorCalendars = getApplicationContext().getContentResolver.query(uri, calendarsColumns, null, null, null);

Als Rückgabeobjekt bekommt man einen Cursor, den man auch von anderen Programmiersprachen kennt. Mit diesem kann man die Daten Zeile für Zeile durchgehen und die jeweiligen Felder auslesen. Das String Array calendarsColumns enthält die gewünschten Spaltennamen, die weiteren Parameter sind bei diesem Beispiel null, hier könnte man bspsw. Sortierung angeben. Würde man den SQL String ausgeben können, würde er wahrscheinlich so aussehen: “SELECT _ID, CALENDAR_DISPLAY_NAME, CALENDAR_COLOR , VISIBLE FROM CALENDARS”

ArrayList<WTCalendar> arrayList = new ArrayList<>();
while (cursorCalendars.moveToNext()) {
WTCalendar calendar = new WTCalendar(
cursorCalendars.getLong(cursorCalendars.getColumnIndex(Calendars._ID)),
cursorCalendars.getString(cursorCalendars.getColumnIndex(Calendars.CALENDAR_DISPLAY_NAME)),
cursorCalendars.getLong(cursorCalendars.getColumnIndex(Calendars.CALENDAR_COLOR)),
cursorCalendars.getString(cursorCalendars.getColumnIndex(Calendars.VISIBLE)).equals(“1”) //boolean
);
arrayList.add(calendar);
}
cursorCalendars.close();

Als nächstes machen wir uns eine leere ArrayList aus eigenen Calendar Objekten die wir mit den bestimmten Daten aus der Tabelle befüllen wollen. In einer while Schleife die prüft ob es eine (weitere) Zeile (Row) im Ergebnis gibt, erstellen und befüllen wir ein Custom Object (WTCalendar) und fügen es danach der ArrayList hinzu.

Schauen wir uns diesen Codeausdruck aus dem oberen Codesnippet genauer an:

cursorCalendars.getString(cursorCalendars.getColumnIndex(Calendars.CALENDAR_DISPLAY_NAME))

Mit der Methode getString des Cursor Objekts bekommen wir einen String eines Feldes. Wichtig hier ist natürlich das dieses Feld auch einen String beinhaltet, ansonsten würde das zu einem Fehler führen. Dieser Methode getString muss ein Index der Spalte/des Feldes als int übergeben werden. Diesen Index bekommen wir über die Methode getColumnIndex des Cursor Objekts, der wir die Konstante Index Calendars.CALENDAR_DISPLAY_NAME übergeben, diese entält den Index der Spalte/des Feldes. Diese Variante schützt davor einen Fehler zu bekommen, falls Android auf einem anderen Gerät die Spalten-Anordnung in der Tabelle geändert hat.

Besonders wichtig ist noch die Zeile cursorCalendars.close(); , damit wird der Cursor auch wieder freigegeben und bleibt damit nicht im Speicher, was u.a. die Batterielaufzeit des Geräts beeinträchtigen würde.

Wollen wir nun Events (Termine) auslesen wäre das Verfahren dasselbe wie im gerade gezeigten Snippet. Nur die Definition des Cursors unterscheidet sich durch eine andere Uri, andere Spalten und mögliche Parameter.

Uri uri = Events.CONTENT_URI;
private static final String[] eventsColumns = new String[] {
Events._ID,
Events.TITLE,
Events.DESCRIPTION,
Events.EVENT_LOCATION,
Events.DTSTART,
Events.DTEND,
Events.ALL_DAY,
Events.RRULE,
Events.CALENDAR_ID
};
Cursor cursorEvents = WTApplication.contentResolver.query(
uri,
eventsColumns,
Events.CALENDAR_ID + ” = 1″,
null,
Events.DTSTART + ” ASC”);

Die ersten beiden Paramter der query Methode sind ja schon bekannt. Der dritte Parameter ist bei SQL als WHERE zu verstehen, also eine Einschränkung des Suchergebnisses. Hier sollen nur Termine zurückgegeben werden, die zum Kalendar mit der ID “1” gehören. Der nächste Parameter, der hier null gesetzt ist, kann dazu verwendet werden den Ausdruck ” = 1″ in einem Array an Parametern zu übergeben, auf dies wurde hier verzichtet, da es nur einen WHERE Parameter gibt. Der letzte Parameter in der query Methode lässt einen die Ergebnisse vorsortieren. Würde man also den SQL String wieder ausgeben können, würde er wahrscheinlich so aussehen: “SELECT _ID, TITLE, DESCRIPTION, EVENT_LOCATION, DTSTART, DTEND, ALL_DAY, RRULE, CALENDAR_ID FROM EVENTS WHERE _ID=1 ORDER BY DTSTART ASC”

Es gibt natürlich auch Methoden für INSERT und UPDATE/DELETE, doch soweit sind wir in unserer App noch gar nicht gekommen diese zu verwenden.

Die Doku zeigt dass es also eine Tabelle “Calendars” und eine Tabelle “Events” gibt. Nun führt sie aber auch eine Tabelle “Instances” an, dort heißt es:

“This table holds the start and end time for each occurrence of an event. Each row in this table represents a single event occurrence. For one-time events there is a 1:1 mapping of instances to events. For recurring events, multiple rows are automatically generated that correspond to multiple occurrences of that event.”

Nachdem ich mir die Events und Instances mal geloggt habe um zu sehen was überhaupt für Daten in diesen Tabellen vorhanden sind, habe ich entdeckt dass diese Aussage ziemlich gewagt ist. Zu den sich nicht wiederholenden Events gibt es keinerlei 1:1 Instances und zu den sich wiederholenden Events scheint es sehr buggy zu sein. So gibt es teilweise Instances und teilweise einfach nicht, obwohl die Regel (für die Wiederholung des Termins) im Event gespeichert ist. Man kann sich also leider nicht darauf verlassen sich nur die Instances auszulesen, sondern muss eigentlich wie folgt vorgehen:

– Auslesen der Events
– Überprüfen ob das Event sich wiederholt
– Überprüfen ob es eine oder mehrere Instances zum Event gibt
– Wenn nicht -> Regeln für sich wiederholende Events selbst interpretieren und Instanzen anlegen
– Eigene(s) Event Objekt(e) selbstständig befüllen und mit eigenenen Daten erweitern, falls nicht in Tabelle vorhanden
– Darauf achten, dass bei der Erstellung keine ungewünschten Duplikate entstehen

Man sieht also an diesem Punkt ist plötzlich viele manuelle Logik nötig, was das Programmieren einer solchen App ziemlich komplex macht. Vielleicht ist das der Grund warum es nur wenige Kalendar Apps und noch weniger Tutorials oder Dokumentationen gibt.

The comments are closed.