Upload image from Android to Server that accepts only JPEG and PNG - android

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)
}

Related

Uploading multiple files with Retrofit 2 to Spring Webflux backend

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.

Upload image to Graphql server using apollo kotlin

I need to upload image to my Graphql server from android application. The details in the documentation is not working. I need an example.
Came up with the solution. 1st I needed to create an upload scalar type. in Fragment class:
requireContext().contentResolver.openFileDescriptor(
selectedImageUri!!,
"r",
null
) ?: return
val file = File(
requireContext().cacheDir, requireContext().contentResolver.getFileName(
selectedImageUri
)
)
val body = UploadRequestBody(file, "image")
val upload = DefaultUpload.Builder()
.content(file)
.fileName(file.name)
.contentType(body.contentType().toString())
.build()
In case what the UploadRequestBody class does:
class UploadRequestBody(
private val file: File,
private val contentType: String
) : RequestBody() {
override fun contentType() = "$contentType/*".toMediaTypeOrNull()
override fun contentLength() = file.length()
override fun writeTo(sink: BufferedSink) {
val length = file.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val fileInputStream = FileInputStream(file)
var uploaded = 0L
fileInputStream.use { inputStream ->
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
uploaded += read
sink.write(buffer, 0, read)
}
}
}
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
}

How I can upload selected file retrofit from onActivityResult?

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>>

Receive error in onSuccessListner in when trying to upload an image to the drive

I have created a function to upload images to drive in kotlin. It is as follow.
DriveServiceHelper class
class DriveServiceHelper(private val mDriveService: Drive) {
private val mExecutor: Executor =
Executors.newSingleThreadExecutor()
private val TAG = "DRIVE_TAG"
fun uploadFile(
localFile: java.io.File,
mimeType: String?, folderId: String?
): Any? {
return Tasks.call(mExecutor, Callable<Any> { // Retrieve the metadata as a File object.
val root: List<String>
root = folderId?.let { listOf(it) } ?: listOf("root")
val metadata =
File()
.setParents(root)
.setMimeType(mimeType)
.setName(localFile.name)
val fileContent = FileContent(mimeType, localFile)
val fileMeta =
mDriveService.files().create(
metadata,
fileContent
).execute()
val googleDriveFileHolder = GoogleDriveFileHolder()
googleDriveFileHolder.id=(fileMeta.id)
googleDriveFileHolder.name=(fileMeta.name)
googleDriveFileHolder
})
}
In my activity i call it as follows.
var mDriveServiceHelper: DriveServiceHelper? = null
private fun driveSetUp() {
val mAccount =
GoogleSignIn.getLastSignedInAccount(this)
val credential = GoogleAccountCredential.usingOAuth2(
applicationContext, setOf(Scopes.DRIVE_FILE)
)
credential.selectedAccount = mAccount!!.account
googleDriveService = Drive.Builder(
AndroidHttp.newCompatibleTransport(),
GsonFactory(),
credential
)
.setApplicationName("GoogleDriveIntegration 3")
.build()
mDriveServiceHelper = DriveServiceHelper(googleDriveService)
}
private fun uploadImageIntoDrive() {
driveSetUp()
val TAG = "image upload"
val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, arrayList[0].uri)
try {
if (bitmap == null) {
Log.i(TAG, "Bitmap is null")
return
}
val file =
File(applicationContext.filesDir, "FirstFile")
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos)
val bitmapdata = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(file)
fos.write(bitmapdata)
fos.flush()
fos.close()
mDriveServiceHelper!!.uploadFile(file, "image/jpeg", null)
.addOnSuccessListener(OnSuccessListener<GoogleDriveFileHolder> { googleDriveFileHolder ->
Log.i(
TAG,
"Successfully Uploaded. File Id :" + googleDriveFileHolder.id)
})
.addOnFailureListener(OnFailureListener { e ->
Log.i(
TAG,
"Failed to Upload. File Id :" + e.message
)
})
} catch (e: Exception) {
Log.i(TAG, "Exception : " + e.message)
}
}
But the problem is in the uploadImageTodrive() function addOnSuccessListener is displayed in red color and says Unresolved Reference: addOnSuccessListener.
Please help
Your helper class returns type Any?:
fun uploadFile(
localFile: java.io.File,
mimeType: String?, folderId: String?
): Any? { ... // Look at the return type
But it actually should return Task<GoogleDriveFileHolder>:
fun uploadFile(
localFile: java.io.File,
mimeType: String?, folderId: String?
): Task<GoogleDriveFileHolder> { ... // Non-optional Task<GoogleDriveFileHolder>

Android: JSON object getString

I use this JSON https://api.github.com/users
I need to get string name, followers, following, and more. But on the program says "No value for name". I think I need to go to a specific user example: https://api.github.com/users/mojombo to getting that info, but I don't know-how.
And I using loopj library.
Here's My Code
private fun getDataGitDetail() {
progressBar.visibility = View.VISIBLE
val client = AsyncHttpClient()
client.addHeader("Authorization", "token 6fe9dff2e5e43d25eb3abe9ff508a750b972f725")
client.addHeader("User-Agent", "request")
val url = "https://api.github.com/users"
client.get(url, object : AsyncHttpResponseHandler() {
override fun onSuccess(
statusCode: Int,
headers: Array<Header>,
responseBody: ByteArray
) {
progressBar.visibility = View.INVISIBLE
val result = String(responseBody)
Log.d(TAG, result)
try {
val jsonArray = JSONArray(result)
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val username: String? = jsonObject.getString("login")
val name: String? = jsonObject.getString("name")
val avatar: String? = jsonObject.getString("avatar_url")
val company: String? = jsonObject.getString("url")
val location: String? = jsonObject.getString("url")
val repository: Int = 0
val followers: Int = 0
val following: Int = 0
listData.add(
Data(
username,
name,
avatar,
company,
location,
repository,
followers,
following
)
)
}
showRecyclerList()
} catch (e: Exception) {
Toast.makeText(this#MainActivity, e.message, Toast.LENGTH_SHORT)
.show()
e.printStackTrace()
}
}
override fun onFailure(
statusCode: Int,
headers: Array<Header>,
responseBody: ByteArray,
error: Throwable
) {
progressBar.visibility = View.INVISIBLE
val errorMessage = when (statusCode) {
401 -> "$statusCode : Bad Request"
403 -> "$statusCode : Forbidden"
404 -> "$statusCode : Not Found"
else -> "$statusCode : ${error.message}"
}
Toast.makeText(this#MainActivity, errorMessage, Toast.LENGTH_LONG)
.show()
}
})
}
The current response you are getting does not contain a key name in the JSONObject.
If you want the Name of all the users you will have to go to each users endpoint in the api. You'll need to make another request inside your for loop that gets datafrom an endpoint like https://api.github.com/users/mojombo
val jsonArray = JSONArray(result)
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val username: String? = jsonObject.getString("login")
//Make the request here using "https://api.github.com/users/" + login
You can then choose to get the rest of the data from either the first response or the 2nd one as both contain that information.
I hope this helps.
No need for a JSON array, cz API https://api.github.com/users/mojombo is JSON Object.
Example:
client.get(url, object : AsyncHttpResponseHandler() {
override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody: ByteArray) {
try {
//parsing json
val result = String(responseBody)
val responseObject = JSONObject(result)
textView2.text = responseObject.getString("login")
textView3.text = responseObject.getString("name")
textView9.text = responseObject.getString("location")
desc.text = responseObject.getString("company")
view?.let { Glide.with(it).load(responseObject.getString("avatar_url")).into(imageView2) }
} catch (e: Exception) {
Log.d("Exception", e.message.toString())
}
}
}

Categories

Resources