Course outline

Android development / Intermediate: state, data, and inclusive features / Lesson 10

Permissions and respectful platform integration

Build optional camera and notification features around user intent, denial, and version-specific platform contracts.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate declarations, runtime grants, and feature availability.
  • Provide useful denied and revoked states.

Start with an alternative to permission

A feature should request only the access needed for the action the user chose. Selecting a cover image may use the system photo picker rather than broad library access. Picking a document can use a system contract and a scoped URI. Direct camera use has a different permission requirement from asking another camera app to capture a result through its contract. Review the actual API path before adding permissions. Every unnecessary declaration increases maintenance and user confusion, while a narrower platform interaction often gives a clearer ownership and privacy model.

Request access in the feature context

Dangerous permissions require runtime handling on Android 6.0, API 23, and later according to the app’s target and platform rules. Declare the needed permission, check its current grant before use, optionally explain the rationale when appropriate, and request through an Activity Result contract. A previous grant is not permanent: users and the platform can revoke access. Do not ask on every recomposition or repeatedly after denial. Keep unrelated reading features available, and let a user decline without being trapped in a loop of system dialogs.

kotlin A direct-camera request owned by an activity
class CaptureActivity : ComponentActivity() {
    private val cameraPermission = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted -> showCameraAvailability(granted) }

    fun onDirectCameraRequested() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
            PackageManager.PERMISSION_GRANTED) showCameraAvailability(true)
        else cameraPermission.launch(Manifest.permission.CAMERA)
    }
}

Declare CAMERA in the manifest and import the AndroidX Activity Result and ContextCompat APIs. Implement showCameraAvailability to render allowed or denied state, and add a rationale branch when required. Hardware opening still needs current checks and lifecycle management; this excerpt does not open the camera.

Separate notification permission from delivery

Android 13, API 33, introduced POST_NOTIFICATIONS for non-exempt notifications. The prompt timing differs for apps targeting API 33 or higher versus older targets, so do not generalize behavior from one emulator. Notification channels also matter on Android 8.0, API 26, and later; a granted runtime permission does not override a blocked channel. A foreground service still requires its notification contract, and launching one does not itself require obtaining POST_NOTIFICATIONS. Keep these distinct rules visible rather than reducing every missing notification to a single permission Boolean.

kotlin A version-guarded permission check
fun needsNotificationPermission(context: Context): Boolean =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
        ContextCompat.checkSelfPermission(
            context, Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED

Compile with an SDK that defines the API 33 constants, declare POST_NOTIFICATIONS, and request it only from an appropriate user-driven UI flow. A false result means this permission check does not require a request; it does not prove that app notifications or a specific channel are enabled.

Treat denial as a supported outcome

Permission denial is a user choice, not a crash condition. Explain which feature is unavailable and offer a useful alternative, such as manually entering a title instead of scanning. Some denial states cannot show another system prompt, and rationale APIs alone do not perfectly identify every history. Offer a settings path when appropriate without nagging. Special access such as exact alarms has its own workflow and must not be treated as an ordinary dangerous permission. Test initial denial, repeated denial, revocation, and return from settings independently.

Test device, target, and user state together

Platform behavior depends on device API, targetSdk, library implementation, and current settings. Build a small matrix that includes the oldest supported environment and relevant behavior-change boundaries. Verify both the permission result and the feature action: a granted permission cannot create absent hardware, enable a disabled system service, or guarantee remote delivery. Keep feature state observable when the app resumes from settings, and avoid retaining a stale permission flag as if it were permanent authority. Document the compatibility assumptions next to the integration.

Common errors and fixes

Permission prompts repeat while the screen renders

Why it happens
The request runs in the composable body.
How to fix it
Launch from a user event through a stable registered contract.
Prevent it next time
Review denial and recomposition paths together.

Notifications remain invisible after permission is granted

Why it happens
The channel or app notification setting is blocked.
How to fix it
Inspect channel creation, channel settings, and posting requirements.
Prevent it next time
Test permission and channel states separately.

A formerly working camera action raises SecurityException

Why it happens
The app relied on a stale stored grant flag.
How to fix it
Recheck current permission and handle loss at the protected operation.
Prevent it next time
Include revocation and settings-return tests.

Check your understanding

1. A user only wants to select one cover image. Compare broad media access with a system picker contract.

Show worked solution

The picker narrows access to user-selected content and avoids an unnecessary broad library permission. Handle cancellation and the returned URI’s lifetime. If access must persist for later background processing, follow the contract’s documented persistence options rather than assuming the URI will remain readable forever.

2. On API 33+, the runtime grant is present but one reminder channel is disabled. What should the app conclude?

Show worked solution

The runtime permission gate is satisfied, but that channel cannot deliver normally under its current setting. Preserve the user’s choice and explain the specific setting if reminders are requested. Re-requesting the runtime permission cannot override the channel, and repeated prompts would misdiagnose the actual problem.

Key takeaways

  • Least access begins with selecting the right platform contract.
  • Permission state is current and revocable.
  • Device API, target behavior, and settings all affect integration.

Official references