---
title: "Photo Picker no Android com Kotlin e Jetpack Compose em 2026"
url: "https://kotlin.dev.br/blog/photo-picker-android-kotlin-compose-2026/"
markdown_url: "https://kotlin.dev.br/blog/photo-picker-android-kotlin-compose-2026.MD"
description: "Use o Photo Picker no Android com Kotlin e Jetpack Compose para selecionar fotos e vídeos sem pedir acesso amplo à galeria, com upload e múltipla seleção."
date: "2026-08-13"
author: "Karina Melo"
---

# Photo Picker no Android com Kotlin e Jetpack Compose em 2026

Use o Photo Picker no Android com Kotlin e Jetpack Compose para selecionar fotos e vídeos sem pedir acesso amplo à galeria, com upload e múltipla seleção.


**Resposta rápida:** para deixar o usuário selecionar fotos ou vídeos em um app Android moderno, use o **Photo Picker** com `ActivityResultContracts.PickVisualMedia` ou `PickMultipleVisualMedia`. Em Jetpack Compose, registre o contrato com `rememberLauncherForActivityResult`, escolha o tipo de mídia e trate o `Uri` devolvido. Esse fluxo normalmente **não exige** `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` nem a antiga `READ_EXTERNAL_STORAGE`, porque o usuário concede acesso apenas aos itens escolhidos. Depois, leia o conteúdo com `ContentResolver`, valide tipo e tamanho e, se precisar manter acesso por muito tempo, copie o arquivo para o armazenamento privado do app ou persista a permissão quando o contrato e o provedor permitirem.

O Photo Picker é a melhor opção quando o requisito é “escolher uma foto de perfil”, “anexar três imagens”, “selecionar um vídeo” ou “enviar mídia para o backend”. Ele oferece mais privacidade que uma galeria própria, reduz pedidos de permissão e evita várias diferenças de armazenamento entre versões do Android.

Este guia mostra seleção única e múltipla com Kotlin e Compose, preview, leitura segura do `Uri`, upload com WorkManager, fallback, testes e erros comuns. Se o app precisa **capturar** a imagem com câmera própria, veja [CameraX com Kotlin e Compose](/blog/camerax-compose-kotlin-2026/). Para entender quando uma permissão ainda é necessária, consulte o guia de [permissões no Android](/blog/permissoes-android-kotlin-2026/).

## Photo Picker, seletor de documentos ou permissão de galeria?

As três soluções parecem equivalentes, mas atendem a necessidades diferentes:

| Necessidade | API recomendada | Permissão ampla? |
|---|---|---|
| Usuário escolhe uma foto ou vídeo | Photo Picker | Não |
| Usuário escolhe várias mídias | Photo Picker múltiplo | Não |
| Usuário escolhe PDF, planilha ou qualquer arquivo | `OpenDocument` | Não |
| App implementa uma galeria própria, listando biblioteca do aparelho | MediaStore | Pode exigir |
| App tira foto com experiência de câmera própria | CameraX | Câmera |
| App só abre a câmera externa e recebe o resultado | `TakePicture` | Geralmente não para câmera própria |

A regra prática é: **se o usuário pode escolher explicitamente os itens, prefira o seletor do sistema**. Pedir acesso a todas as fotos para enviar uma imagem de avatar é privilégio excessivo e aumenta a chance de abandono na tela de permissão.

O Photo Picker também reduz trabalho de compatibilidade. O contrato da Activity Result API usa o seletor adequado quando disponível e pode recorrer a mecanismos compatíveis do sistema em aparelhos sem a implementação principal. O app continua tratando um `Uri`, em vez de depender de um caminho físico no armazenamento.

## Dependência e configuração

Use uma versão atual da biblioteca AndroidX Activity compatível com o projeto. Centralize a versão no Version Catalog:

```toml
# gradle/libs.versions.toml
[versions]
activity = "<versao-estavel-atual>"

[libraries]
androidx-activity-compose = {
  module = "androidx.activity:activity-compose",
  version.ref = "activity"
}
```

```kotlin
// build.gradle.kts do módulo app
dependencies {
    implementation(libs.androidx.activity.compose)
}
```

O Photo Picker não faz parte do [Compose BOM](/blog/compose-bom-android-kotlin-guia-2026/). O BOM alinha artefatos do Compose; `activity-compose` mantém uma versão própria.

