Search code examples
kotlinfirebase-storageandroid-jetpack-composeandroid-jetpackpdfrenderer

How to do more efficient render a pdf with PDFRender and show in LazyColumn in JetPack Compose


i want to download a pdf file from FireStorage, render the file, add every page like ImageBitMap in a List and show in a LazyColumn. The problem is that it is very slow trying to scroll, even freezing 4-5 secs when page change.

Any ideas on how to make it more efficient and faster? Is there a library for JetPack Compose that I can use to make this better?

Thanks

PDFScreen.kt

@Composable
fun PDFScreen(obraId: ObraId, autorId: AuthorId) {

    val storageRef = FirebaseStorage.getInstance().reference
    val pathReference = storageRef.child("obras/${autorId}/${obraId}.pdf")
    val localFile = File.createTempFile("obra", "pdf")
    var imageList = remember { mutableStateListOf<ImageBitmap?>(null) }

    pathReference.getFile(localFile).addOnSuccessListener {
        imageList.clear()
        if (it.task.isSuccessful) {
            val input = ParcelFileDescriptor.open(localFile, ParcelFileDescriptor.MODE_READ_ONLY)
            val renderer = PdfRenderer(input)

            for (i in 0 until renderer.pageCount) {

                val page = renderer.openPage(i)
                val bitmap =
                    Bitmap.createBitmap(Constants.width, Constants.height, Bitmap.Config.ARGB_8888)
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                imageList.add(bitmap.asImageBitmap())
                page.close()
                
            }
            renderer.close()
        }
    }

    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(items = imageList) { imagen ->

            if (imagen != null) {

                Image(
                    modifier = Modifier
                        .fillMaxSize(), bitmap = imagen, contentDescription = "Prueba"
                )
            }
        }
    }
}


Solution

  • You should not make any heavy calculations or db/network calls directly from the composable functions.

    All composite functions are view constructors and can be reconfigured (e.g., recalled) whenever the values of a changeable state change, and for animations - up to every frame. In your code, getFile query is executed when a new item is added to the list of changeable states making an endless cycle.

    I suggest you start with the state in compose documentation, including this youtube video which explains the basic principles.

    In your case, the fething code should be moved to view model: The PDFScreenViewModel object will be created when the view appears and destroyed when the view leaves the view hierarchy:

    class PDFScreenViewModel: ViewModel() {
        val imageList = mutableStateListOf<ImageBitmap?>(null)
        
        private val storageRef = FirebaseStorage.getInstance().reference
        private val pathReference = storageRef.child("obras/${autorId}/${obraId}.pdf")
        private val localFile = File.createTempFile("obra", "pdf")
        
        init {
            load()
        }
        
        fun load() {
            pathReference.getFile(localFile).addOnSuccessListener {
                imageList.clear()
                if (it.task.isSuccessful) {
                    val input = ParcelFileDescriptor.open(localFile, ParcelFileDescriptor.MODE_READ_ONLY)
                    val renderer = PdfRenderer(input)
    
                    for (i in 0 until renderer.pageCount) {
    
                        val page = renderer.openPage(i)
                        val bitmap =
                            Bitmap.createBitmap(Constants.width, Constants.height, Bitmap.Config.ARGB_8888)
                        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                        imageList.add(bitmap.asImageBitmap())
                        page.close()
    
                    }
                    renderer.close()
                }
            }
        }
    }
    
    @Composable
    fun PDFScreen(obraId: ObraId, autorId: AuthorId) {
        val viewModel: PDFScreenViewModel = viewModel()
        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(items = viewModel.imageList) { imagen ->
    
                if (imagen != null) {
    
                    Image(
                        modifier = Modifier
                            .fillMaxSize(), bitmap = imagen, contentDescription = "Prueba"
                    )
                }
            }
        }
    }