Skip to content
All posts
June 4, 20264 min read

Android Version Update Strategy: How to Support New APIs Without Breaking Old Devices

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.

AndroidKotlinEngineering
Share:

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.


The Compatibility Model

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.


Version Checks in Kotlin

kotlin
// 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))
}

The @RequiresApi Annotation

Mark functions that require a specific API level:

kotlin
@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
    }
}

code
@RequiresApi
tells lint to warn you if you call the function without the appropriate version check. Never suppress these warnings — they prevent crashes on older devices.


AndroidX Components for Key Compatibility Cases

FeatureAPI IntroducedJetpack Alternative
Biometric authAPI 28
code
BiometricPrompt
(supports API 21+)
WorkManager scheduling
code
WorkManager
(API 14+)
Notification channelsAPI 26
code
NotificationCompat
(handles API differences)
Permission resultsAPI 30
code
ActivityResultContracts.RequestPermission
Exact alarmsAPI 31
code
AlarmManagerCompat
Predictive backAPI 33
code
OnBackPressedDispatcher
(handles all versions)

When in doubt, check if AndroidX has a solution before writing your own version checks.


Deciding What MinSdk to Use

Setting

code
minSdk
determines how much compatibility code you write and how many devices you support. Current guidelines for 2026:

MinSdkAndroid VersionApprox. Market Coverage
215.0 Lollipop~99%
247.0 Nougat~98%
268.0 Oreo~97%
289 Pie~94%
3112~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.


New Permission Model (Android 13+)

Android 13 split broad permissions into granular ones:

kotlin
// 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:

kotlin
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)
    }
}

Predictive Back Gesture (Android 13+)

Android 13 added predictive back — users can preview where the back gesture leads. Opt in:

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

Use

code
OnBackPressedDispatcher
for custom back handling (works across all API levels):

kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        onBackPressedDispatcher.addCallback(this) {
            // Custom back handling
            if (shouldHandleBack()) {
                handleBack()
            } else {
                isEnabled = false
                onBackPressedDispatcher.onBackPressed()
            }
        }
    }
}

Testing Across API Levels

Firebase Test Lab lets you run tests on real devices with different API levels. In your CI:

yaml
- 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=34

Test at:

  • Your minSdk (lowest supported)
  • One mid-range API level
  • Latest Android release

Three device/API combinations catch the vast majority of compatibility issues.


Raising MinSdk: The Process

When raising minSdk (e.g., from 24 to 26):

  1. Check Play Console → Android Vitals → API distribution for your app
  2. What percentage of your users are on API < 26? If < 2%, raise it.
  3. Remove all
    code
    Build.VERSION.SDK_INT >= 26
    version checks (now safe to assume)
  4. Replace deprecated APIs that minSdk 26 no longer requires compat workarounds for
  5. Test on an API 24-25 emulator to confirm new build correctly targets higher minimum

Raising minSdk once a year or so reduces accumulated compatibility code significantly.


Takeaways

  • Use Jetpack/AndroidX when available — it handles compatibility internally
  • code
    @RequiresApi
    + lint errors prevent forgetting version checks before new API calls
  • MinSdk 24 covers 98% of devices in 2026 — a reasonable default
  • Android 13 granular permissions and predictive back require explicit opt-in
  • Test at minSdk, a mid-range level, and latest release — three configurations cover most issues
Share:
S

Sudarshan 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.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

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.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this