2

I have the request which works well in postman:

enter image description here

and I'm trying to make it with Retrofit. In general file sizes will be >500MB that. I did such uploading method:

fun uploadFile(file:File) {

        val client = OkHttpClient().newBuilder()
            .build()
        val mediaType: MediaType? = "text/plain".toMediaTypeOrNull()
        val body: RequestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart(
                "data", file.name,
                file.asRequestBody()
            )
            .build()
        val request: Request = Request.Builder()
            .url("https://..../upload.php")
            .method("POST", body)
            .build()
        val response: okhttp3.Response = client.newCall(request).execute()

       println(response.message)
    }

but I need to have file for uploading. I can create temporary file with such way:

val path = requireContext().cacheDir
val file = File.createTempFile(
    name ?: "",
    fileUri.lastPathSegment,
    path
)
val os = FileOutputStream(file)
os.write(string)
os.close()

but I usually receive outOfMemoryException. I also added to the AndroidManifest.xml heap param:

android:largeHeap="true"

but it didn't help me at all during temp file creating. I don't know how postman uploads files, but in general I managed to upload with his help file with size about 600Mb. I can also cut selected file with chunks:

val data = result.data
data?.let {
      val fileUri = data.data
      var name: String? = null
      var size: Long? = null
      fileUri.let { returnUri ->
            contentResolver?.query(returnUri!!, null, null, null, null)
      }?.use { cursor ->
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)

            cursor.moveToFirst()
            name = cursor.getString(nameIndex)
            size = cursor.getLong(sizeIndex)
       }


val inputStream: InputStream? = fileUri?.let { it1 ->
    contentResolver.openInputStream(
        it1
    )
}

val fileData = inputStream?.readBytes()
val mimeType = fileUri.let { returnUri ->
returnUri.let { retUri ->
    if (retUri != null) {
           contentResolver.getType(retUri)
    }
}
}


fileData?.let {
       val MAX_SUB_SIZE = 4194304 // 4*1024*1024 == 4MB
       var start = 0 // From 0
       var end = MAX_SUB_SIZE // To MAX_SUB_SIZE bytes
       var subData: ByteArray // 4MB Sized Array

       val max = fileData.size
       if (max > 0) {
           while (end < max) {
                subData = fileData.copyOfRange(start, end)
                start = end
                end += MAX_SUB_SIZE
                if (end >= max) {
                    end = max
                }
                                
                println("file handling" + subData.size)



        }
     end-- // To avoid a padded zero
     subData = fileData.copyOfRange(start, end)
     println("file handling" + subData.size)
     }
   }
}

all actions will be made in:

 private val filesReceiver =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {

             }
         }

so I won't have any file path in normal way. Anyway I think I did something wrong.

UPDATE

right now I have such file uploading from inputStream:

 private fun doSomeNetworkStuff(file:InputStream, name:String) {
        GlobalScope.launch(Dispatchers.IO) {
            val client = OkHttpClient()
                .newBuilder()
                .protocols(listOf(Protocol.HTTP_1_1))
                .connectTimeout(10, TimeUnit.MINUTES)
                .readTimeout(10, TimeUnit.MINUTES)
                .build()
            val mediaType: MediaType? = "text/plain".toMediaTypeOrNull()
            val body: RequestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart(
                    "data", name,
                    file.readBytes().toRequestBody(mediaType)
                )
                .build()
            val request: Request = Request.Builder()
                .url("https://.../upload.php")
                .method("POST", body)
                .build()

            val response: Response = client.newCall(request).execute()

            println(response.body)
        }
    }

and receive such error:

java.lang.OutOfMemoryError: Failed to allocate a 173410912 byte allocation with 25165824 free bytes and 89MB until OOM, max allowed footprint 199761800, growth limit 268435456

but I can upload with this code file with size about 90mb

Andrew
  • 1,947
  • 2
  • 23
  • 61
  • Unclear what your problem is. You have a file and you wanna upload it. And suddenly you talk about creating a temporary file. Why? And why do you need a file path if you have an uri? And where are you mentioning that you have an uri for your file? – blackapps Nov 24 '21 at 07:28
  • You should upload a large file in the same way as a small one. And use the uri. Dont make a copy first. You also did not mention that you made a copy first. – blackapps Nov 24 '21 at 07:34
  • the problem is connected with `outOfMemory` exception, I can create temp file which then will be converted to requestbody, that is why I mentioned it, in case when I won't create temp file I don't know how to create `file.asRequestBody()`, file path can be used for file creating, as I remember from file chooser we can't get normal file path for file creation – Andrew Nov 24 '21 at 07:42
  • You still did not make clear why you need to make a copy. You create your own problem by making a copy. A copy is not needed. Use the uri. Still you did not mention the uri you have. – blackapps Nov 24 '21 at 07:46
  • yes, maybe you are right, and I think that I don't need copy, but in such case I don't know how to make file as request body, I only know such way :( – Andrew Nov 24 '21 at 07:48
  • Wrong problem description. You dont know how to use the uri for the request body. Still you did not mention an uri. – blackapps Nov 24 '21 at 07:49

2 Answers2

1

The retrofit multipart stuff has a member that takes an Uri for a request body.

You try to use the one for a File instance.

blackapps
  • 8,011
  • 2
  • 11
  • 25
  • can you clarify pls which staff, because I saw this question https://stackoverflow.com/questions/34562950/post-multipart-form-data-using-retrofit-2-0-including-image and used staff from him – Andrew Nov 24 '21 at 07:56
  • Did you see somerhing for an uri there? On for an input stream? – blackapps Nov 24 '21 at 08:03
  • Google for inputstreamrequestbody. – blackapps Nov 24 '21 at 08:05
  • I tried to use input stream as you said, but with byte array usage, and my uploading method fails on file size > 90mb, can you check my question upd pls? – Andrew Nov 25 '21 at 07:57
  • I did only say to use the uri. It seems you are not doing that. You should not use a byte array. Or an input stream. Well... Not in this way. – blackapps Nov 25 '21 at 12:55
  • maybe you can add some sample, because I did it like understood you, maybe you know better it that me?) because I didn't find any mentions about uri to requestbody – Andrew Nov 25 '21 at 13:06
  • https://commonsware.com/blog/2020/07/05/multipart-upload-okttp-uri.html – blackapps Nov 25 '21 at 13:49
0

Have you set log in loggingInterceptor or restadapter ?
if yes then try to set it NONE.