Android 16 Release & Google Play Requirements
- Stable Release: June 10, 2025 (Pixel devices first, other manufacturers later)
- Material 3 Expressive UI: Rollout Q4 2025
- New apps / updates required: Target SDK ≥ Android 15 (API Level 35) from August 31,
2025
- Existing apps required: Target SDK ≥ Android 14 (API Level 34) from August 31, 2025
(extension possible until November 1, 2025)
- Android 16 Target SDK Mandatory?: No deadline yet, but it's worth preparing now
Important new features in Android 16
- Adaptive apps for large screens: Apps can now use all the space on all screens,
including tablets and foldables.
- Predictive Back: System-wide animations for back navigation and three-button
navigation.
- Progress notifications: Live updates for ride services, deliveries or navigation.
- Advanced Graphic Effects (AGSL): RuntimeColorFilter & RuntimeXfermode for complex
effects.
- JobScheduler updates & ART optimizations: Improved startup times, system boot and
camera, ART performance.
- Data protection & security: Improved intent protection, privacy sandbox integration,
secure location data during device pairing.
Figure 1: Design Mobile Only vs Design Adaptive Apps
Practice: Orientation Change & State Management on Android 16
Here are practical code examples (Kotlin + Jetpack Compose) around orientation change,
state handling and migration to modern Android (Android 16/targetSdk latest
version).
Manifest: Orientation no longer a config change
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:screenOrientation="unspecified"
android:configChanges="keyboardHidden|keyboard|screenLayout|uiMode">
</activity>
Note: Orientation should no longer be intercepted via configChanges
to avoid
unnecessary
activity reloads.
ViewModel + SavedStateHandle
@HiltViewModel
class AppViewModel @Inject constructor(
private val savedState: SavedStateHandle,
private val repo: ItemsRepository
) : ViewModel() {
private val _query = MutableStateFlow(savedState.get("query") ?: "")
val query: StateFlow = _query.asStateFlow()
val items = query
.debounce(250)
.flatMapLatest { q -> repo.search(q) }
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
fun onQueryChange(newValue: String) {
_query.value = newValue
savedState["query"] = newValue
}
}
Note: SavedStateHandle is optional here, but useful if app values should be preserved
when the process dies.
Compose screen: collectAsStateWithLifecycle vs. rememberSaveable
@Composable
fun SearchScreen(viewModel: AppViewModel = hiltViewModel()) {
val query by viewModel.query.collectAsStateWithLifecycle()
val items by viewModel.items.collectAsStateWithLifecycle()
Column(Modifier.fillMaxSize().padding(16.dp)) {
OutlinedTextField(
value = query,
onValueChange = viewModel::onQueryChange,
label = { Text("Suche") },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(12.dp))
LazyColumn(Modifier.fillMaxSize()) {
items(items) { item ->
Text(item.title, Modifier.padding(vertical = 8.dp))
Divider()
}
}
}
}
Note: collectAsStateWithLifecycle
is the default for automatically observing changes from
the ViewModel. UI-specific state that is independent of the ViewModel should be managed
with rememberSaveable
.
Window-Size Classes & Foldables
// Implementation required, some APIs still alpha
val windowSizeClass = calculateWindowSizeClass(this)
AppRoot(windowSizeClass.widthSizeClass)
Note: A method for calculating the SizeClass must be implemented. Various APIs exist,
some still in alpha.
Secure lifecycle & data
@Composable
fun LifecycleAwareScreen(onStopSaveDraft: (String) -> Unit) {
var draft by rememberSaveable { mutableStateOf("") }
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner, draft) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) onStopSaveDraft(draft)
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
OutlinedTextField(
value = draft,
onValueChange = { draft = it },
label = { Text("Notiz") },
modifier = Modifier.fillMaxWidth()
)
}
Note: Useful if inputs should be persisted before Activity Destroy, e.g. in database or
via network.
Permissions & Privacy
- Android 13+:
POST_NOTIFICATIONS
- Android 14+: granular access to media (selected files only)
- Android 16: Changes here so far minimal, relevant for migration across multiple
versions
val permission = Manifest.permission.POST_NOTIFICATIONS
val state = rememberPermissionState(permission)
LaunchedEffect(Unit) { if (!state.status.isGranted) state.launchPermissionRequest() }
Performance tuning
- Baseline Profiles for faster startup (see documentation, not the comments shown
above)
- Configure R8/ProGuard properly
- Immutable State + Diffing in Compose avoid unnecessary recompositions
Testing
@Test fun state_survives_rotation() {
compose.onNodeWithText("Klicks: 0").performClick()
rotateScreen()
compose.onNodeWithText("Klicks: 1").assertExists()
}
Checks that the screen state is retained after rotation.
Migration checklist
- Update targetSdk & compileSdk
- Orientation/Resize via ViewModel + SavedStateHandle + rememberSaveable
- Consider Window Size Class & Foldables
- Check Runtime Permissions (Android 13/14+)
- Use Material 3 / Compose
- Optimize Baseline Profiles & ProGuard
- UI & instrumentation tests for rotation/multi-window
Conclusion
Android 16 requires updates for state management, layouts, and some permissions. With
let's dev, your app will be modernized, more performant, and Play Store-ready—including
orientation handling, multi-window, foldables, and performance tuning.