Multipart Uploads with OkHttp... and a Uri

As changes like scoped storage increase the need to be able to work with content Uri values, I have been increasingly pointing developers to this OkHttp issue comment. If you have a need to do a multipart form upload, and your content is identified by a Uri instead of a File, the InputStreamRequestBody that Jared Burrows created (with help from Jake Wharton) is what you need.

However, I never created a sample app for this, simply because I did not have a Web service needing that sort of upload. I realized a week ago that RequestBin exists and can handle this scenario. So, this evening I tossed together this sample app to demonstrate a Kotlin port of InputStreamRequestBody and its use.


The OkHttp recipes page shows how to use MultipartBody.Builder to create the body of a POST request. That MultipartBody.Builder has addFormDataPart() methods that let you fill in the elements of the “form”. For including a “file” (from the standpoint of the form), addFormDataPart() can accept a RequestBody. The specific recipe in that OkHttp documentation shows creating one from a File, such as by using the asRequestBody() extension function in Kotlin.

If you have a content Uri, though, OkHttp alone will not be sufficient. OkHttp supports non-Android environments. As a result, OkHttp knows nothing about Uri or ContentResolver or other Android SDK classes. But RequestBody is an abstract class, so we can create our own implementation that can use Android SDK classes for our own Android projects. That’s what Jared did in that OkHttp issue.

In Kotlin, an equivalent implementation looks like this:

class InputStreamRequestBody(
  private val contentType: MediaType,
  private val contentResolver: ContentResolver,
  private val uri: Uri
) : RequestBody() {
  override fun contentType() = contentType

  override fun contentLength(): Long = -1

  @Throws(IOException::class)
  override fun writeTo(sink: BufferedSink) {
    val input = contentResolver.openInputStream(uri)

    input?.use { sink.writeAll(it.source()) }
      ?: throw IOException("Could not open $uri")
  }
}

Now, I spent perhaps an hour on this sample. As the saying goes, “your mileage may vary”, and your standard concerns about using code from blog posts are warranted. That said, it’s a fairly straight-up port of Jared’s original Java sample, and it works in light testing.

You can create an instance of InputStreamRequestBody and include it in an addFormDataPart() call where the OkHttp recipe shows the one based on the File. So, you wind up with something like this:

val contentPart = InputStreamRequestBody(type, resolver, content)

val requestBody = MultipartBody.Builder()
  .setType(MultipartBody.FORM)
  // TODO add other form elements here
  .addFormDataPart("something", name, contentPart)
  .build()

val request = Request.Builder()
  .url(serverUrl)
  .post(requestBody)
  .build()

ok.newCall(request).execute().use { response ->
  // TODO something with the response
}

Here:

  • type is an OkHttp MediaType identifying the MIME type of the content

  • resolver is a ContentResolver, obtained from a handy Context

  • content is the Uri to the content to upload

  • name is some “filename” for the content

  • serverUrl is the URL you want to POST to

In the sample app, I have a version of that code that uses DocumentFile to obtain the MIME type and name to use. My serverUrl comes from BuildConfig; see the sample app’s README for information about where that URL will need to come from if you clone and try running the sample. The content Uri comes from ACTION_OPEN_DOCUMENT: the user clicks a big “Upload Something!” button, chooses a document, and that document gets uploaded to the server.

If you are using a different HTTP client library, it needs some similar solution if it is going to behave well on modern versions of Android, where filesystem access is constrained and content Uri values are increasingly common.

Many thanks to Jared and Jake for the original implementation!

UPDATE: 2020-07-06: See also this related blog post from cketti. It shows a similar ContentUriRequestBody and also shows the download-to-a-Uri scenario.