Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Jetpack Compose works best when the UI renders state, reports user events, and lets an appropriate state holder decide what changes. Android runtime permissions follow a separate but related rule: a grant covers the protected access named by that permission, not every action an app might take. Keep state ownership clear, request access when a feature needs it, and make denial a supported path.

How Compose connects UI, events, and state

Compose UI is a function of state: composables receive values to display, and recomposition updates the interface when observed state changes. A composable can also report an event through a callback. Its caller can then decide how to handle that event and where the resulting state belongs.

The flow is unidirectional: events move from the UI to a state holder, and updated state moves back to the UI. A screen-level state holder such as a ViewModel can handle business logic. This separation gives the app a clear place to process actions and can make UI behavior easier to test and less prone to inconsistent state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. The user interacts with a composable.
  2. The composable reports an event through a callback.
  3. A state holder evaluates the event and updates state as appropriate.
  4. The composable receives the updated state and renders it.

Android’s Compose UI Architecture documentation cautions: “The UI layer should never change state outside of an event handler because this can introduce inconsistencies and bugs in your application.”

Where should Compose state live?

Keep state with the lowest-level owner that can read and write it. A simple value used only by one composable can remain local. If several composables need the same value, move it to their lowest common ancestor. If screen state depends on business logic, use an appropriate screen-level state holder.

State location Use it when Ownership and trade-off
Inside a composable The state is simple, UI-only, and local to that composable. It stays close to its consumer; other composables do not share it directly.
Hoisted to a parent composable Multiple composables need to read or update the same value. Place it at their lowest common ancestor so the shared value has one owner.
Screen-level state holder, such as a ViewModel Screen state is tied to business logic. The holder processes events and exposes state for the UI to render.

Avoid passing a ViewModel through every nested composable. Pass the immutable state each composable needs and callbacks or events it can report. This keeps nested UI focused on rendering and communicating user interaction instead of owning screen-wide business rules. See Android’s state-hoisting guidance.

When should an Android app request a runtime permission?

Request a permission when the user reaches the feature or action that needs protected access, rather than asking for unrelated access at startup. Explain the connection in context, request only what is needed for that action, check permission before each protected operation, and handle both grant and denial.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Android’s runtime-permission guidance says: “You must check whether you have a permission every time you perform an operation that requires that permission.” A grant permits access to the resource or information governed by that permission; it does not establish blanket authorization for unrelated downstream actions or prove the user’s intent beyond the grant.

This is the practical meaning of “permission to invoke is not permission to act”: treat a grant as scoped access, and design the feature’s subsequent actions around what the user actually requested. The phrase is product-design guidance, not a quoted Android platform rule.

Request at the feature, not as a blanket precondition

Connect the request to the feature action that needs access. Android’s Permissions on Android page states: “When the user requests a particular action in your app, your app should request only the permissions that it needs to complete that action.” A permission request at app startup can lack that immediate context; requesting as the user enters the relevant flow makes the purpose easier to understand.

Support denial without blocking unrelated use

If the user denies access, gracefully disable or reduce the feature that depends on it while keeping other app functionality usable where possible. Do not repeatedly pressure the user after denial. Permission handling is part of the feature flow, not a reason to make every other part of the app unusable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Compose example: requesting notification permission

For notifications, Android’s Compose notification-permission guide recommends rememberLauncherForActivityResult() with ActivityResultContracts.RequestPermission(). Put that request where users can understand what notifications are for and why they might opt in. This is a notification-specific Compose example, not a universal implementation for every protected Android resource.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.