MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Every Android release brings new APIs that improve your app. Supporting them while maintaining compatibility with older Android versions requires a clear strategy. Here's how to handle API level differences, use version checks correctly, and decide when to raise minSdk.
On this page
Android OS fragmentation is real. Supporting API 24 through API 36 means 12 years of Android releases. Here's how to use newer APIs where they're available without breaking older devices.
Android provides two mechanisms for compatibility:
Version checks: Runtime checks that determine which code path to execute based on the device's API level.
AndroidX (Jetpack): Compatibility libraries that backport newer APIs to older versions. The library handles the version check internally.
When Jetpack has a compatible component, use it. When it doesn't, use a version check.
// Basic version check
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ only code
val locale = context.packageManager.getApplicationLocales(packageName)
}
// Inline helper for cleaner code
inline fun <T> whenAtLeast(version: Int, block: () -> T): T? {
return if (Build.VERSION.SDK_INT >= version) block() else null
}
// Usage
whenAtLeast(Build.VERSION_CODES.S) {
val vibrator = context.getSystemService(Vibrator::class.java)
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
}Mark functions that require a specific API level:
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun getAppLocales(packageManager: PackageManager, packageName: String): LocaleList {
return packageManager.getApplicationLocales(packageName)
}
// Calling code must handle the version check
fun getCurrentLocale() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val locales = getAppLocales(packageManager, packageName) // No lint warning
}
}@RequiresApi| Feature | API Introduced | Jetpack Alternative |
|---|---|---|
| Biometric auth | API 28 | code |
| WorkManager scheduling | — | code |
| Notification channels | API 26 | code |
| Permission results | API 30 | code |
| Exact alarms | API 31 | code |
| Predictive back | API 33 | code |
When in doubt, check if AndroidX has a solution before writing your own version checks.
Setting
minSdk| MinSdk | Android Version | Approx. Market Coverage |
|---|---|---|
| 21 | 5.0 Lollipop | ~99% |
| 24 | 7.0 Nougat | ~98% |
| 26 | 8.0 Oreo | ~97% |
| 28 | 9 Pie | ~94% |
| 31 | 12 | ~80% |
My current default is minSdk 24 (Android 7.0). It covers 98% of devices and avoids having to support Android 5-6 edge cases.
For new apps in 2026, minSdk 26 is increasingly reasonable — it enables using several modern APIs without version checks, and Android 8 support policies are well established.
Android 13 split broad permissions into granular ones:
// Android 12 and below
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
// Android 13+ (granular)
<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" />In code, request the right permission based on API level:
fun requestMediaPermission(launcher: ActivityResultLauncher<String>) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
launcher.launch(Manifest.permission.READ_MEDIA_IMAGES)
} else {
launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}Android 13 added predictive back — users can preview where the back gesture leads. Opt in:
<!-- AndroidManifest.xml -->
<application android:enableOnBackInvokedCallback="true">Use
OnBackPressedDispatcherclass MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this) {
// Custom back handling
if (shouldHandleBack()) {
handleBack()
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
}
}Firebase Test Lab lets you run tests on real devices with different API levels. In your CI:
- name: Run tests on Firebase Test Lab
run: |
gcloud firebase test android run \
--type instrumentation \
--app app-debug.apk \
--test app-debug-androidTest.apk \
--device model=redfin,version=30 \
--device model=oriole,version=32 \
--device model=shiba,version=34Test at:
Three device/API combinations catch the vast majority of compatibility issues.
When raising minSdk (e.g., from 24 to 26):
Build.VERSION.SDK_INT >= 26Raising minSdk once a year or so reduces accumulated compatibility code significantly.
@RequiresApiSudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read