Coding Bihar

Android App Development

Coding Bihar is dedicated to providing you with the knowledge and tools you need to excel in Android development. Start your journey with us and transform your app ideas into reality with Jetpack Compose. Connect, learn, and grow with Coding Bihar today!

How to Show Pdf with Jetpack Compose

How to Show Pdf with Jetpack Compose - Coding Bihar
How to Show Pdf with Jetpack Compose

How to Show Pdf in Jetpack Compose without using any third party library?

What is PDF?

PDF means Portable Document Format developed by Abode in the year 1992. It is a combination of vector and bitmap graphics. It is popular as it is portable and less memory consuming format and the most advance feature is that it can be encrypted for security in which a password is required to open, edit or view the contents.

 How to Show Pdf in Jetpack Compose?

There are many third party external library are available to display PDF files. To display a PDF in Jetpack Compose, you need to use an external library because Jetpack Compose doesn't have built-in support for PDF rendering. One popular library for this purpose is AndroidPdfViewer.

dependencies {
implementation ("com.github.barteksc:android-pdf-viewer:3.2.0-beta.1")
}

But in this tutorial, we are going to use the built-in Android APIs. Specifically, you can use Pdf Renderer to render PDF pages into bitmaps, which can then be displayed using Jetpack Compose's Image composable. 

Why Pdf Renderer is used?

Pdf Renderer is used to render each page of the PDF into a bitmap, which is then displayed using Jetpack Compose's Image composable. So, this approach does not rely on any third-party libraries and uses only Android's built-in PDF rendering capabilities.

Steps:

1. Open Android Studio
2. Create a New Project and choose Empty Compose Activity
3. Create assets folder and save pdf files in it.
4. MainAcitivity
5. Create a new file named PdfViewerApp

Create an assets folder

Create an assets folder

Create an assets folder

MainActivity

Copy this code →

package com.codingbihar.pdfviewerapp

import ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            JetpackComposeSkillTheme {
             val navController = rememberNavController()
                PdfViewerApp(navController)

            }
        }
    }
}

PdfViewerApp

Copy this code →

package com.codingbihar.pdfviewerapp

import ...

@Composable
fun PdfViewerApp(navController: NavHostController) {
    NavHost(navController, startDestination = "list") {
        composable("list") { PdfList(navController) }
        composable("pdfViewer/{fileName}") { backStackEntry ->
            PdfViewer(
                navController,
                fileName = backStackEntry.arguments?.getString("fileName") ?: ""
            )
        }
    }
}
@Composable
fun PdfList(navController: NavHostController) {
    val context = LocalContext.current
    val pdfFiles = remember { mutableStateOf>(emptyList()) }

    LaunchedEffect(Unit) {
        pdfFiles.value = try {
            context.assets.list("")?.filter { it.endsWith(".pdf") } ?: emptyList()
        } catch (e: IOException) {
            emptyList()
        }
    }

    Column(modifier = Modifier.fillMaxSize().statusBarsPadding()) {
        pdfFiles.value.forEach { fileName ->
            Button(onClick = {
                navController.navigate("pdfViewer/$fileName")
            }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) {
                Text(text = fileName)
            }
        }
        if (pdfFiles.value.isEmpty()) {
            Text("No PDFs available", modifier = Modifier.fillMaxWidth().padding(top = 16.dp))
        }
    }
}

@Composable
fun PdfViewer(navController: NavHostController, fileName: String) {
    val context = LocalContext.current
    val zoomState = remember { mutableFloatStateOf(1f) }
    val bitmapState = remember { mutableStateOf(null) }
    val coroutineScope = rememberCoroutineScope()
    val currentPage = remember { mutableIntStateOf(0) }
    val totalPages = remember { mutableIntStateOf(0) }

    // Handle back press to navigate back to the list
    BackHandler {
        navController.popBackStack()
    }

    LaunchedEffect(fileName, currentPage.intValue) {
        coroutineScope.launch {
            try {
                // Copy PDF from assets to cache
                val cacheFile = File(context.cacheDir, fileName)
                if (!cacheFile.exists()) {
                    context.assets.open(fileName).use { inputStream ->
                        FileOutputStream(cacheFile).use { outputStream ->
                            inputStream.copyTo(outputStream)
                        }
                    }
                }

                // Open PDF file and get the current page
                val fileDescriptor = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.MODE_READ_ONLY)
                val pdfRenderer = PdfRenderer(fileDescriptor)
                totalPages.intValue = pdfRenderer.pageCount // Set total pages

                val page = pdfRenderer.openPage(currentPage.intValue)
                val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)

                withContext(Dispatchers.Main) {
                    bitmapState.value = bitmap
                }
                page.close()
                pdfRenderer.close()
                fileDescriptor.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        Box(modifier = Modifier
            .weight(1f)
            .fillMaxSize()
            .pointerInput(Unit) {
                detectHorizontalDragGestures { change, dragAmount ->
                    change.consume()
                    if (dragAmount > 0) {
                        // Swipe right to left
                        if (currentPage.intValue < totalPages.intValue - 1) {
                            currentPage.intValue += 1
                        }
                    } else if (dragAmount < 0) {
                        // Swipe left to right
                        if (currentPage.intValue > 0) {
                            currentPage.intValue -= 1
                        }
                    }
                }
            }) {
            bitmapState.value?.let { bmp ->
                Image(
                    bitmap = bmp.asImageBitmap(),
                    contentDescription = "PDF Page",
                    modifier = Modifier
                        .fillMaxSize()
                        .simpleZoomable(zoomState)
                        .graphicsLayer(
                            scaleX = zoomState.floatValue,
                            scaleY = zoomState.floatValue
                        ),
                    contentScale = ContentScale.Fit
                )
            } ?: run {
                CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
            }
        }

        // Navigation Controls (Optional, for testing purposes)
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Button(
                onClick = {
                    if (currentPage.intValue > 0) {
                        currentPage.intValue -= 1
                    }
                }
            ) {
                Text("Previous")
            }
            Button(
                onClick = {
                    if (currentPage.intValue < totalPages.intValue - 1) {
                        currentPage.intValue += 1
                    }
                }
            ) {
                Text("Next")
            }
        }
    }
}

fun Modifier.simpleZoomable(
    zoomState: MutableState
) = this.pointerInput(Unit) {
    detectTransformGestures { _, _, zoomChange, _ ->
        val newZoom = max(1f, zoomState.value * zoomChange)
        zoomState.value = min(2f, newZoom)

    }
}

OUTPUT:

Pdf Viewer App Output 1
 Sandeep Gupta

Posted by Sandeep Gupta

We’d love to hear your thoughts and suggestions to make Coding Bihar even better – please share your feedback us at:sandeep@codingbihar.com. Thank you for being a part of our community!

Special Message

Welcome to coding bihar!