Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil 12 Kontakte und Organizer
Pfeil 12.1 Kontakte
Pfeil 12.1.1 Emulator konfigurieren
Pfeil 12.1.2 Eine einfache Kontaktliste ausgeben
Pfeil 12.1.3 Weitere Kontaktdaten ausgeben
Pfeil 12.1.4 Geburtstage hinzufügen und aktualisieren
Pfeil 12.2 Kalender und Termine
Pfeil 12.2.1 Termine anlegen und auslesen
Pfeil 12.2.2 Alarme und Timer
Pfeil 12.2.3 Die Klasse »CalendarContract«
Pfeil 12.3 Zusammenfassung
 
Zum Seitenanfang

12.2    Kalender und Termine Zur vorigen ÜberschriftZur nächsten Überschrift

Vor Ice Cream Sandwich bot Android erstaunlich wenige öffentliche und dokumentierte Schnittstellen für Apps, um Termine auszulesen und zu bearbeiten. Glücklicherweise hat die Plattform seitdem deutlich dazugelernt.

 
Zum Seitenanfang

12.2.1    Termine anlegen und auslesen Zur vorigen ÜberschriftZur nächsten Überschrift

Das Projekt KalenderDemo1 demonstriert, wie Sie durch das Versenden eines Intents mit der Standardkalender-App einen Termin anlegen können. Deren Eingabeseite für Termine ist in Abbildung 12.6 zu sehen.

Eingabeseite für Termine

Abbildung 12.6    Eingabeseite für Termine

In der Klasse KalenderDemo1Activity (Listing 12.5) werden zwei java.util.Date-Objekte mit dem Beginn (aktuelles Datum und Uhrzeit) und dem Ende (eine Stunde später) des anzulegenden Termins initialisiert. Hierbei hilft ein Objekt des Typs android.icu.util.Calendar. Danach ruft die App die private Methode createEntry() auf. Diese erzeugt ein Intent mit der Action Intent.ACTION_INSERT und setzt mit putExtra() die Terminattribute Beginn, Ende, Titel und Beschreibung. allDay kennzeichnet einen Termin als ganztägig. In meinem Beispiel ist der Wert vom Ende des Termins abhängig: Liegt es vor 12 Uhr, ist der Termin ganztägig. Die Stunde wird mit cal.get(Calendar.HOUR_OF_DAY) ermittelt.

package com.thomaskuenneth.androidbuch.kalenderdemo1

import android.content.ActivityNotFoundException
import android.content.Intent
import android.icu.util.Calendar
import android.os.Bundle
import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*

private val TAG = KalenderDemo1Activity::class.simpleName
class KalenderDemo1Activity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
// Beginn und Ende eines Termins
val cal = Calendar.getInstance()
val from = cal.time
cal.add(Calendar.HOUR_OF_DAY, 1)
val to = cal.time
// Termin anlegen
createEntry(getString(R.string.title),
getString(R.string.hello), from, to,
cal.get(Calendar.HOUR_OF_DAY) < 12)
}
}

private fun createEntry(title: String, description: String,
from: Date, to: Date, allDay: Boolean) {
val intent = Intent(Intent.ACTION_INSERT,
Events.CONTENT_URI)
intent.putExtra(Events.TITLE, title)
intent.putExtra(Events.DESCRIPTION, description)
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
from.time)
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, to.time)
intent.putExtra(Events.ALL_DAY, allDay)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "keine passende Activity", e)
}
}
}

Listing 12.5    Die Klasse »KalenderDemo1Activity«

[+]  Tipp

Sie sollten den Aufruf von startActivity() wie in meinem Beispiel immer in einem try-catch-Block kapseln und die ActivityNotFoundException fangen. Es ist nämlich nicht garantiert, dass eine Kalender-App installiert ist, die das Intent verarbeiten kann.

Würden Sie Ihre App gern um eine Alarmfunktion erweitern? Auch das ist möglich. Wie Sie hierzu vorgehen, zeige ich Ihnen im folgenden Abschnitt.

 
Zum Seitenanfang

