Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Multipart Requests on Android

I would really appreciate some help with the problem I'm facing.

I'm trying to post an image to a receipt parsing API and have problems constructing the actual request.

I have read and used much of the code from this article written by tarek on Medium to create a MultiPart class (with https) like the following:

Multipart.kt

package com.example.skopal.foodme.services

import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.net.URL
import javax.net.ssl.HttpsURLConnection

class Multipart
/**
 * This constructor initializes a new HTTPS POST request with content type
 * is set to multipart/form-data
 * @param url
 * *
 * @throws IOException
 */
@Throws(IOException::class)
constructor(url: URL) {

    companion object {
        private val LINE_FEED = "\r\n"
        private val maxBufferSize = 1024 * 1024
        private val charset = "UTF-8"
    }

    // creates a unique boundary based on time stamp
    private val boundary: String = "===" + System.currentTimeMillis() + "==="
    private val httpsConnection: HttpsURLConnection = url.openConnection() as HttpsURLConnection
    private val outputStream: OutputStream
    private val writer: PrintWriter

    init {

        httpsConnection.setRequestProperty("Accept-Charset", "UTF-8")
        httpsConnection.setRequestProperty("Connection", "Keep-Alive")
        httpsConnection.setRequestProperty("Cache-Control", "no-cache")
        httpsConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary)
        httpsConnection.setChunkedStreamingMode(maxBufferSize)
        httpsConnection.doInput = true
        httpsConnection.doOutput = true    // indicates POST method
        httpsConnection.useCaches = false
        outputStream = httpsConnection.outputStream
        writer = PrintWriter(OutputStreamWriter(outputStream, charset), true)
    }

    /**
     * Adds a upload file section to the request
     * @param fieldName  - name attribute in <input type="file" name="..."></input>
     * *
     * @param uploadFile - a File to be uploaded
     * *
     * @throws IOException
     */
    @Throws(IOException::class)
    fun addFilePart(fieldName: String, uploadFile: File, fileName: String, fileType: String) {
        writer.append("--").append(boundary).append(LINE_FEED)
        writer.append("Content-Disposition: file; name=\"").append(fieldName)
            .append("\"; filename=\"").append(fileName).append("\"").append(LINE_FEED)
        writer.append("Content-Type: ").append(fileType).append(LINE_FEED)
        writer.append(LINE_FEED)
        writer.flush()

        val inputStream = FileInputStream(uploadFile)
        inputStream.copyTo(outputStream, maxBufferSize)

        outputStream.flush()
        inputStream.close()
        writer.append(LINE_FEED)
        writer.flush()
    }

    /**
     * Adds a header field to the request.
     * @param name  - name of the header field
     * *
     * @param value - value of the header field
     */
    fun addHeaderField(name: String, value: String) {
        writer.append("$name: $value").append(LINE_FEED)
        writer.flush()
    }

    /**
     * Upload the file and receive a response from the server.
     * @param onSuccess
     * *
     * @param onFailure
     * *
     * @throws IOException
     */
    @Throws(IOException::class)
    fun upload(onSuccess: (String) -> Unit, onFailure: ((Int) -> Unit)? = null) {
        writer.append(LINE_FEED).flush()
        writer.append("--").append(boundary).append("--")
                .append(LINE_FEED)
        writer.close()

        try {
            // checks server's status code first
            val status = httpsConnection.responseCode
            if (status == HttpsURLConnection.HTTP_OK) {
                val reader = BufferedReader(InputStreamReader(httpsConnection.inputStream))
                val response = reader.use(BufferedReader::readText)
                httpsConnection.disconnect()
                onSuccess(response)
            } else {
                onFailure?.invoke(status)
            }

        } catch (e: IOException) {
            e.printStackTrace()
        }

    }

}

And I'm calling the above class from:

ReceiptRecognitionApi.kt

fun parseReceipt(file: File, cb: (String) -> Unit) {
    println("parseReceipt_1")

    Thread {
        val multipartReq = Multipart(URL(baseUrl))
        multipartReq.addHeaderField("apikey", taggunApiKey)
        multipartReq.addHeaderField("Accept", "application/json")

        multipartReq.addFilePart("file", file, "receipt.jpg", "image/jpeg")

        multipartReq.upload(
                onSuccess = { response: String ->
                    cb(response)
                },
                onFailure = { responseCode: Int ->
                    cb("$responseCode")
                })

    }.start()
}

The problem is that after initialisation of a Multipart object, I cannot append any headers or data to it. E.g. if the two addHeaderField-calls within the parseReceipt-function call is moved to the init-block in Multipart.kt, the headers are in the request, but otherwise not.

What am I doing wrong here?

like image 349
Peter Skopal Avatar asked Dec 11 '25 15:12

Peter Skopal


1 Answers

Usage of a third-party library solved my problem:

Fuel.upload(path = baseUrl, method = Method.POST)
            .header(TaggunConstants.taggunHeader(taggunApiKey))
            .dataParts { _, _ -> listOf(DataPart(file, "file", "image/jpeg")) }
            .responseJson { _, _, result ->
                result.fold(
                        success = { data ->
                            cb(gson.fromJson(data.content, Receipt::class.java))
                        },
                        failure = { error ->
                            println("An error of type ${error.exception} happened: ${error.message}")
                            cb(null)
                        }
                )
            }
like image 91
Peter Skopal Avatar answered Dec 13 '25 08:12

Peter Skopal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!