Android 16 Migration Checklist for Production Apps

Editorial team
Dot
July 2, 2026
A technical development banner titled "Android 16 Migration Checklist for Production Apps" with the subtitle "Everything you need to check, test, and update before rolling out to Android 16." In the center, a 3D green Android mascot stands with the number "16" printed on its chest next to a smartphone displaying "Android 16 Ready" with a checkmark. To the left, a clipboard labeled "Migration Checklist" sits next to blocks for API & Behavior Changes, Permissions & Privacy Updates, and System Compatibility Checks. To the right are blocks for UI/UX Adjustments, Library & Dependency Updates, Testing on Android 16, and Performance & Stability Validation. A bottom horizontal banner details deployment steps: Review Changes, Update & Adapt, Test Thoroughly, Optimize Performance, Ensure Compatibility, and Ship with Confidence.

“Your CI just went green. You ship to production. Then crash reports flood in targetSdkVersion mismatch, predictive back gesture crashing, photo picker broken on every Android 16 device in the field.” “Sounds familiar? Android 16 isn’t just a version bump; it’s a behavioral overhaul that breaks assumptions your app has held for years.” “This checklist exists so your team doesn’t find out the hard way on launch day.”

Introduction

Android 16 (API level 36) is not a routine update. Released by Google in mid-2025, it represents one of the most sweeping behavioral and API changes the Android platform has seen since Android 12 introduced the Privacy Dashboard and approximate location permissions.

For production apps, the stakes are high. Google has confirmed that all apps targeting the Play Store must target API 36 to remain fully distributed on new Android 16 devices. Apps that miss the deadline face distribution restrictions and, in some app categories, immediate blocking from new installs.

This blog is your team’s migration command center: a structured, code-backed checklist to ensure every corner of your production app is ready for Android 16.

Problem Statement: What Actually Breaks on Android 16

Most teams assume a targetSdkVersion bump is a one-afternoon job. Android 16 will challenge that assumption. Here’s a snapshot of every major breaking change and who it affects.

Breaking Changes at a Glance

Change Area Impact Table
Change Area Impact Who’s Affected
Predictive Back Gesture — mandatory Critical All apps
Photo Picker READ_EXTERNAL_STORAGE removed Critical Apps accessing media
16 KB memory page size (NDK) Critical Apps with native .so code
POST_NOTIFICATIONS strictly enforced High Apps that send notifications
Health Connect permission model High Health & fitness apps
Large screen / foldable adaptive layout enforcement High Tablet / foldable apps
OpenJDK 21 runtime behaviors High Apps using reflection/serialization
Exact alarm permission changes Medium Calendar, reminder apps
R8 Full Mode now the default in AGP 8.5 Medium All apps using minification
JobScheduler strict-mode enforcement Medium Background sync apps

Warning: Apps that use native libraries (.so files) compiled WITHOUT 16 KB page-size alignment will hard-crash silently on Android 16 devices. This includes apps using FFmpeg, OpenCV, SQLCipher, or any custom NDK code. There is no graceful fallback.

Pre-Migration: Environment Setup

Update your entire development environment before touching a single line of app code. Migrating to Android 16 with outdated tooling introduces false lint positives, incorrect emulator behavior, and build failures that mask the real issues.

Required Tool Versions for Android 16

Tool and Version Requirements Table
Tool Minimum Version
Android Studio Meerkat (2024.3.1) or later
Android Gradle Plugin (AGP) 8.5.0+
Gradle Wrapper 8.10+
Kotlin 2.0.21+
Compile SDK 36
Target SDK 36
NDK (if applicable) r27c or later
Java / JDK Toolchain JDK 21
Compose BOM 2025.04.00+

Update build.gradle.kts App Level

// app/build.gradle.kts


android {
  compileSdk = 36                      // Android 16


  defaultConfig {
      applicationId    = "com.yourcompany.app"
      minSdk           = 21
      targetSdk        = 36            // ← Required for Play Store compliance
      versionCode      = 42
      versionName      = "4.2.0"
  }


  compileOptions {
      sourceCompatibility = JavaVersion.VERSION_21
      targetCompatibility = JavaVersion.VERSION_21
  }


  kotlinOptions {
      jvmTarget = "21"
  }
}

Version Catalog (libs.versions.toml)

# gradle/libs.versions.toml


[versions]
agp            = "8.5.0"
kotlin         = "2.0.21"
compose-bom    = "2025.04.00"
core-ktx       = "1.15.0"
activity       = "1.10.1"
lifecycle      = "2.9.0"
hilt           = "2.53"
room           = "2.7.1"


[plugins]
android-app    = { id = "com.android.application",      version.ref = "agp"    }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt           = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Checklist A Predictive Back Gesture (Now Mandatory)

What Changed