12.2.2    Alarme und Timer Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klasse android.provider.AlarmClock beinhaltet unter anderem die Konstanten ACTION_SET_TIMER, ACTION_SET_ALARM, EXTRA_MESSAGE, EXTRA_HOUR und EXTRA_MINUTES, mit denen Sie eine Activity zum Stellen eines Alarms oder Timers starten können. Wie das funktioniert, zeige ich Ihnen anhand des Beispiels AlarmClockDemo1. Die App ist in Abbildung 12.7 zu sehen.

Die App »AlarmClockDemo1«

Abbildung 12.7    Die App »AlarmClockDemo1«

In der Klasse AlarmClockDemo1Activity (Listing 12.6) kann ein Alarm für 20 Uhr gesetzt werden. Wird er aktiviert, erscheint der Hinweis »Ein Alarm«. Alternativ ist das Starten eines 90-Sekunden-Timers mit der Nachricht »Ein Timer« möglich. Welche der beiden Aktionen ausgeführt wird, steuern Sie über zwei Radiobuttons. Ich habe sie in einer RadioGroup zusammengefasst. Mithilfe dieser Klasse kann sehr bequem der aktuell ausgewählte Knopf abgefragt (group.checkedRadioButtonId) und gesetzt (check()) werden. Aber das ist nicht die Hauptfunktion einer RadioGroup. Sie erweisen sich als besonders nützlich, wenn Sie mehrere unabhängige RadioButton-Gruppen in einer Activity benötigen. Stellen Sie sich ein Programm zum Umrechnen von Einheiten vor, in dem Sie zwischen Millimeter, Zentimeter und Meter sowie zwischen Radius, Umfang und Flächeninhalt wählen können. In jedem der beiden Bereiche ist stets ein Element aktiv.

package com.thomaskuenneth.androidbuch.alarmclockdemo1

import android.content.Intent
import android.os.Bundle
import android.provider.AlarmClock
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class AlarmClockDemo1Activity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
group.check(R.id.alarm)
go.setOnClickListener {
when (group.checkedRadioButtonId) {
R.id.alarm -> fireAlarm()
R.id.timer -> fireTimer()
}
}
}

private fun fireAlarm() {
val alarm = Intent(AlarmClock.ACTION_SET_ALARM)
alarm.putExtra(AlarmClock.EXTRA_MESSAGE, "Ein Alarm")
alarm.putExtra(AlarmClock.EXTRA_HOUR, 20)
alarm.putExtra(AlarmClock.EXTRA_MINUTES, 0)
alarm.putExtra(AlarmClock.EXTRA_SKIP_UI, false)
startActivity(alarm)
}

private fun fireTimer() {
val timer = Intent(AlarmClock.ACTION_SET_TIMER)
timer.putExtra(AlarmClock.EXTRA_MESSAGE, "Ein Timer")
timer.putExtra(AlarmClock.EXTRA_LENGTH, 90)
timer.putExtra(AlarmClock.EXTRA_SKIP_UI, false)
startActivity(timer)
}
}

Listing 12.6    Die Klasse »AlarmClockDemo1Activity«

Die eigentlichen Funktionen, nämlich das Setzen eines Alarms bzw. das Starten eines Timers, stecken in den beiden privaten Methoden fireAlarm() und fireTimer(). Ihr Aufbau ist sehr ähnlich. Es wird ein Intent zusammengesetzt und mittels startActivity() gefeuert. Um das Programm möglichst kurz zu halten, habe ich den Aufruf diesmal nicht in einen try-catch-Block gekapselt. In Ihren produktiven Apps sollten Sie die Ausnahme ActivityNotFoundException fangen und den Anwender informieren.

Die meisten übergebenen Werte sind sprechend. EXTRA_SKIP_UI legt fest, ob Apps, die das Intent auswerten, eine Benutzeroberfläche (zum Beispiel Activities oder Dialoge) darstellen dürfen (false) oder nicht (true). Damit das Setzen von Timern und Alarmen funktioniert, müssen Sie die normale Berechtigung com.android.alarm.permission.SET_ALARM anfordern.

Welche App sich um Alarme und Timer kümmert, ist Android-typisch nicht festgelegt. Es kann sich um die standardmäßig mitgelieferte Uhr-App handeln oder um eine selbst geschriebene. Lassen Sie mich das näher erläutern.