Para o fluxo comum de seleção, não adicione permissões de leitura de imagens “por garantia”. Um manifesto mínimo pode não precisar de nenhuma permissão relacionada à galeria:

```xml
<manifest ...>
    <!-- INTERNET somente se o app fizer upload para um backend -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application ... />
</manifest>
```

Se o mesmo app possui uma galeria própria via MediaStore, trate esse recurso separadamente. Não use a necessidade de uma tela administrativa como justificativa para pedir acesso amplo no fluxo simples de avatar.

## Selecionando uma foto no Jetpack Compose

O contrato `PickVisualMedia` devolve um `Uri?`. Valor `null` significa que o usuário cancelou ou não selecionou um item:

```kotlin
@Composable
fun PhotoPickerButton(
    onPhotoSelected: (Uri) -> Unit,
) {
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickVisualMedia(),
    ) { uri: Uri? ->
        uri?.let(onPhotoSelected)
    }

    Button(
        onClick = {
            launcher.launch(
                PickVisualMediaRequest(
                    ActivityResultContracts.PickVisualMedia.ImageOnly
                )
            )
        }
    ) {
        Text("Escolher foto")
    }
}
```

Não crie o launcher dentro do `onClick`. `rememberLauncherForActivityResult` deve ser chamado durante a composição e o clique apenas dispara `launch`.

Para selecionar somente vídeo:

```kotlin
launcher.launch(
    PickVisualMediaRequest(
        ActivityResultContracts.PickVisualMedia.VideoOnly
    )
)
```

Para aceitar imagem ou vídeo:

```kotlin
launcher.launch(
    PickVisualMediaRequest(
        ActivityResultContracts.PickVisualMedia.ImageAndVideo
    )
)
```

Há também suporte a tipos MIME específicos por meio de `SingleMimeType`, quando a versão adotada da biblioteca oferece o comportamento necessário. Mesmo assim, valide o tipo recebido no app: filtros de UI ajudam a escolha, mas não substituem validação antes de processar ou enviar o conteúdo.

## Mostrando preview com Coil

Bibliotecas de imagem para Android normalmente aceitam `Uri` diretamente. Com Coil em Compose:

```kotlin
@Composable
fun AvatarEditor() {
    var selectedUri by rememberSaveable {
        mutableStateOf<String?>(null)
    }

    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.PickVisualMedia(),
    ) { uri ->
        selectedUri = uri?.toString()
    }

    Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
        AsyncImage(
            model = selectedUri,
            contentDescription = "Prévia da foto selecionada",
            modifier = Modifier
                .size(128.dp)
                .clip(CircleShape),
            contentScale = ContentScale.Crop,
        )

        Button(
            onClick = {
                launcher.launch(
                    PickVisualMediaRequest(
                        ActivityResultContracts.PickVisualMedia.ImageOnly
                    )
                )
            }
        ) {
            Text("Alterar foto")
        }
    }
}
```

Salvar `uri.toString()` ajuda a restaurar o estado visual após uma recriação simples da Activity, mas não garante acesso eterno ao conteúdo. A concessão pode ser temporária, o item pode ser removido pelo usuário e o processo pode voltar muito tempo depois. Para um avatar que será enviado imediatamente, mantenha o `Uri` só durante o fluxo. Para edição duradoura ou fila offline, copie o conteúdo para um arquivo privado do app.

Veja mais detalhes de preview, placeholder e tratamento de erro no guia de [Coil com Jetpack Compose](/blog/coil-jetpack-compose-kotlin-2026/).

## Selecionando várias fotos

Use `PickMultipleVisualMedia` e defina um limite coerente com o produto:

```kotlin
@Composable
fun MultiplePhotoPicker(
    onPhotosSelected: (List<Uri>) -> Unit,
) {
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickMultipleVisualMedia(
            maxItems = 5
        ),
    ) { uris ->
        onPhotosSelected(uris)
    }

    OutlinedButton(
        onClick = {
            launcher.launch(
                PickVisualMediaRequest(
                    ActivityResultContracts.PickVisualMedia.ImageOnly
                )
            )
        }
    ) {
        Text("Selecionar até 5 fotos")
    }
}
```

O limite não deve existir apenas na UI. O ViewModel ou caso de uso também precisa rejeitar quantidade acima da regra, especialmente se outras entradas puderem acionar o upload.

