
The problem
Wouldn’t it be great if we could forget about old Android versions and just use the latest libraries and features?
According to apilevels.com, most users today run Android 10 (SDK 29) or higher. But if your app has millions of users, dropping support for older devices could mean losing a big chunk of your audience — and therefore revenue.
Android itself offers excellent backward compatibility. For example, Jetpack Compose supports all the way back to Android 5 (SDK 21), released in 2014. However, not every library is so generous. Some require you to increase your app’s minSdkVersion in order to use them.
This creates a tough question:
“Do I add this library to get a new feature, but lose 5% of my users?”
In most cases, the answer is: No.
Solution
Fortunately, there’s a workaround. You can integrate a library with a higher minSdkVersion than your app while still providing a fallback solution for users on unsupported devices.
Let’s walk through an example using the Jetpack PDF library, which currently only supports devices with SDK 31 or higher.
Suppose your app has minSdkVersion = 24. If you try to add the library:
dependencies {
implementation("androidx.pdf:pdf-viewer-fragment:1.0.0-alpha10")
implementation("androidx.fragment:fragment-compose:1.8.9") # Needed for Pdf Viewer Fragment
}
You’ll hit this build error:
uses-sdk:minSdkVersion 24 cannot be smaller than version 31 declared in library [androidx.pdf:pdf-viewer:1.0.0-alpha10] /path/pdf-viewer-1.0.0-alpha10/AndroidManifest.xml as the library might be using APIs not available in 24 Suggestion: use a compatible library with a minSdk of at most 24, or increase this project's minSdk version to at least 31, or use tools:overrideLibrary="androidx.pdf" to force usage (may lead to runtime failures)
The last suggestion is the key. We can override the library’s minSdkVersion in our AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
android:minSdkVersion="24"
tools:overrideLibrary="androidx.pdf" />
<!-- Other configurations -->
</manifest>
But when you build again, you’ll get another error for androidx.pdf.document.service. Add that too, and eventually you’ll need to include multiple overrides:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk
android:minSdkVersion="24"
tools:overrideLibrary="androidx.pdf, androidx.pdf.document.service, androidx.pdf.viewer.fragment" />
<!-- Other configurations -->
</manifest>
At this point, the project will compile successfully.
Adding Runtime Checks
Now we need to make sure our app behaves correctly on devices below SDK 31.
In the screen where the user selects and opens a PDF, we add a runtime SDK version check in rememberLauncherForActivityResult.
- If the device is running SDK 31+, we use the Jetpack PDF library to render the PDF.
- Otherwise, we fall back to an intent that delegates PDF rendering to another installed app.
This way:
- Users with newer devices get the modern in-app PDF viewer.
- Users with older devices still get the feature — just via an external app.
| package com.example.pdfapp | |
| import android.content.Intent | |
| import android.net.Uri | |
| import android.os.Build | |
| import android.os.Bundle | |
| import androidx.activity.compose.rememberLauncherForActivityResult | |
| import androidx.activity.compose.setContent | |
| import androidx.activity.enableEdgeToEdge | |
| import androidx.activity.result.contract.ActivityResultContracts | |
| import androidx.compose.foundation.layout.Box | |
| import androidx.compose.foundation.layout.fillMaxSize | |
| import androidx.compose.foundation.layout.padding | |
| import androidx.compose.foundation.layout.safeContentPadding | |
| import androidx.compose.material3.Button | |
| import androidx.compose.material3.Scaffold | |
| import androidx.compose.material3.Surface | |
| import androidx.compose.material3.Text | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.getValue | |
| import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | |
| import androidx.compose.runtime.setValue | |
| import androidx.compose.ui.Alignment | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.platform.LocalContext | |
| import androidx.compose.ui.res.stringResource | |
| import androidx.core.os.bundleOf | |
| import androidx.fragment.app.FragmentActivity | |
| import androidx.fragment.compose.AndroidFragment | |
| import androidx.pdf.viewer.fragment.PdfViewerFragment | |
| import com.example.pdfapp.ui.theme.PdfAppTheme | |
| class MainActivity : FragmentActivity() { | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| enableEdgeToEdge() | |
| setContent { | |
| PdfAppTheme { | |
| Scaffold( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .safeContentPadding() | |
| ) { innerPadding -> | |
| Surface(modifier = Modifier.padding(innerPadding)) { | |
| DocumentSelectionScreen() | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| @Composable | |
| private fun DocumentSelectionScreen() { | |
| val context = LocalContext.current | |
| var documentUri: Uri? by remember { mutableStateOf(null) } | |
| val documentSelectionLauncher = | |
| rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> | |
| if (Build.VERSION.SDK_INT >= 31) { | |
| documentUri = uri | |
| } else { | |
| val openPdfIntent = Intent(Intent.ACTION_VIEW).apply { | |
| setDataAndType(uri, "application/pdf") | |
| addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | |
| } | |
| context.startActivity(openPdfIntent) | |
| } | |
| } | |
| if (documentUri == null) { | |
| Box( | |
| contentAlignment = Alignment.Center, | |
| modifier = Modifier.fillMaxSize() | |
| ) { | |
| Button( | |
| onClick = { documentSelectionLauncher.launch(arrayOf("application/pdf")) } | |
| ) { | |
| Text(stringResource(R.string.select_document)) | |
| } | |
| } | |
| } else { | |
| documentUri?.let { PdfRenderer(uri = it, modifier = Modifier.fillMaxSize()) } | |
| } | |
| } | |
| @Composable | |
| private fun PdfRenderer( | |
| uri: Uri, | |
| modifier: Modifier = Modifier | |
| ) { | |
| AndroidFragment<PdfViewerFragment>( | |
| arguments = bundleOf( | |
| "documentUri" to uri | |
| ), | |
| modifier = modifier | |
| ) | |
| } |
Result: no one is left behind. 🎉
Demo Project
You can see the full implementation in my demo app here:
👉 GitHub: pdf-app
Job Offers
Closing
If you found this article helpful or interesting, please give it a clap and consider subscribing for more content! I’d love to hear your thoughts! Your feedback and insights are always welcome, as I’m eager to learn, collaborate, and grow with other developers in the community.
Have any questions? Feel free to reach out!
You can also follow me on Medium or LinkedIn for more insightful articles and updates. Let’s stay connected!
This article was previously published on proandroiddev.com