Auf das Setzen von Alarmen und Timern reagieren

Mein Beispiel AlarmClockDemo2 zeigt Ihnen, wie Sie in Ihren Apps auf das Setzen von Alarmen und Timern reagieren können. Lassen Sie uns zunächst einen Blick auf die Manifestdatei werfen. Sie definiert eine Activity, die drei Intent-Filter enthält. Der erste (mit der Aktion MAIN und der Kategorie LAUNCHER) sorgt dafür, dass die Activity mit dem Android-Programmstarter ausgeführt werden kann. Die folgenden beiden Intent-Filter haben die Kategorie DEFAULT und die Aktionen SET_TIMER bzw. SET_ALARM. Sie sorgen dafür, dass sich die App für das Setzen von Alarmen und Timern »zuständig fühlt«.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.thomaskuenneth.androidbuch.alarmclockdemo2">
<application
android:allowBackup="false"
...
android:theme=
"@style/AppTheme">
<activity android:name=".AlarmClockDemo2Activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SET_ALARM" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SET_TIMER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

Listing 12.7    Manifestdatei des Projekts »AlarmClockDemo2«

Wenn Sie nach der Installation von AlarmClockDemo2 die App AlarmClockDemo1 aufrufen und einen Alarm oder Timer starten, erscheint die in Abbildung 12.8 dargestellte Rückfrage, welche App für die Aktion verwendet werden soll.

Tippen Sie auf AlarmClockDemo2 und danach auf Just once oder Always. Die App wird daraufhin von Android gestartet (Abbildung 12.9). Activities können abfragen, welches Intent ihnen beim Aufruf übergeben wurde. Wie das funktioniert, ist in Listing 12.8 zu sehen. Die Klasse AlarmClockDemo2Activity lädt in onCreate() die Benutzeroberfläche und zeigt sie an. onStart() gibt die Daten des übergebenen Intents aus.

Nach einer Prüfung auf null ermittle ich mit extras ein Objekt des Typs android.os.Bundle. Ein solches Bündel ist ein Schlüssel-Wert-Speicher. Alle Schlüssel erhalten Sie mit der Methode keySet(). Der Zugriff auf einzelne Werte erfolgt mit get...()-Aufrufen. Wissen Sie beispielsweise, dass sich hinter einem Schlüssel eine Fließkommazahl verbirgt, erhalten Sie diese direkt mit getFloat(). Meine Implementierung ermittelt in einer Schleife mit bundle[it] den Wert jedes Schlüssels und gibt ihn aus. Wenn Sie das Programm ausführen lassen, können Sie sehr schön die Werte sehen, die Sie schon aus AlarmClockDemo1 kennen.

Rückfrage, welche App sich um den Timer oder Alarm kümmern soll

Abbildung 12.8    Rückfrage, welche App sich um den Timer oder Alarm kümmern soll

Die App »AlarmClockDemo2«

Abbildung 12.9    Die App »AlarmClockDemo2«

[+]  Tipp

Sie können mit action die Aktion des Intents abfragen. Vor einer Auswertung der übergebenen Parameter sollten Sie die gelieferte mit der erwarteten Aktion vergleichen. Da ich nur alle übergebenen Daten ausgebe, ist eine solche Prüfung natürlich nicht nötig.

package com.thomaskuenneth.androidbuch.alarmclockdemo2

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class AlarmClockDemo2Activity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onStart() {
super.onStart()
textview.text = getString(R.string.no_intent)
intent?.run {
textview.text = String.format("%s\n", action)
extras?.let { bundle ->
bundle.keySet()?.let { keys ->
keys.forEach {
textview.append("$it\n")
textview.append("${bundle[it]}\n\n")
}
}
}
}
}
}

Listing 12.8    Die Klasse »AlarmClockDemo2«

