I'm trying to upload file to remote server via retrofit. In Postman I did it in such way:
I did such interface method:
#Multipart
#POST("user/upload")
fun uploadFile(#Part("upload_doc") file: RequestBody): Call<EditResponse>
and I'm going to send selected file from onActivityResult:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == AppCompatActivity.RESULT_OK) {
if (data != null) {
val file = File(getRealPathFromURI(data.data!!)!!)
if (file.exists()) {
val uploadFile = MultipartBody.Part.createFormData("upload_doc", file.name, file.asRequestBody())
val requestBody: RequestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(uploadFile)
.build()
}
}
}
}
The problem is connected with getting file path. As I think you know this is a problem. I tried to use this method:
fun getFile(documentUri: Uri): File {
val inputStream = context?.contentResolver?.openInputStream(documentUri)
var file: File
inputStream.use { input ->
file =
File(context?.cacheDir, System.currentTimeMillis().toString() + ".pdf")
FileOutputStream(file).use { output ->
val buffer =
ByteArray(4 * 1024) // or other buffer size
var read: Int = -1
while (input?.read(buffer).also {
if (it != null) {
read = it
}
} != -1) {
output.write(buffer, 0, read)
}
output.flush()
}
}
return file
}
But the server returned me that I had sent wrong file type. So I think that I have two problems:
Getting bad file path
Sending normal multipart file for all file types not only sending pdf
I think maybe some problems are connected with this file converting line:
val uploadFile = MultipartBody.Part.createFormData("upload_doc", file.name, file.asRequestBody())
Maybe someone knows how to solve this problem?
Change your code as below :
fun updateProfile(
image: File?,
firstName: String?,
userName: String)
{
var picture: MultipartBody.Part? = null
try {
val requestFile: RequestBody? =
image!!.asRequestBody("image/*".toMediaTypeOrNull())
picture = MultipartBody.Part.createFormData("picture",
image!!.name, requestFile!!)
} catch (ex: Exception) {
}
val userName: RequestBody = userName
.toRequestBody(MultipartBody.FORM)
val name: RequestBody = firstName!!
.toRequestBody(MultipartBody.FORM)
File file = new File(mediaPath);
File file1 = new File(mediaPath1);
// Parsing any Media type file
RequestBody requestBody1 =
RequestBody.create(MediaType.parse("*/*"),file);
RequestBody requestBody2 =
RequestBody.create(MediaType.parse("*/*"),file1);
MultipartBody.Part fileToUpload1 =
MultipartBody.Part.createFormData("file1", file.getName(),
requestBody1);
MultipartBody.Part fileToUpload2 =
MultipartBody.Part.createFormData("file2", file1.getName(),
requestBody2);
postProfile(image,name,username,fileToUpload1 ,fileToUpload2)
}
and change your retrofit Call to this :
#Headers("Accept: application/json")
#Multipart
#POST(yourEndPoint)
fun postProfile(
#Part picture: MultipartBody.Part?,
#Part("name") name: RequestBody?,
#Part("user_name") user_name: RequestBody,
#Part filea: MultipartBody.Part?,
#Part fileb: MultipartBody.Part?
): Deferred<Response<yourClass>>
Related
I get the following error when I try to upload multiple files with retrofit 2:
org.springframework.core.codec.DecodingException: Could not find first boundary
When I upload multiple files with postman on the same API endpoint, it works perfectly.
Server controller endpoint:
#PostMapping("{loveSpotId}/photos")
suspend fun uploadToSpot(
#PathVariable loveSpotId: Long,
#RequestPart("photos") filePartFlux: Flux<FilePart>
) {
loveSpotPhotoService.uploadToSpot(loveSpotId, filePartFlux.asFlow())
}
Retrofit API definition:
interface LoveSpotPhotoApi {
#Multipart
#POST("/lovespots/{loveSpotId}/photos")
fun uploadToLoveSpot(
#Path("loveSpotId") loveSpotId: Long,
#Part photos: List<MultipartBody.Part>
): Call<ResponseBody>
// ...
}
Reading photos on Android device:
if (activityResult.resultCode == Activity.RESULT_OK) {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
files.add(File(uri.path!!))
}
loveSpotPhotoService.uploadToLoveSpot(loveSpotId, files, this#LoveSpotDetailsActivity)
}
Client code using Retrofit:
suspend fun uploadToLoveSpot(loveSpotId: Long, photos: List<File>, activity: Activity) {
val loadingBarShower = LoadingBarShower(activity).show()
withContext(Dispatchers.IO) {
val parts: List<MultipartBody.Part> = photos.map { prepareFilePart(it) }
val call = loveSpotPhotoApi.uploadToLoveSpot(loveSpotId, parts)
try {
val response = call.execute()
loadingBarShower.onResponse()
if (response.isSuccessful) {
toaster.showToast(R.string.photo_uploaded_succesfully)
} else {
toaster.showResponseError(response)
}
} catch (e: Exception) {
loadingBarShower.onResponse()
toaster.showToast(R.string.photo_upload_failed)
}
}
}
private fun prepareFilePart(file: File): MultipartBody.Part {
// create RequestBody instance from file
val requestFile = RequestBody.create(
MediaType.get("image/*"),
file
)
// MultipartBody.Part is used to send also the actual file name
return MultipartBody.Part.createFormData("photos", file.name, requestFile)
}
Example headers logged on server when I upload with postman and it works:
[Authorization:"Bearer ...", User-Agent:"PostmanRuntime/7.30.0", Accept:"/", Postman-Token:"7ad875eb-2fe5-40ea-99f0-3ad34c3fa875", Host:"localhost:8090", Accept-Encoding:"gzip, deflate, br", Connection:"keep-alive", Content-Type:"multipart/form-data; boundary=--------------------------877409032202838734061440", content-length:"1555045"]
Example headers logged on server when I upload with retrofit client and it fails:
[Authorization:"Bearer ...", Content-Type:"multipart/form-data; boundary=c6177139-6b31-4d91-b66d-54772a51d963", Host:"192.168.0.143:8090", Connection:"Keep-Alive", Accept-Encoding:"gzip", User-Agent:"okhttp/3.14.9", content-length:"528"]
The problem was that I was not reading photos from the Android device properly. Here is my code that fixed that:
launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
handlePhotoPickerResult(activityResult)
}
fun startPickerIntent(launcher: ActivityResultLauncher<Intent>) {
val intent = Intent()
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_PICK
launcher.launch(intent)
}
private fun handlePhotoPickerResult(activityResult: ActivityResult) {
MainScope().launch {
if (activityResult.resultCode == RESULT_OK) {
val files = PhotoUploadUtils.readResultToFiles(activityResult, contentResolver)
Log.i(this#LoveSpotDetailsActivity::class.simpleName, "Starting upload")
val result: Boolean = if (photoUploadReviewId != null) {
loveSpotPhotoService.uploadToReview(
loveSpotId,
photoUploadReviewId!!,
files,
this#LoveSpotDetailsActivity
)
} else {
loveSpotPhotoService.uploadToLoveSpot(
loveSpotId,
files,
this#LoveSpotDetailsActivity
)
}
photosLoaded = !result
photosRefreshed = !result
photoUploadReviewId = null
if (result) {
Log.i(
this#LoveSpotDetailsActivity::class.simpleName,
"Upload finished, starting refreshing views."
)
startPhotoRefreshSequence()
}
} else {
toaster.showToast(R.string.failed_to_access_photos)
}
}
}
fun readResultToFiles(
activityResult: ActivityResult,
contentResolver: ContentResolver
): List<File> {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
Log.i("uri", "$uri")
addToFilesFromUri(uri, files, contentResolver)
}
return files
}
private fun addToFilesFromUri(
uri: Uri,
files: ArrayList<File>,
contentResolver: ContentResolver
) {
val projection = arrayOf(MediaStore.MediaColumns.DATA)
contentResolver.query(uri, projection, null, null, null)
?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
Log.i("columnIndex", "$columnIndex")
val filePath = cursor.getString(columnIndex)
Log.i("filePath", " $filePath")
if (filePath != null) {
val file = File(filePath)
Log.i("file", "$file")
files.add(file)
}
}
}
}
Rest of the code was fine.
I am stuck in between a strange issue of uploading image file to server. Although I did upload file several times before, but this time I don't understand what is the issue.
I get the file path of respective file but RequestBody returns null. Below I mentioned what library I'm using.
I am using kotlin, MultiPart and RequestBody for file upload.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
Below is my code which I wrote for file upload. In which you can see GalleryCameraUtility.getImageRequestBody(imageFile) returns null for file.
File path from mobile device /storage/emulated/0/DCIM/Screenshots/test_image.jpg
fun addNewCompany(companyName: String, email: String,imageFile: File, ownerName: String, address: String, companyDetails: String){
val companyNameBody: RequestBody = companyName.toRequestBody("text/plain".toMediaType())
val emailBody: RequestBody = email.toRequestBody("text/plain".toMediaType())
val fileData: RequestBody? = GalleryCameraUtility.getImageRequestBody(imageFile)
val ownerNameBody: RequestBody = ownerName.toRequestBody("text/plain".toMediaType())
val addressBody: RequestBody = address.toRequestBody("text/plain".toMediaType())
val userIdBody: RequestBody = PreferenceHelper.readUserIdPref(Constants.USER_ID).toString()
.toRequestBody("text/plain".toMediaType())
addCompanyRepo.addNewCompanyApi(companyNameBody, emailBody, fileData, ownerNameBody, addressBody, userIdBody)
}
class GalleryCameraUtility {
companion object{
fun getImageRequestBody(sourceFile: File) : RequestBody? {
var requestBody: RequestBody? = null
Thread {
val mimeType = getMimeType(sourceFile);
if (mimeType == null) {
Log.e("file error", "Not able to get mime type")
return#Thread
}
try {
requestBody = sourceFile.path.toRequestBody("multipart/form-data".toMediaTypeOrNull())
/*MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(serverImageKey, uploadedFileName,sourceFile.asRequestBody(mimeType.toMediaTypeOrNull()))
.build()*/
} catch (ex: Exception) {
ex.printStackTrace()
Log.e("File upload", "failed")
}
}.start()
return requestBody;
}
// url = file path or whatever suitable URL you want.
private fun getMimeType(file: File): String? {
var type: String? = null
val extension = MimeTypeMap.getFileExtensionFromUrl(file.path)
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
return type
}
}
}
I spent so many hours on this but not able to find solution. Please help me out on this.
MediaStore.Images.Media.Data is deprecated so I tried using byte array, but my server throws an error that it accepts only file types of jpeg and png. I have read #commonsware's answers a couple of times from other questions but I can't seem to get it right. How do I get the file type and also the image so it can be attached to my network call? Thanks in advance
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK && requestCode == PROFILE_REQUEST_CODE) {
val uri = data?.data
createImageData(uri)
profile_image.setImageURI(data?.data)
}
}
private fun createImageData(uri: Uri?) {
val inputStream = activity?.contentResolver?.openInputStream(uri!!)
processImage(inputStream)
}
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageData!!)
val image: MultipartBody.Part = MultipartBody.Part.createFormData("image", "user", imageBody)
uploadImage(image)
}
private fun uploadImage(image: MultipartBody.Part) {
val user = appPreferences?.getUser()
val userToken = user?.jwt_token
val token = "Bearer $userToken"
val headers = HashMap<String, String>()
//headers["Authorization"] = token
val uploadResponseCall: Call<ProfileImageResponse> = client.getApi().upload(token, image)
uploadResponseCall.enqueue(object : retrofit2.Callback<ProfileImageResponse> {
override fun onResponse(call: Call<ProfileImageResponse>, response: Response<ProfileImageResponse>) {
val imageResponse = response.body()
val resCode = imageResponse?.statuscode
val msg = imageResponse?.message
if (resCode == 200) {
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, msg!!)
} else {
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, "wrong file type")
}
}
override fun onFailure(call: Call<ProfileImageResponse>, t: Throwable) {
if (t is IOException) {
call.cancel()
Log.d("profilefragment", "issue")
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, "server error")
}
}
})
}
Here is example
#Multipart
#POST(ApiConstants.SIGNUP)
fun signUp(
#Query("first_name") firstName: String,
#Query("last_name") lastName: String,
#Query("email") email: String,
#Query("password") password: String,
#Query("gender") gender: String,
#Query("phone_code") phoneCode: String,
#Query("phone_no") phoneNo: String,
#Part("id_card_front\"; filename=\"pp.png") id_card_front: RequestBody?
) : Observable<SignUpModel>
Here is usage
var image = RequestBody.create(
MediaType.parse("image/*"),
getFileFromBitmap((imageview.drawable as BitmapDrawable).bitmap, 13)
)
disposable = retroClient.signUp(
fname,
lname,
email,
password,
gender,
phoneCode,
phone,
image,
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result ->
Log.d("response", result.toString())
}, { error ->
Log.e("error", error.toString())
})
the method is used to get file from bitmap
fun getFileFromBitmap(bitmap: Bitmap, index: Int): File {
val f = File(cacheDir, "something+$index")
f.createNewFile()
val bitmap = bitmap
bitmap
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 25 /*ignored for PNG*/, bos)
val bitmapdata = bos.toByteArray()
val fos = FileOutputStream(f)
fos.write(bitmapdata)
fos.flush()
fos.close()
return f
}
Try to add an appropriate Content-Type header. image/png or image/jpg should fit. Also, it possibly, should not be the multipart request. So you'll need such kind of request.
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = imageData!.toRequestBody()
uploadImage(imageBody)
}
or if it's really should be multipart/form-data, you should correctly set mime type for the image part like this:
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = RequestBody.create(MediaType.parse("image/png"), imageData!!)
val image: MultipartBody.Part = MultipartBody.Part.createFormData("image", "user", imageBody)
uploadImage(image)
}
Let me share some of my code which i have implemented to send image file in the request.
Below is my function of api request:
#Multipart
#POST("api/order/order_create")
fun createOrder(
#Header("Authorization") authorization: String?,
#Part("category_id") categoryId: RequestBody?,
#Part("size") size: RequestBody?,
#Part("narration") narration: RequestBody?,
#Part("ref_picture") file: RequestBody?
): Call<OrderCreateResponse>
Below is the code where i am calling the api by sending the necessary parameters:
var fbody = RequestBody.create(MediaType.parse("image/*"), imageFile)
var size = RequestBody.create(MediaType.parse("text/plain"), et_custom_order_size.text.toString())
var catId = RequestBody.create(MediaType.parse("text/plain"), selectedID.toString())
var narration = RequestBody.create(MediaType.parse("text/plain"),et_custom_order_narration.text.toString())
val orderCreateAPI = apiService!!.createOrder(complexPreferences?.getPref("token", null), catId,size,narration,fbody)
Here imageFile is fetched by the below way,
imageFile = File(Global.getRealPathFromURI(activity!!, imageUri!!))
Using below function to get the real path,
fun getRealPathFromURI(context: Context, contentUri: Uri): String {
var cursor: Cursor? = null
try {
val proj = arrayOf(MediaStore.Images.Media.DATA)
cursor = context.contentResolver.query(contentUri, proj, null, null, null)
val column_index = cursor!!.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
cursor.moveToFirst()
return cursor.getString(column_index)
} catch (e: Exception) {
Log.e(TAG, "getRealPathFromURI Exception : " + e.toString())
return ""
} finally {
if (cursor != null) {
cursor.close()
}
}
}
By sending image in the above way, i am not able to send it! Please guide me with the same.
Thanks in advance.
#Multipart
#POST("register")
Observable<SignInResponse> signUp(#Part("name") RequestBody name, #Part MultipartBody.Part fileToUpload);
Then pass image file as MultipartBody.Part variable
// image as file
var body: MultipartBody.Part? = null
if (!profileImagePath.isNullOrBlank()) {
val file = File(profileImagePath)
val inputStream = contentResolver.openInputStream(Uri.fromFile(file))
val requestFile = RequestBody.create(MediaType.parse("image/jpeg"), getBytes(inputStream))
body = MultipartBody.Part.createFormData("image", file.name, requestFile)
Log.d("nama file e cuk", file.name)
}
Last thing you can make RequestBody var
RequestBody.create(MediaType.parse("text/plain"), user_full_name)
finally send request :)
Try changing
#Part("ref_picture") file: RequestBody?
to
#Part("ref_picture") file: MultipartBody.Part?
And do this
// create RequestBody instance from file
RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)),file);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body = MultipartBody.Part.createFormData("picture", file.getName(), requestFile);
You may also check this answer
https://stackoverflow.com/a/34562971/8401371
You can do this like this way :
var propertyImagePart: MultipartBody.Part? = null
imageUrl.value?.let {
val propertyImageFile = File(FILE_PATH)
val propertyImage: RequestBody = RequestBody.create(MediaType.parse("image/*"), propertyImageFile)
propertyImagePart =MultipartBody.Part.createFormData("userImage", propertyImageFile.name, propertyImage)
}
job = launch {
try {
val response = apiServiceWithoutHeader.doUpdateProfile(propertyImagePart,profileRequest.getMultipart()).await()
stateLiveData.postValue(UserProfileState.SuccessUpdateProfile(response))
} catch (e: JsonSyntaxException) {
onException(e)
} catch (e: JsonParseException) {
onException(e)
} catch (e: IOException) {
onException(e)
} catch (e: HttpException) {
onException(e)
}
}
Let's say I have this model:
data class PhotoRequest(
#SerializedName("page_number")
val pageNumber: Int,
#SerializedName("image")
val requestBody: MultipartBody.Part
)
The multipart is created using this:
val photo = File(picturePath)
val requestFile = RequestBody.create(
MediaType.parse("image/jpeg"),
photo
)
return MultipartBody.Part.createFormData("images", photo.name, requestFile)
The Retrofit builder is:
val builder = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
And this is the Retrofit interface:
#Multipart
#POST("my_endpoint")
fun sendExample(
#Part("name") name: String,
#Part("email") email: String,
#Part("images[]") images: List<PhotoRequest>
): Single<String>
What I'm trying to accomplish is to send multiples images along with a param per image, in this case, the number of the page.
The server (rails) is not recognizing the image in any request, in fact, the content inside the key "images" is received as an array of strings.
"images"=>["{\"page_number\":1,\"image\":{\"headers\":{\"namesAndValues\":[\"Content-Disposition\",\"form-data; name=\\\"image\\\"; filename=\\\"1537970501549.jpg\\\"\"]]}}}"]
Does anyone know how to properly submit this request? or how to manually process the images on the rails side?
That's how you declare in your api interface. You don't need part name for list of images.
#Multipart
#POST("my_endpoint")
fun sendExample(
#Part("name") name: String,
#Part("email") email: String,
#Part images: List<MultipartBody.Part>
): Single<String>
then wherever you trying to send the request add this method and create list of multipartBody.part
fun prepareFilePart(partName: String, file: File): MultipartBody.Part {
val requestFile = RequestBody.create(MediaType.parse("image/png"), file)
return MultipartBody.Part.createFormData(partName, file.name, requestFile)
}
val listOfImages = ArrayList<MultipartBody.Part>()
for (i in 0 until images.size) {
listOfImages.add(prepareFilePart("image[$i]", images[i]))
}
then you can send the request like that:
sendExample("some name", "email#ru.org", listOfImages)
Opening an Gallery intent for multiple images
public void choosePhotoFromGallary() {
Intent galleryIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(galleryIntent, GALLERY);
}
On receiving the result of Gallery intent for multiple images
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == GALLERY && resultCode == RESULT_OK && data != null) {
Uri uri = null;
//**if Data have multiple images **
if (data.getClipData() != null) {
for (int index = 0; index < data.getClipData().getItemCount(); index++) {
// Getting the URIs of the selected files and logging them into logcat at debug level
uri = data.getClipData().getItemAt(index).getUri();
Log.e("mutlifilesUri [" + uri + "] : ", String.valueOf(uri));
file = new File(new FileUtils(getContext()).getPath(fileUri));
arrayList.add(fileUri);
}
} else {
uri = data.getData();
file = new File(new FileUtils(getContext()).getPath(fileUri));
arrayList.add(fileUri); //add the Uri in a array list
}
}
}
Convert multiple images into multipartBody for send into service side
public List < MultipartBody.Part > getMultiAttachment() {
if (arrayList != null) {
// create part for file (photo, video, ...)
for (int i = 0; i < arrayList.size(); i++) {
parts.add(prepareFilePart("multifileAttachment[" + i + "]", arrayList.get(i)));
}
}
return parts;
}
#NonNull
private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) {
// use the FileUtils to get the actual file by uri
File file = new File(new FileUtils(getContext()).getPath(fileUri));
// create RequestBody instance from file
RequestBody requestFile = RequestBody.create(MediaType.parse("*/*"), file);
// MultipartBody.Part is used to send also the actual file name
return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}
#Multipart
#POST("api_end_point")
Call < ApiResponse > hitEmailApi(#Part List < MultipartBody.Part > getMultiAttachment());