Permissions, storage and publishing

Requesting runtime permissions, choosing where files belong, and the steps to put a signed build on Google Play.

Requesting permissions

Normal permissions are granted at install time just by declaring them. Dangerous permissions — camera, location, contacts, microphone — must additionally be requested at runtime, and the user can refuse.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-feature android:name="android.hardware.camera" android:required="false" />
private val askCamera = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) openCamera() else showRationale()
}

fun ensureCamera() {
    when {
        ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED -> openCamera()
        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> showRationale()
        else -> askCamera.launch(Manifest.permission.CAMERA)
    }
}
  • Check shouldShowRequestPermissionRationale to decide whether to explain first instead of firing the system dialog blindly.
  • A refusal is permanent after two denials on modern Android: the app must route the user to system settings rather than asking again.
  • Declare android:required on <uses-feature> so devices without the hardware are not filtered out of the store listing.

Where files belong

NeedUseCleared when
Small key/value settingsDataStore (or SharedPreferences)App data is cleared
Private files, only your app reads theminternal storage via context.filesDirApp is uninstalled
Temporary scratch datacontext.cacheDirThe system may reclaim it any time
Photos, music, documents shared with othersMediaStore / Storage Access FrameworkUser deletes them
Structured, queryable dataRoom (SQLite)App is uninstalled
Secrets and tokensEncrypted storage or the KeystoreApp is uninstalled
// private file inside the app sandbox
val notes = File(context.filesDir, "notes.txt")
notes.writeText("remember to ship")
val restored = notes.readText()

// let the user pick a destination for an export, no storage permission needed
val create = registerForActivityResult(
    ActivityResultContracts.CreateDocument("text/plain")
) { uri -> uri?.let { context.contentResolver.openOutputStream(it)?.use { s ->
    s.write(restored.toByteArray())
} } }
create.launch("notes.txt")

Since Android 10 the scoped storage rules mean you no longer ask for broad storage permission to write a file the user chose. Direct paths like /sdcard/Download are not yours to write; go through MediaStore or the Storage Access Framework.

Signing and shipping

android {
    defaultConfig {
        applicationId = "com.example.notes"
        versionCode = 12
        versionName = "1.4.0"
        minSdk = 24
        targetSdk = 35
    }
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

// ./gradlew bundleRelease    ->  app/build/outputs/bundle/release/app-release.aab
  • Play distributes App Bundles (.aab); Google generates per-device APKs from it. APKs are still accepted for direct distribution.
  • versionCode must increase with every upload; versionName is the string users see.
  • Keep the upload keystore and its passwords in a safe place. Losing it means you cannot ship updates to that listing unless Play App Signing can reset it for you.
  • Ship to a closed testing track first. Internal testing is near-instant; production review of a new app or a sensitive permission can take days.
⚠️
Never commit a keystore or its passwords to version control. Read them from environment variables or a local properties file that is listed in .gitignore, and enable Play App Signing so the upload key can be rotated if it leaks.

FAQ

Do I really need a permission to save a file?
Only for files the user did not explicitly choose. Anything your app creates in its own sandbox or writes through the Storage Access Framework needs no storage permission at all.
Why was my release build rejected?
Common causes are a targetSdk below the current Play requirement, a debuggable or unoptimised build, a missing privacy policy for data-collecting permissions, and placeholder or duplicate content in the listing.

Layouts and Jetpack Compose Android projects and activities

Last refreshed 2026-09-18.