Vielleicht fragen Sie sich, warum Sie einen eigenen Dialog zur Eingabe von Alarmen anzeigen sollten. Dies bietet sich in erster Linie für Apps an, die den eingebauten Wecker vollständig ersetzen sollen. In diesem Fall müssen Sie Weckzeiten und Nachrichten in einer eigenen Datenbank verwalten. Ausführliche Informationen zu SQLite-Datenbanken finden Sie in Kapitel 10, »Datenbanken«. Auch das Setzen der Alarme selbst liegt dann in Ihrer Verantwortung. Die Klasse android.app.AlarmManager stellt die Methode set() zur Verfügung, die zu einem bestimmten Zeitpunkt ein PendingIntent feuert. Mit ihm können Sie weitere Aktionen auslösen, zum Beispiel einen Text ausgeben oder einen Sound abspielen. Hierbei handelt es sich allerdings um fortgeschrittene Themen, die an dieser Stelle nicht weiter behandelt werden können.

 
Zum Seitenanfang

12.2.3    Die Klasse »CalendarContract« Zur vorigen ÜberschriftZur nächsten Überschrift

Wie ich Ihnen in den vorangegangenen Kapiteln zeige, spielen Content Provider eine äußerst wichtige Rolle in Android. Insofern liegt es nahe, dass die Plattform auch für Termine und Ereignisse auf dieses mächtige Instrument baut. Tatsächlich können Sie über einen Content Resolver Kalenderdaten ausgeben. Wie, zeige ich Ihnen anhand des Beispiel-Projekts KalenderDemo2.

In der privaten Methode logEvents() der Klasse KalenderDemo2Activity (Listing 12.9) iteriert eine Schleife mit moveToNext() über einen von query() gelieferten Cursor. In meinem Beispiel ist die Abfrage sehr einfach: Sie wählt alle vorhandenen Spalten und Zeilen aus, es gibt keine Einschränkung des Suchraumes. Deshalb sind mit Ausnahme des Uniform Resource Identifiers (Events.CONTENT_URI) alle übergebenen Werte null. Die Spalten _ID und TITLE der gefundenen Tabellenzeilen werden in einem Textfeld ausgegeben. Das Auslesen der Werte erfolgt mittels getString(). Der Methode wird ein Spaltenindex übergeben. Da dieser abhängig von dem Parameter projection des query()-Aufrufes ist, ist es am sichersten, den Index mittels getColumnIndex() zu erfragen.

[+]  Tipp

query()-Aufrufe können null liefern. Um nicht zur Laufzeit in eine NullPointerException zu laufen, sollten Sie den Zugriff auf den Cursor (zum Beispiel die moveToNext()-Schleife oder getColumnIndex()-Aufrufe) wie in meinem Beispiel absichern. Denken Sie auch daran, den Cursor nach Gebrauch mit close() zu schließen.

package com.thomaskuenneth.androidbuch.kalenderdemo2

import android.Manifest.permission
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Bundle
import android.provider.CalendarContract.Events
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

private const val REQUEST_READ_CALENDAR = 123
class KalenderDemo2Activity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onStart() {
super.onStart()
if (checkSelfPermission(permission.READ_CALENDAR)
!= PERMISSION_GRANTED) {
requestPermissions(arrayOf(permission.READ_CALENDAR),
REQUEST_READ_CALENDAR)
} else {
logEvents()
}
}

override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
if (requestCode == REQUEST_READ_CALENDAR &&
grantResults.isNotEmpty() &&
grantResults[0] == PERMISSION_GRANTED) {
logEvents()
} else {
textview.text = getString(R.string.no_permission)
}
}

private fun logEvents() {
contentResolver.query(Events.CONTENT_URI, null, null,
null, null)?.run {
val indexId = getColumnIndex(Events._ID)
val indexTitle = getColumnIndex(Events.TITLE)
while (moveToNext()) {
textview.append("_ID: ${getString(indexId)}\n")
textview.append("TITLE: ${getString(indexTitle)}\n\n")
}
close()
}
}
}

Listing 12.9    Die Klasse »KalenderDemo2Activity«

Um auf den Kalender zugreifen zu können, müssen Sie in der Manifestdatei sowie zur Laufzeit die gefährliche Berechtigung android.permission.READ_CALENDAR anfordern. Nach bewährtem Muster habe ich hierzu die beiden Methoden onStart() und onRequestPermissionsResult() überschrieben. Für schreibende Zugriffe ist android.permission.WRITE_CALENDAR erforderlich.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern