Onboarding: move the contacts-access request out of compose into a dedicated onboarding step #127

Closed
opened 2026-07-02 13:31:32 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 13:31:32 +00:00 (Migrated from github.com)

The READ_CONTACTS permission (used only for recipient autocomplete) is currently requested lazily in the compose screen: ComposeScreen.kt fires a LaunchedEffect(Unit) on screen open (~line 101) that checks the permission and, if not granted, immediately launches the system dialog. Because it is keyed on Unit, it re-fires every time the compose screen opens — on minSdk 29/30 (no auto-permanent-deny), a user who declined without checking "don't ask again" gets re-prompted on every compose.

Proposal

Move the contacts-access request to a dedicated, clearly skippable step in the onboarding flow (alongside the existing battery-optimization step), requested once. Compose then just reads the current permission/autocomplete state and never prompts.

Notes

  • Degradation is already graceful (this is UX, not a stability fix): ContactsRepository.search() returns empty without permission (wrapped in runCatching), ComposeViewModel.searchContacts() guards on contactsAllowed, and the suggestion list only renders when non-empty.
  • Contacts is optional — the onboarding step must be skippable and the app fully usable without it.
  • Companion tickets: the onboarding step should carry a rationale (see the rationale follow-up) and there should be a way to enable it later after declining (see the re-enable follow-up).
  • Remove the per-compose-open LaunchedEffect trigger as part of this.

Origin: investigation of the contacts-denial path (compose recipient autocomplete).

The `READ_CONTACTS` permission (used only for recipient autocomplete) is currently requested **lazily in the compose screen**: `ComposeScreen.kt` fires a `LaunchedEffect(Unit)` on screen open (~line 101) that checks the permission and, if not granted, immediately launches the system dialog. Because it is keyed on `Unit`, it **re-fires every time the compose screen opens** — on minSdk 29/30 (no auto-permanent-deny), a user who declined without checking "don't ask again" gets re-prompted on every compose. ## Proposal Move the contacts-access request to a **dedicated, clearly skippable** step in the onboarding flow (alongside the existing battery-optimization step), requested **once**. Compose then just reads the current permission/autocomplete state and never prompts. ## Notes - Degradation is already graceful (this is UX, not a stability fix): `ContactsRepository.search()` returns empty without permission (wrapped in `runCatching`), `ComposeViewModel.searchContacts()` guards on `contactsAllowed`, and the suggestion list only renders when non-empty. - Contacts is optional — the onboarding step must be skippable and the app fully usable without it. - Companion tickets: the onboarding step should carry a rationale (see the rationale follow-up) and there should be a way to enable it later after declining (see the re-enable follow-up). - Remove the per-compose-open `LaunchedEffect` trigger as part of this. Origin: investigation of the contacts-denial path (compose recipient autocomplete).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#127