Antes de escolher um número alto, considere memória, tempo de envio e experiência em rede móvel. Para uma avaliação de produto, 5 imagens podem bastar; para um chat, talvez o limite seja 10; para backup de galeria, o Photo Picker pontual provavelmente não é a arquitetura adequada.

Um estado de domínio simples evita passar listas soltas por toda a UI:

```kotlin
data class MediaSelectionState(
    val items: List<SelectedMedia> = emptyList(),
    val isUploading: Boolean = false,
    val error: String? = null,
)

data class SelectedMedia(
    val uri: Uri,
    val mimeType: String?,
    val sizeBytes: Long?,
)
```

## Nunca transforme o Uri em “caminho real”

Um dos erros mais persistentes em Android é procurar uma função `getRealPathFromUri()`. Em armazenamento moderno, o `Uri` pode representar conteúdo local, nuvem, provedor de documentos ou mídia gerenciada pelo sistema. Um caminho de arquivo pode não existir ou não estar acessível ao app.

Use `ContentResolver`:

```kotlin
fun readBytes(
    context: Context,
    uri: Uri,
): ByteArray {
    return context.contentResolver
        .openInputStream(uri)
        ?.use(InputStream::readBytes)
        ?: error("Não foi possível abrir a mídia selecionada")
}
```

Esse exemplo é simples, mas carregar uma foto ou vídeo inteiro em `ByteArray` pode estourar memória. Para upload, prefira streaming. Para transformação de imagem, decodifique com amostragem e limites. Para arquivos grandes, copie em blocos:

```kotlin
fun copyToPrivateStorage(
    context: Context,
    uri: Uri,
    destination: File,
) {
    context.contentResolver.openInputStream(uri).use { input ->
        requireNotNull(input) { "Conteúdo indisponível" }

        destination.outputStream().use { output ->
            input.copyTo(output, bufferSize = 8 * 1024)
        }
    }
}
```

O arquivo copiado para `filesDir` ou `cacheDir` passa a ser controlado pelo app. Use `filesDir` quando ele precisa sobreviver por mais tempo e `cacheDir` quando pode ser removido e recriado. Crie uma rotina de limpeza; mídia temporária esquecida vira consumo invisível de armazenamento.

## Descobrindo tipo, nome e tamanho

Não confie na extensão exibida. Consulte metadados e imponha limites:

```kotlin
data class MediaMetadata(
    val displayName: String?,
    val mimeType: String?,
    val sizeBytes: Long?,
)

fun queryMetadata(context: Context, uri: Uri): MediaMetadata {
    val resolver = context.contentResolver
    val mimeType = resolver.getType(uri)

    val projection = arrayOf(
        OpenableColumns.DISPLAY_NAME,
        OpenableColumns.SIZE,
    )

    resolver.query(uri, projection, null, null, null)?.use { cursor ->
        if (cursor.moveToFirst()) {
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)

            return MediaMetadata(
                displayName = nameIndex.takeIf { it >= 0 }
                    ?.let(cursor::getString),
                mimeType = mimeType,
                sizeBytes = sizeIndex.takeIf { it >= 0 && !cursor.isNull(it) }
                    ?.let(cursor::getLong),
            )
        }
    }

    return MediaMetadata(
        displayName = null,
        mimeType = mimeType,
        sizeBytes = null,
    )
}
```

Nem todo provedor informa tamanho. Quando `sizeBytes` vier nulo, aplique um limite durante a leitura: conte bytes e interrompa ao ultrapassar o máximo. Valide novamente no backend, porque qualquer regra apenas no cliente pode ser contornada.

Exemplo de validação local:

```kotlin
fun validateImage(metadata: MediaMetadata): Result<Unit> = runCatching {
    require(metadata.mimeType in setOf("image/jpeg", "image/png", "image/webp")) {
        "Formato não suportado"
    }

    metadata.sizeBytes?.let { size ->
        require(size <= 10L * 1024 * 1024) {
            "A imagem deve ter no máximo 10 MB"
        }
    }
}
```

O backend deve verificar MIME real, tamanho, dimensões e conteúdo antes de publicar. Nome e `Content-Type` enviados pelo cliente não são prova de que o arquivo é uma imagem válida.

