Blog
Working with Multipart Form Data using Retrofit

Working with Multipart Form Data using Retrofit

Project
Excerpt
How to send dynamic form data
2nd Posted URL
Action Items
Channel
DEV.TO
DavidAmunga.com
Next Action Date
Feb 17, 2022
Posted URL
Published Date
Feb 17, 2022
Status
Published
Type
Blog
Keywords
Kotlin,Android

Background

In the past few years i’ve been heavily involved in building Android applications especially using Retrofit, the popular open source HTTP Client for Android and Java.
You can send a POST / PUT request by either submitting a body depending on the API Content Type Form Data , Form URL Encoded or using JSON. To submit a JSON object you can use the @SerializedName to specify how to send each field data in the request. However I encountered an interesting challenge when an API Endpoint required pushing Multipart Form Data with an Image. To make it more interesting I was required to submit an uploaded image with the data. I had to figure out how to build the request that accepted data as multipart/formdata and personally found the lack of comprehensive resources on the internet lacking. So i wrote this blogpost to share in my learnings.

Introduction

Lets suppose that we’re trying to send in a survey completed survey to a server in an Android app that accepts a document(image) and some textual data. The Survey Share service takes the following Input :-
  • Name - apiField(”name”)
  • Email - apiField(”email”)
  • Phone Number - apiField(”phoneNumber”)
  • Image - apiField(”image”)
  • Array of Ratings - apiField(”ratings”)
 
We can imagine this data is stored into these data class objects
//SurveyResponse.kt data class SurveyResponse( val name:String, val email:String, val phoneNumber:String, val image:String var ratings:List<Rating> ) //Rating.kt data class Rating( val ratingNameId:Int val ratingScoreId:Int )
 
For us to begin sending this data , we’ll need to create a MutableMap collection that consists of the API Field name as the key and the respective value from the SurveyResponse object. This will look like this.
val map: MutableMap<String, RequestBody> = mutableMapOf()
The RequestBody value will have to be created for every value submitted. We can create an extension function to handle this for us.
// Extensions.kt fun createPartFromString(stringData: String): RequestBody { return stringData.toRequestBody("text/plain".toMediaTypeOrNull()) }
This assumes that all data submitted is plain text object hence the text/plain specification.
 

Submit Text Data

We already know what data to represent the key in the map collection. We can now prepare the map value which has a RequestBody field . This will look like below:
val name = createPartFromString(name) val email = createPartFromString(email) val phoneNumber = createPartFromString(phoneNumber)
Once we are done. We can submit to the map object like below:
map.put("name", name) map.put("email", email) map.put("phoneNumber", phoneNumber)
 

Submit List of Data

Once we are done submitting the simple text data the next hurdle is preparing the map for the List ofratings data specified in each SurveyResponse. A Rating consists of a ratingNameId and a ratingScoreId fields required to be submitted.
 
To prepare the request we have to loop through all the ratings like below:
for((index,rating) in ratings.withIndex()){ .... .... }
We use the Collection extension withIndex() that returns wraps each element of the collection into an IndexedValue that contains the index of that element in the list and the element itself. We can then proceed to prepare the map items as below:
for((index,rating) in ratings.withIndex()){ map["ratings[${index}][ratingNameId]"] = createPartFromString("${rating.ratingNameId}") map["ratings[${index}][ratingScoreId]"] = createPartFromString("${rating.ratingScoreId}") }
To correctly submit the request we prepare the key as a multidimensional array like
ratings[index][apiField] With the index of the collection as the first element followed by the API Field name. The value part is extracted from the individual rating fields as a RequestBody using ourcreatePartFromString method.
 

Sending an Image

For images we can assume that you will save the the Image URI after selecting/taking a picture during the Survey and stored in the imageURI field in the SurveyResponse object. For this we will have to create a separate variable called MultiBody.Part . We can initialize it like below:
var multipartImage: MultipartBody.Part? = null
 
We first extract the file to send from the imageURI field. like below
val fileUri = Uri.parse(surveyResponse.imageUri) String path = getPath(context, uri); if (path != null && isLocal(path)) { return new File(path); } } val file = LocalStorageProvider.getFile(activity, fileUri)
LocalStorageProvider is a popular utility class that helps in getting access to the Phone’s local storage using the Android 4.4 Storage access framework. You can add it to your projects in utils folder and access it here.
Once we’ve extracted the file we can prepare the RequestBody like below.
val requestFile: RequestBody = RequestBody.create( "image/jpg".toMediaType(), file )
We use mediaType image/jpg assuming the image was saved in that format. We can then prepare the multipartImage object we initialized like below:
multipartImage = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
Once we are done we are now ready to build the Retrofit Call
 

Prepare the Retrofit Interface

We can finally prepare the call using our APIService service as below:
interface APIService{ @Multipart @POST("survey") fun postSurveyResponse( @PartMap() partMap: MutableMap<String,RequestBody>, @Part file: MultipartBody.Part ):Response<Unit>
We annotate the call method with @Multipart to denote the Request Body. Doing this we can now submit our data in the parameters as @PartMap and @Part . @PartMap will contain our map of data prepared for the textual data and the ratings list. @Part will denote the request body for the file that is to be sent. You can now finally send the Survey data by calling.
fun sendSurveyResponse(){ // Prepare Survey Response apiService.postSurveyResponse(map,requestFile) }

Conclusion

On completed you will have the capability of sending dynamic pieces of form data specified as multipart/formdata.

Further Reading

Last edited on Thu Dec 22 2022

🗒Hey there !

Was anything I wrote confusing, outdated, or incorrect? Please let me know! Just write a few words below and I'll be sure to amend this post with your suggestions.