Predictive Back Gesture was opt-in from Android 13, opt-out from Android 14–15. In Android 16, it is mandatory and cannot be disabled. The system intercepts all back events. Apps that still call activity.onBackPressed() directly will receive a deprecation crash in Android 16 and a hard crash in future releases.

Warning: Every override of fun onBackPressed() in your codebase is a ticking crash. Find them all before your next release.

Step 1: Enable in Manifest

<!-- AndroidManifest.xml -->
<application
  android:enableOnBackInvokedCallback="true"
  ...>
</application>=

Step 2: Replace onBackPressed() in Activities

// ❌ OLD — deprecated and crashes on Android 16
override fun onBackPressed() {
  if (someCondition) doSomething()
  else super.onBackPressed()
}


// ✅ NEW — OnBackPressedDispatcher
class DetailActivity : AppCompatActivity() {


  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)


      onBackPressedDispatcher.addCallback(this) {
          if (someCondition) {
              doSomething()
          } else {
              isEnabled = false                         // disable this callback
              onBackPressedDispatcher.onBackPressed()   // delegate to system
          }
      }
  }
}

Step 3: WebView Back Navigation

// In a Fragment or Activity that hosts a WebView
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
  if (webView.canGoBack()) {
      webView.goBack()      // navigate back in web history
  } else {
      isEnabled = false
      requireActivity().onBackPressedDispatcher.onBackPressed()
  }
}

Checklist

  • android:enableOnBackInvokedCallback="true" added to <application>
  • All onBackPressed() overrides were removed and replaced with OnBackPressedDispatcher
  • WebView back handled via OnBackPressedCallback
  • Custom dialogs and bottom sheets use the dispatcher
  • Tested predictive back animation on Android 16 emulator

 

Checklist B Photo Picker (READ_EXTERNAL_STORAGE Removed)

What Changed

Android 16 permanently removes READ_EXTERNAL_STORAGE for media access. Apps must use the system Photo Picker or the granular media permissions (READ_MEDIA_IMAGES, READ_MEDIA_VIDEO) introduced in Android 13.

Update Your Manifest

<!-- AndroidManifest.xml -->


<!-- ❌ REMOVE — invalid on API 36 -->
<!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> -->


<!-- ✅ ADD — granular media permissions -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />


<!-- Partial access for Android 14+ (user selects specific photos) -->
<uses-permissionandroid:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

Implement the System Photo Picker

// No runtime permission needed for Photo Picker — system handles it
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts


class GalleryFragment : Fragment() {


  // Single image selection
  private val pickSingleImage = registerForActivityResult(
      ActivityResultContracts.PickVisualMedia()
  ) { uri ->
      uri?.let { loadImage(it) }
  }


  // Multi-select (up to 10 items)
  private val pickMultiple = registerForActivityResult(
      ActivityResultContracts.PickMultipleVisualMedia(maxItems = 10)
  ) { uris ->
      uris.forEach { uri -> processImage(uri) }
  }


  // Launch — images only
  fun openImagePicker() {
      pickSingleImage.launch(
          PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
      )
  }


  // Launch — images + videos
  fun openMediaPicker() {
      pickMultiple.launch(
          PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)
      )
  }
}

Checklist

  • READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE were removed from the manifest
  • Photo Picker (PickVisualMedia) implemented for all image/video selection
  • Granular permissions added with correct maxSdkVersion attributes
  • Partial access (READ_MEDIA_VISUAL_USER_SELECTED) handled for Android 14+ devices
  • FileProvider is still configured for sharing files with external apps

 

Checklist C 16 KB Memory Page Size (NDK Apps)

What Changed

Android 16 ships on devices with 16 KB memory page sizes (previously 4 KB). Any .solibrary compiled without 16 KB alignment will fail to load with a hard crash, no warning, no fallback.

E/linker: "libmynative.so" is 4KB-page-aligned only.
        Cannot load on 16KB-page devices.
        Process terminated.

Fix CMakeLists.txt

# CMakeLists.txt


cmake_minimum_required(VERSION 3.22.1)
project("mynativelib")


# ✅ Required — 16 KB alignment linker flag
set(CMAKE_SHARED_LINKER_FLAGS
  "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")


add_library(
  mynativelib
  SHARED
  src/main/cpp/native-lib.cpp
)


target_link_libraries(mynativelib android log)

Update NDK Version

// app/build.gradle.kts


android {
  ndkVersion = "27.2.12479018"   // r27c — minimum version for 16KB support


  defaultConfig {
      ndk {
          abiFilters += listOf("arm64-v8a", "x86_64")
      }
  }
}

Verify Your .so Files.

# Run from your project root after a release build
# Check LOAD segment alignment — must be 0x4000 (16384 = 16 KB)