## Mantendo acesso depois que o app fecha

Há três estratégias, em ordem de simplicidade:

1. **Consumir imediatamente:** leia ou envie o `Uri` enquanto o fluxo está ativo.
2. **Copiar para armazenamento privado:** opção mais previsível para upload posterior, edição ou retry.
3. **Persistir a permissão do URI:** útil em fluxos baseados em documentos quando as flags e o provedor permitem; não presuma que todo resultado do Photo Picker oferece a mesma concessão persistente.

Para uma fila de upload confiável, copiar para uma área privada costuma ser a escolha mais fácil de testar. O ViewModel registra o arquivo local; o worker envia; após sucesso, uma rotina remove a cópia.

Não guarde apenas uma string de URI no banco e espere que ela funcione semanas depois. Teste explicitamente reinicialização do processo, reinício do aparelho e remoção da mídia original.

## Upload em background com WorkManager

Uploads que precisam sobreviver à saída da tela devem ir para o WorkManager. O fluxo recomendado é:

1. usuário seleciona a mídia;
2. app valida e copia para arquivo privado;
3. banco salva um item `PENDING` com caminho interno e identificador;
4. WorkManager agenda envio com restrição de rede;
5. worker faz upload em streaming;
6. backend valida e devolve o identificador final;
7. app marca `UPLOADED` e remove o temporário quando seguro.

```kotlin
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

val request = OneTimeWorkRequestBuilder<MediaUploadWorker>()
    .setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        30,
        TimeUnit.SECONDS,
    )
    .setInputData(
        workDataOf("media_id" to mediaId)
    )
    .build()

WorkManager.getInstance(context).enqueueUniqueWork(
    "media-upload-$mediaId",
    ExistingWorkPolicy.KEEP,
    request,
)
```

Passe um ID pequeno no `Data`, não bytes, bitmap nem URI sem garantia de acesso. O worker consulta o banco e abre o arquivo privado. Use trabalho único para evitar duas tentativas simultâneas do mesmo upload.

O guia de [WorkManager com Kotlin](/blog/workmanager-kotlin-android-2026/) aprofunda retry, constraints e observabilidade do trabalho.

## ViewModel e fluxo de estado

O launcher pertence à camada de UI, porque depende do mecanismo de Activity Result. O processamento pertence ao ViewModel ou a um caso de uso:

```kotlin
class ProfilePhotoViewModel(
    private val mediaImporter: MediaImporter,
) : ViewModel() {

    private val _state = MutableStateFlow(ProfilePhotoState())
    val state = _state.asStateFlow()

    fun onPhotoSelected(uri: Uri) {
        viewModelScope.launch {
            _state.update { it.copy(importing = true, error = null) }

            runCatching {
                mediaImporter.importImage(uri)
            }.onSuccess { localMedia ->
                _state.update {
                    it.copy(
                        importing = false,
                        previewPath = localMedia.path,
                    )
                }
            }.onFailure { error ->
                _state.update {
                    it.copy(
                        importing = false,
                        error = error.message ?: "Não foi possível importar a foto",
                    )
                }
            }
        }
    }
}
```

Não passe `Context` arbitrário para o ViewModel. Encapsule `ContentResolver` em uma implementação Android de `MediaImporter`, injetada por interface. Isso mantém validação e regras testáveis.

## Compatibilidade e fallback

Antes de criar uma solução manual, deixe a Activity Result API cuidar do caminho compatível suportado pela versão adotada. Se o produto precisa mudar a interface conforme disponibilidade, consulte a API de disponibilidade do contrato:

```kotlin
val available = ActivityResultContracts.PickVisualMedia
    .isPhotoPickerAvailable(context)
```

A ausência do Photo Picker principal não significa automaticamente que o app deve pedir acesso a toda a galeria. Para seleção pontual, `OpenDocument` ou o fallback do próprio contrato continuam sendo opções mais privadas.

Teste em mais de uma combinação:

- Android recente com Photo Picker nativo;
- aparelho ou emulador com implementação compatível/backport;
- versão antiga que usa fallback;
- provedor de nuvem;
- usuário cancelando sem seleção;
- mídia removida entre seleção e leitura.

Evite condicionar toda a lógica a `Build.VERSION.SDK_INT` se a biblioteca já expõe disponibilidade. Recursos distribuídos por componentes do sistema nem sempre são descritos apenas pela versão da API.

