Hello champs. I have this implementation but am ...
# compose
d
Hello champs. I have this implementation but am falling into this error after download.
Copy code
class PDFGenerator(private val context: Context) {
    private var webView: WebView? = null
    private val templateEngine: TemplateEngine by lazy {
        TemplateEngine().apply {
            setTemplateResolver(AssetTemplateResolver(context, "templates/", ".html"))
        }
    }

    sealed class PdfResult {
        data object Success : PdfResult()
        data class Error(val message: String) : PdfResult()
    }

    suspend fun generatePDF(
        customerName: String,
        amount: String,
        date: String,
        onProgress: (Float) -> Unit
    ): PdfResult = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        try {
            // Format amount with currency
            val formattedAmount = try {
                val amountValue = amount.toDoubleOrNull() ?: 0.0
                String.format("$%,.2f", amountValue)
            } catch (e: Exception) {
                "$${amount}"
            }

            // Format date to be more readable
            val formattedDate = try {
                val parsedDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)
                SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault()).format(parsedDate!!)
            } catch (e: Exception) {
                date
            }

            val data = mapOf(
                "customerName" to customerName,
                "amount" to formattedAmount,
                "date" to formattedDate
            )

            val htmlContent = processTemplate(data)
            val result = generateAndSavePdf(htmlContent, onProgress)
            cleanup()
            result
        } catch (e: Exception) {
            Log.e(TAG, "PDF generation failed", e)
            PdfResult.Error(e.message ?: "Unknown error occurred")
        }
    }

    private fun processTemplate(data: Map<String, String>): String {
        val thymeleafContext = ThymeleafContext()
        data.forEach { (key, value) -> thymeleafContext.setVariable(key, value) }
        return templateEngine.process("invoice_template", thymeleafContext)
    }

    private suspend fun generateAndSavePdf(
        htmlContent: String,
        onProgress: (Float) -> Unit
    ): PdfResult = withContext(Dispatchers.Main) {
        try {
            if (webView == null) {
                webView = WebView(context).apply {
                    settings.apply {
                        javaScriptEnabled = false
                        defaultFontSize = 16
                        defaultTextEncodingName = "UTF-8"
                    }
                }
            }

            val fileName = "invoice_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())}.pdf"
            saveWithMediaStore(fileName, htmlContent, onProgress)
        } catch (e: Exception) {
            Log.e(TAG, "Failed to generate PDF", e)
            PdfResult.Error("Failed to generate PDF: ${e.message}")
        }
    }

    private suspend fun saveWithMediaStore(
        fileName: String,
        htmlContent: String,
        onProgress: (Float) -> Unit
    ): PdfResult = withContext(Dispatchers.Main) {
        var contentValues: ContentValues? = null
        var uri: Uri? = null

        try {
            // Create ContentValues on IO thread
            contentValues = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
                ContentValues().apply {
                    put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
                    put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS)
                        put(MediaStore.MediaColumns.IS_PENDING, 1)
                    }
                }
            }

            // Get URI on IO thread
            uri = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    context.contentResolver.insert(
                        MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
                        contentValues
                    )
                } else {
                    val documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
                    if (!documentsDir.exists()) {
                        documentsDir.mkdirs()
                    }
                    val file = File(documentsDir, fileName)
                    Uri.fromFile(file)
                }
            } ?: throw IOException("Failed to create new MediaStore record.")

            // Initialize WebView if needed
            if (webView == null) {
                webView = WebView(context).apply {
                    settings.apply {
                        javaScriptEnabled = false
                        defaultFontSize = 16
                        defaultTextEncodingName = "UTF-8"
                    }
                }
            }

            // Create a deferred to handle the async completion
            val completableDeferred = CompletableDeferred<PdfResult>()

            webView?.webViewClient = object : WebViewClient() {
                override fun onPageFinished(view: WebView?, url: String?) {
                    super.onPageFinished(view, url)
                    try {
                        val printAdapter = webView?.createPrintDocumentAdapter(fileName)
                        val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager

                        printManager.print(
                            fileName,
                            printAdapter!!,
                            PrintAttributes.Builder()
                                .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
                                .setResolution(PrintAttributes.Resolution("pdf", "pdf", 600, 600))
                                .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
                                .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
                                .build()
                        )
                        completableDeferred.complete(PdfResult.Success)
                    } catch (e: Exception) {
                        completableDeferred.complete(PdfResult.Error("Failed to generate PDF: ${e.message}"))
                    }
                }
            }

            // Load the content
            webView?.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)

            // Wait for the result
            val result = completableDeferred.await()

            // Update the MediaStore on IO thread
            withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    contentValues?.clear()
                    contentValues?.put(MediaStore.MediaColumns.IS_PENDING, 0)
                    context.contentResolver.update(uri, contentValues, null, null)
                }
            }

            result
        } catch (e: Exception) {
            Log.e(TAG, "Failed to save PDF with MediaStore", e)
            PdfResult.Error("Failed to save PDF: ${e.message}")
        }
    }

    private fun cleanup() {
        webView?.destroy()
        webView = null
    }

    companion object {
        private const val TAG = "PDFGenerator"
    }
}
What could be the issue?
🧵 17
not kotlin but kotlin colored 1
s
Not sure how this is related to Kotlin at all, let alone Compose. This is very Android specific. Also, please post long code blocks as replies to the thread, instead of cluttering the main message list. However, at a quick glance at the error, you should (probably) call WebView methods on the main thread. In Coroutines I think you can switch to that using
withContext(Dispatchers.Main) { ... }
.