Berechtigungen und Foto-Auswahl in Compose – rememberLauncherForActivityResult
Kamera-Erlaubnis abfragen, ein Bild aus der Galerie holen, ein Dokument öffnen: Dafür gibt es in Compose genau einen Weg, und er ist erfreulich kurz. onActivityResult und startActivityForResult brauchst du nie wieder.
Fast jede App braucht das irgendwann: „Darf ich auf die Kamera?", „Such dir ein Bild aus", „Öffne diese PDF". Früher war das ein Ritual aus startActivityForResult, magischen Request-Codes und einem onActivityResult, in dem irgendwann alles zusammenlief. In Compose ist es ein Einzeiler pro Anliegen.
Eine Berechtigung anfragen
@Composable
fun KameraButton(onErlaubt: () -> Unit) {
val ctx = LocalContext.current
var abgelehnt by remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { erlaubt ->
if (erlaubt) onErlaubt() else abgelehnt = true
}
Column {
Button(onClick = {
val schon = ContextCompat.checkSelfPermission(ctx, Manifest.permission.CAMERA)
if (schon == PackageManager.PERMISSION_GRANTED) onErlaubt()
else launcher.launch(Manifest.permission.CAMERA)
}) { Text("Foto aufnehmen") }
if (abgelehnt) {
Text("Ohne Kamera-Erlaubnis geht das leider nicht.")
}
}
}Zwei Dinge sind hier wichtig:
- Erst prüfen, dann fragen.
checkSelfPermissionkostet nichts und erspart dem Nutzer einen Dialog, den er schon einmal beantwortet hat. - Der Launcher lebt an der Composition.
rememberLauncherForActivityResultregistriert sich beim Aufbau und meldet sich beim Verschwinden wieder ab. Genau deshalb darfst du ihn nicht in einemif-Zweig oder einer Schleife erzeugen — er muss bei jeder Recomposition an derselben Stelle stehen.
Ein Bild auswählen – ohne jede Berechtigung
Das ist der Teil, den viele übersehen: Für den Photo Picker brauchst du gar keine Speicher-Berechtigung. Das System zeigt die Auswahl, deine App bekommt nur das eine Bild.
var bild by remember { mutableStateOf<Uri?>(null) }
val picker = rememberLauncherForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri -> bild = uri } // uri ist null, wenn abgebrochen wurde
Button(onClick = {
picker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}) { Text("Bild wählen") }
bild?.let { AsyncImage(model = it, contentDescription = null) }Weniger Code und weniger Berechtigungen — die einzige Kombination, bei der man nichts abwägen muss. Wer noch READ_EXTERNAL_STORAGE anfragt, nur um ein Profilbild auszuwählen, sollte hier umsteigen.
Mehrere Berechtigungen auf einmal
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { ergebnis: Map<String, Boolean> ->
val alleDa = ergebnis.values.all { it }
// …
}
launcher.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))Was ich dabei gelernt habe
Frag erst, wenn es einen sichtbaren Grund gibt. Ein Berechtigungsdialog beim ersten App-Start, ohne dass der Nutzer irgendetwas angetippt hat, wird sehr oft weggeklickt — und beim zweiten „Nein" ist die Tür bei Android dauerhaft zu. Frage stattdessen genau in dem Moment, in dem jemand auf „Foto aufnehmen" tippt. Dann ist der Zusammenhang klar und die Zustimmungsrate deutlich höher.
Und plane den Fall ein, dass jemand ablehnt. Eine App, die danach nur noch eine leere Fläche zeigt, wirkt kaputt. Ein Satz reicht: was fehlt, und wie man es in den Einstellungen nachholen kann.
Du baust gerade eine Android-App und bist dir bei so einer Stelle unsicher? Schreib mir — ich schaue mir das gerne an.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
ModalBottomSheet in Material 3 – das Blatt, das von unten hereinfährt
Ein Menü, eine Detailansicht, eine Auswahl – auf dem Handy kommt so etwas von unten. Material 3 bringt das fertig mit, inklusive Wischgeste und Verdunkelung. Man muss nur wissen, wie man es richtig schließt.
callbackFlow – Listener-APIs in einen Flow verwandeln
Android steckt voller Callback-APIs mit `register`/`unregister`. Mit `callbackFlow` machst du daraus einen ganz normalen Flow – inklusive automatischem Abmelden, wenn niemand mehr zuhört.
CompositionLocal – Werte durch den Compose-Baum reichen, ohne sie durchzuschleifen
Wenn ein Wert durch acht Composables durchgereicht wird, nur damit ihn das neunte benutzen kann, ist etwas schief. CompositionLocal löst das – aber nur für eine sehr bestimmte Sorte von Werten.