## Android 14+ e acesso parcial a fotos

Versões recentes do Android permitem que o usuário conceda acesso apenas a fotos selecionadas quando um app solicita permissões de biblioteca. Esse fluxo é importante para apps que realmente implementam uma galeria própria via MediaStore.

Ele não transforma a permissão ampla na primeira escolha para anexos. Se o usuário só precisa escolher dois itens, o Photo Picker já oferece seleção explícita sem obrigar o app a manter uma tela de galeria, sincronizar mudanças de permissão ou explicar por que deseja listar toda a biblioteca.

Use a seguinte decisão:

- **Anexo/avatar/upload pontual:** Photo Picker.
- **Galeria própria ou editor que navega repetidamente por toda a biblioteca:** MediaStore + modelo de permissões aplicável.
- **Documento que não é mídia visual:** `OpenDocument`.

## Vídeos, duração e reprodução

Ao aceitar vídeos, tamanho não é a única regra. Valide duração, codec e resolução conforme o backend. Um vídeo 4K curto pode ser maior que o limite; um arquivo pequeno pode usar um formato não suportado pelo pipeline.

Para preview e reprodução, passe o `Uri` ao AndroidX Media3:

```kotlin
val mediaItem = MediaItem.fromUri(selectedUri)
player.setMediaItem(mediaItem)
player.prepare()
player.playWhenReady = true
```

O guia de [Media3 ExoPlayer com Compose](/blog/media3-exoplayer-compose-kotlin-2026/) mostra lifecycle, `PlayerView`, cache e liberação do player. Não use o Photo Picker para “converter” vídeo: ele seleciona conteúdo; transcodificação e upload são responsabilidades separadas.

## Segurança e privacidade

Mesmo sem pedir acesso amplo, o app ainda processa conteúdo do usuário. Adote estas práticas:

- colete apenas a mídia necessária ao recurso;
- explique antes da seleção como a foto ou o vídeo será usado;
- não registre URI completo, nome de arquivo ou imagem em analytics;
- remova cópias temporárias após upload, cancelamento ou expiração;
- valide conteúdo no cliente para UX e novamente no servidor para segurança;
- gere nomes internos aleatórios em vez de confiar no nome original;
- limite bytes, dimensões, duração e quantidade;
- remova metadados EXIF sensíveis quando o produto não precisa deles;
- não publique automaticamente uma imagem apenas porque o upload terminou;
- trate falha parcial ao enviar vários itens.

Se a imagem for usada como avatar público, mostre confirmação e política de remoção. Se houver moderação, modele estados como `PENDING`, `APPROVED` e `REJECTED`, em vez de trocar a URL pública imediatamente.

## Testes

Separe o teste em camadas.

### Teste unitário

Teste regras sem depender do seletor do sistema:

- MIME permitido e proibido;
- limite de tamanho conhecido e desconhecido;
- quantidade máxima;
- cópia interrompida;
- retry idempotente;
- limpeza após upload.

Use um `MediaImporter` fake que devolve um arquivo de teste ou falha de forma controlada.

### Teste de UI e integração

O Photo Picker é UI do sistema, então não baseie toda a suíte em encontrar textos específicos do fabricante. Valide principalmente:

- botão dispara o launcher;
- retorno com URI atualiza a tela;
- cancelamento mantém estado anterior;
- erro de leitura gera mensagem acessível;
- rotação não agenda upload duplicado;
- seleção múltipla respeita limite;
- processo recriado recupera item já copiado.

Em testes instrumentados, você pode abstrair o disparo por uma interface ou testar a tela com callback injetável. Deixe poucos testes end-to-end reais para o seletor e concentre regras no domínio.

## Erros comuns

