Solution 1
If you like to send your data exactly like the structure you mentioned, you should convert files content to Base64 and wrap them in a serializable class and post it as the body. Here is the sample of wrapper class:
data class AnswerExerciceBase64(val state: String, val medias: List<Media>) : Serializable
data class Media(val file: Base64File) : Serializable
class Base64File(file: File) : Serializable {
val name: String
val content: String
init {
name = file.name
content = Base64.encodeToString(FileInputStream(file).readBytes(), Base64.DEFAULT)
}
}
And your Api is like this:
@POST("api/exercice/{id}")
fun submitExercice(
@Path("id") id: Int,
@Header("Authorization") token: String,
@Body data: AnswerExerciceBase64
): Call<Void>
Then posted data to server will be like below:
{
"state": "this is state",
"medias": [{
"file": {
"content": "Base64 file content",
"name": "f1.txt"
}
}, {
"file": {
"content": "Base64 file content",
"name": "f2.txt"
}
}, {
"file": {
"content": "Base64 file content",
"name": "f3.txt"
}
}]
}
This approach is so close to what you want but you should know you must decode files content on the server-side by yourself, so you need more effort on the server-side.
Solution 2
It's better to use multipart/form-data to upload files and data. Based on "Is it possible to have a nested MultipartEntities or FormBodyPart in a multipart POST?" question and its answer, multipart/form-data has a flat structure and there is no hierarchy, so you can't have desired data structure but you can still pass all of the inputs to Api through a single object.
According to this article, you can send multiple files in a List, so if your Api be like this
@Multipart
@POST("post")
fun submitExercice(@Part data: List<MultipartBody.Part>): Call<ResponseBody>
then you will be able to upload multiple files. You just need to create a List of MultipartBody.Part and add your files to it like below:
list.add(MultipartBody.Part.createFormData(name, fileName, RequestBody.create(mediaType, file)))
Now you must add the state parameter to this list. You can do it like this:
list.add(MultipartBody.Part.createFormData("state", state))
I developed a class that handles all this stuff. You can use it.
class AnswerExerciceList(state: String) : ArrayList<MultipartBody.Part>() {
init {
add(MultipartBody.Part.createFormData("state", state))
}
fun addFile(name: String, fileName: String, mediaType: MediaType?, file: File) {
add(MultipartBody.Part.createFormData(name, fileName,
RequestBody.create(mediaType, file)))
}
}
You can create an instance of this class, add your files and then pass it to the submitExercice Api method as input.
Update
This answer is based on your Api documnetation. I tested my answer and the example that you mentioned in your question via https://postman-echo.com and result was the same. Please try the following code snippet:
Api
@Multipart
@POST("api/exercice/{id}")
fun submitExercice(@Path("id") id: Int,
@Header("Authorization") authorization: String,
@Part("answer") answer: String,
@Part medias: List<MultipartBody.Part>,
@Part("state") state: String): Call<ResponseBody>
Media Class
data class Media(val urlVidel: String, val file: File?, val mediaType: MediaType?) {
companion object {
fun mediaListToMultipart(mediaList: List<Media>): List<MultipartBody.Part> {
val list = ArrayList<MultipartBody.Part>()
for (i in mediaList.indices) {
mediaList[i].let {
if (!TextUtils.isEmpty(it.urlVidel))
list.add(MultipartBody.Part.createFormData("medias[$i][urlVideo]", it.urlVidel))
if (it.file != null) {
val requestFile = RequestBody.create(
it.mediaType,
it.file
)
list.add(MultipartBody.Part.createFormData("medias[$i][file]", it.file.getName(), requestFile))
}
}
}
return list
}
}
}
and then call Api like this:
ApiHelper.Instance.submitExercice(1, "Authorization Token", "Answer", Media.mediaListToMultipart(mediaList), "State").enqueue(callback)