readelf -l app/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a/libyourlib.so \
| grep "LOAD"


# ✅ GOOD:  LOAD  ... p_align 0x4000
# ❌ BAD:   LOAD  ... p_align 0x1000   ← 4KB — recompile with linker flag

Warning: Popular libraries such as SQLCipher, OpenCV 4.x, and older FFmpeg builds ship 4 KB-aligned .so files. Check each library’s changelog for Android 16 / 16 KB support before relying on an update alone.

Checklist

  • NDK updated to r27c (27.2.12479018) or later
  • CMakeLists.txt has -Wl,-z,max-page-size=16384 linker flag
  • All first-party .so files verified with readelf
  • All third-party native libraries updated to 16 KB-compatible versions
  • Tested on Android 16 emulator with 16 KB kernel page image

 

Checklist D Notification Permissions (Strictly Enforced)

What Changed

POST_NOTIFICATIONS (introduced in Android 13) is now strictly enforced on Android 16. Posting a notification without permission throws a SecurityException instead of silently failing.

Implement the Full Permission Flow

class NotificationHelper(private val activity: AppCompatActivity) {


  private val launcher = activity.registerForActivityResult(
      ActivityResultContracts.RequestPermission()
  ) { granted ->
      if (granted) createChannelAndSchedule()
      else showPermissionRationale()
  }


  fun requestIfNeeded() {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
          createChannelAndSchedule()   // API < 33 — no permission needed
          return
      }
      val status = ContextCompat.checkSelfPermission(
          activity, Manifest.permission.POST_NOTIFICATIONS
      )
      when {
          status == PackageManager.PERMISSION_GRANTED ->
              createChannelAndSchedule()
          activity.shouldShowRequestPermissionRationale(
              Manifest.permission.POST_NOTIFICATIONS
          ) -> showPermissionRationale()
          else ->
              launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
      }
  }


  private fun createChannelAndSchedule() {
      val channel = NotificationChannel(
          "general",
          "General Notifications",
          NotificationManager.IMPORTANCE_DEFAULT
      )
      activity.getSystemService(NotificationManager::class.java)
          .createNotificationChannel(channel)
  }
}

Checklist

  • POST_NOTIFICATIONS declared in manifest (with minSdkVersion="33")
  • Runtime permission request implemented with rationale flow
  • Notification channels created before posting (required since API 26)
  • FCM / push notification integration tested on Android 16
  • Deep links from notifications tested end-to-end

Checklist E Large Screen & Foldable Adaptations

What Changed

Android 16 introduces stricter adaptive layout enforcement for Play Store listing quality on large-screen devices. Apps that don’t adapt to multi-window, resizable, and foldable layouts receive lower Play Store rankings on tablets and foldables.

Jetpack Compose WindowSizeClass

// Adaptive layout driven by WindowSizeClass


@Composable
fun AdaptiveHomeScreen(windowSizeClass: WindowSizeClass) {
  when (windowSizeClass.widthSizeClass) {
      WindowWidthSizeClass.Compact  -> SingleColumnLayout()   // phone
      WindowWidthSizeClass.Medium   -> TwoPaneLayout()        // folded / small tablet
      WindowWidthSizeClass.Expanded -> ListDetailLayout()     // full tablet
  }
}


class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContent {
          val windowSizeClass = calculateWindowSizeClass(this)
          AdaptiveHomeScreen(windowSizeClass)
      }
  }
}

Manifest Declarations

<!-- AndroidManifest.xml -->
<application android:resizeableActivity="true" ...>
  <activity
      android:name=".MainActivity"
      android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density">
      <meta-data
          android:name="android.max_aspect"
          android:value="2.4" />
  </activity>
</application>

Checklist

  • android:resizeableActivity="true" set in the manifest
  • WindowSizeClass is used for all adaptive layout decisions
  • App tested in split-screen multi-window mode
  • App tested on foldable emulator (Pixel Fold profile in AVD)
  • No hardcoded pixel dimensions — all values in dp
  • Landscape orientation functional (not blocked without reason)

Checklist F Kotlin, Compose & Jetpack Updates

Compose BOM & Material 3 Expressive

# libs.versions.toml
[versions]
compose-bom  = "2025.04.00"        # Android 16 Material 3 Expressive support
material3    = "1.4.0-alpha07"
lifecycle    = "2.9.0"
room         = "2.7.1"
hilt         = "2.53"

ViewModel + Compose State Modern Pattern

// ViewModel with structured concurrency (Android 16 enforced)
class HomeViewModel : ViewModel() {


  private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
  val uiState: StateFlow<UiState> = _uiState.asStateFlow()


  fun load() {
      viewModelScope.launch {
          _uiState.value = UiState.Loading
          runCatching { repository.fetch() }
              .onSuccess { _uiState.value = UiState.Success(it) }
              .onFailure { _uiState.value = UiState.Error(it.message) }
      }
  }
}