1. **Pedir `READ_MEDIA_IMAGES` para escolher avatar.** O Photo Picker resolve sem acesso amplo.
2. **Tentar obter caminho físico do URI.** Abra o conteúdo com `ContentResolver`.
3. **Carregar vídeo inteiro em memória.** Faça streaming ou cópia em blocos.
4. **Guardar somente `uri.toString()` para upload futuro.** A concessão pode não sobreviver; copie para área privada.
5. **Confiar na extensão `.jpg`.** Consulte MIME e valide o arquivo no backend.
6. **Não limitar quantidade e tamanho.** Uma seleção válida pode ser inviável para rede ou memória.
7. **Criar launcher no clique.** Registre-o durante a composição.
8. **Disparar dois uploads do mesmo item.** Use ID persistente e trabalho único.
9. **Manter temporários para sempre.** Implemente limpeza após sucesso, cancelamento e timeout.
10. **Ignorar cancelamento.** `null` ou lista vazia é resultado normal, não exceção.
11. **Usar o URI como chave de negócio.** Gere um identificador próprio; provedores podem entregar representações diferentes.
12. **Logar dados da mídia.** URI e nome podem conter informações pessoais.

## Checklist de produção

- `activity-compose` atualizado e centralizado no Version Catalog.
- `PickVisualMedia` para seleção única e `PickMultipleVisualMedia` para múltipla.
- Nenhuma permissão de galeria adicionada sem necessidade real.
- Tipo de mídia explícito: imagem, vídeo ou ambos.
- `Uri` tratado com `ContentResolver`, sem “real path”.
- Limites de quantidade, bytes, dimensões e duração documentados.
- Preview com estado de loading e erro.
- Cópia privada antes de upload adiado.
- WorkManager usa ID persistido, constraint de rede e trabalho único.
- Backend revalida conteúdo e não confia em MIME do cliente.
- Temporários possuem política de limpeza.
- Cancelamento, processo recriado e mídia indisponível foram testados.
- Acessibilidade: botão rotulado e preview com descrição adequada ou decorativa.

## Perguntas frequentes

### Photo Picker precisa de permissão de armazenamento?

Para o fluxo normal de seleção explícita, não. O sistema concede acesso aos itens escolhidos. Permissões de mídia são necessárias em outros cenários, como uma galeria própria que consulta a biblioteca pelo MediaStore.

### Como selecionar várias fotos no Compose?

Registre `ActivityResultContracts.PickMultipleVisualMedia(maxItems)` com `rememberLauncherForActivityResult` e lance um `PickVisualMediaRequest(ImageOnly)`. O callback recebe `List<Uri>`.

### Posso salvar o URI no Room e abrir depois?

Você pode salvar a string, mas precisa garantir que a concessão de leitura continuará válida. Para upload posterior confiável, copie a mídia para armazenamento privado e salve o identificador do arquivo importado.

### Como enviar a imagem para uma API?

Abra um `InputStream` pelo `ContentResolver` e envie como corpo multipart em streaming, ou copie antes para um arquivo privado e deixe o WorkManager fazer o upload. Não converta arquivos grandes em `ByteArray` sem limite.

### Photo Picker substitui CameraX?

Não. Photo Picker seleciona mídia existente; CameraX controla captura, preview e análise da câmera. Um produto pode usar ambos: “Escolher da galeria” com Photo Picker e “Tirar foto” com CameraX ou `TakePicture`.

### O que fazer quando o usuário cancela?

Mantenha o estado anterior e não mostre erro. Cancelar é uma decisão normal. Só remova a foto atual se houver uma ação separada e explícita de remoção.

## Conclusão

O **Photo Picker no Android com Kotlin e Jetpack Compose** é o padrão certo para seleção pontual de fotos e vídeos: menos permissões, menos diferenças entre versões e uma experiência mais previsível para o usuário. A implementação básica é curta — `rememberLauncherForActivityResult`, `PickVisualMediaRequest` e um `Uri` —, mas uma solução de produção também precisa validar conteúdo, evitar caminhos físicos, controlar memória e preparar acesso duradouro quando houver upload em background.

A decisão arquitetural mais importante é distinguir seleção de biblioteca. Se o app pede ao usuário um anexo, use o seletor do sistema. Se precisa navegar por toda a galeria, aí sim avalie MediaStore e permissões. Esse recorte reduz risco de privacidade e simplifica manutenção.

Para continuar, leia [permissões modernas no Android](/blog/permissoes-android-kotlin-2026/), [CameraX com Compose](/blog/camerax-compose-kotlin-2026/), [WorkManager](/blog/workmanager-kotlin-android-2026/) e [offline-first com Kotlin](/blog/android-offline-first-kotlin-2026/). Quem quer aplicar essa stack em produtos reais pode acompanhar as [vagas Kotlin e Android](/vagas/) atualizadas no Kotlin Brasil.