// Composable — lifecycle-aware collection
@Composable
fun HomeScreen(vm: HomeViewModel = hiltViewModel()) {
  val state by vm.uiState.collectAsStateWithLifecycle()   // ← not collectAsState()
  when (state) {
      is UiState.Loading -> CircularProgressIndicator()
      is UiState.Success -> ContentView((state as UiState.Success).data)
      is UiState.Error   -> ErrorView((state as UiState.Error).message)
  }
}

Checklist

  • Compose BOM updated to 2025.04.00 or later
  • Material 3 components used throughout (Material 2 deprecated)
  • collectAsStateWithLifecycle() used instead of collectAsState()
  • Room migrated to KSP (Kapt is deprecated)
  • Hilt updated to 2.53+
  • Kotlin 2.0.21+ with K2 compiler enabled in gradle.properties

 

Checklist G ProGuard / R8 Full Mode

What Changed

AGP 8.5 enables R8 Full Mode by default. This is more aggressive than the previous compat mode — it strips code that older ProGuard rules preserved, which can cause ClassNotFoundException crashes in release builds.

// app/build.gradle.kts
android {
  buildTypes {
      release {
          isMinifyEnabled    = true
          isShrinkResources  = true
          proguardFiles(
              getDefaultProguardFile("proguard-android-optimize.txt"),
              "proguard-rules.pro"
          )
      }
  }
}

# proguard-rules.pro


# Data classes (Gson / Moshi / kotlinx.serialization)
-keepclassmembers class com.yourapp.model.** { *; }


# Retrofit service interfaces
-keep interface com.yourapp.api.** { *; }


# Parcelable
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}


# OpenJDK 21 reflection fix (Android 16)
-keepattributes Signature
-keepattributes *Annotation*

Checklist

  • Release build tested with R8 Full Mode (not just debug)
  • ProGuard rules updated for all serialization libraries in use
  • No ClassNotFoundException in release build crash logs
  • APK size reduction verified (R8 Full Mode should reduce further)
  • Deobfuscation mapping file uploaded to Play Console for crash analysis

 

Final Validation Table

Run through this gate before every Play Store submission targeting Android 16.

Validation Checklist Table
Category Validation Item Done?
Build compileSdk = 36, targetSdk = 36
Build AGP 8.5+, Gradle 8.10+, Kotlin 2.0.21+
Back Gesture enableOnBackInvokedCallback = true in the manifest
Back Gesture All onBackPressed() overrides removed
Photo Picker READ_EXTERNAL_STORAGE removed from the manifest
Photo Picker PickVisualMedia was implemented for all media selection
NDK (if used) 16 KB page alignment verified with readelf
NDK (if used) NDK r27c+ used in build
Notifications POST_NOTIFICATIONS runtime permission implemented
Notifications Notification channels created before posting
Large Screen resizeableActivity = true in the manifest
Large Screen WindowSizeClass adaptive layout implemented
Compose BOM 2025.04.00+ and Material 3 in use
ProGuard R8 Full Mode release build tested end-to-end
Testing Tested on Android 16 emulator (API 36)
Testing Tested on a physical Android 16 device
Play Store Target API policy deadline confirmed met

Key Takeaways

  • Android 16 is not optional: Google Play restricts distribution for apps that miss the targetSdk 36 deadline.
  • Predictive Back Gesture is mandatory: every onBackPressed() override must be migrated before your next release.
  • 16 KB page size is a silent hard crash:  NDK apps must recompile with alignment flags and use NDK r27c+.
  • Photo Picker replaces READ_EXTERNAL_STORAGE: the permission is gone; use PickVisualMedia for all media access.
  • R8 Full Mode is now the default: test release builds thoroughly; it strips more aggressively than before.
  • Large screen adaptability is a ranking signal:  implement WindowSizeClass to improve Play Store placement on tablets and foldables.

Conclusion

Android 16 raises the bar for production-quality Android apps. The behavioral changes mandatory predictive back, strict media permissions, 16 KB alignment for native code, and R8 Full Mode defaults demand a deliberate, tested migration approach.

Use this checklist as your team’s pre-release gate. Work through each section before your next Play Store submission, and you’ll ship a faster, safer, and fully compliant Android 16 app.

The Android 16 emulator is available in Android Studio Meerkat today. There is no reason to wait; start your migration this sprint.

 

References

FAQ’s

No items found.

Actionable Insights,
Straight to Your Inbox

Subscribe to our newsletter to get useful tutorials , webinars,use cases, and step-by-step guides from industry experts

Start Pushing Real-Time App Updates Today
Try AppsOnAir for Free
Stay Uptodate