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
}
}
Related
I have a progreesBar for uploading with retrofit and I implementation that with some of examples.
my problem is 'WriteTo' function in my custom requestBody class.
This function send progress value for use in my progressBar but this function is called twice. I used debugger and I think some interceptors call WriteTo function.
Let me explain the problem more clearly,When I click Upload button, The number of progress bar reaches one hundred and then it starts again from zero.
Some of the things I did:
I removed HttpLoggingInterceptor.
I used a boolean variable for check that 'writeTo' don't post anything the first time
I don't have any extra interceptors
Also I read this topics:
Retrofit 2 RequestBody writeTo() method called twice
using Retrofit2/okhttp3 upload file,the upload action always performs twice,one fast ,and other slow
Interceptor Problem
My codes:
ProgressRequestBody class
class ProgressRequestBody : RequestBody() {
var mutableLiveData = MutableLiveData<Int>()
lateinit var mFile: File
lateinit var contentType: String
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
override fun contentType(): MediaType? {
return "$contentType/*".toMediaTypeOrNull()
}
#Throws(IOException::class)
override fun contentLength(): Long {
return mFile.length()
}
#Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val fileLength = mFile.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val `in` = FileInputStream(mFile)
var uploaded: Long = 0
`in`.use { `in` ->
var read: Int
while (`in`.read(buffer).also { read = it } != -1) {
val percentage = (100 * uploaded / fileLength).toInt()
mutableLiveData.postValue(percentage)
uploaded += read.toLong()
sink.write(buffer, 0, read)
}
}
}
}
private fun upload(file: File, fileType: FileType) {
val fileBody = ProgressRequestBody()
fileBody.mFile = file
fileBody.contentType = file.name
uploadImageJob = viewModelScope.launch(Dispatchers.IO) {
val body = MultipartBody.Part.createFormData("File", file.name, fileBody)
fileUploadRepo.upload(body).catch {
// ...
}.collect {
when (it) {
// ...
}
}
}
}
In my fragment I use liveData for collect progressBar progress value.
I have the request which works well in postman:
and I'm trying to make it with Retrofit. In general file sizes will be >500MB that. I did such uploading method:
fun uploadFile(file:File) {
val client = OkHttpClient().newBuilder()
.build()
val mediaType: MediaType? = "text/plain".toMediaTypeOrNull()
val body: RequestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(
"data", file.name,
file.asRequestBody()
)
.build()
val request: Request = Request.Builder()
.url("https://..../upload.php")
.method("POST", body)
.build()
val response: okhttp3.Response = client.newCall(request).execute()
println(response.message)
}
but I need to have file for uploading. I can create temporary file with such way:
val path = requireContext().cacheDir
val file = File.createTempFile(
name ?: "",
fileUri.lastPathSegment,
path
)
val os = FileOutputStream(file)
os.write(string)
os.close()
but I usually receive outOfMemoryException. I also added to the AndroidManifest.xml heap param:
android:largeHeap="true"
but it didn't help me at all during temp file creating. I don't know how postman uploads files, but in general I managed to upload with his help file with size about 600Mb. I can also cut selected file with chunks:
val data = result.data
data?.let {
val fileUri = data.data
var name: String? = null
var size: Long? = null
fileUri.let { returnUri ->
contentResolver?.query(returnUri!!, null, null, null, null)
}?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
name = cursor.getString(nameIndex)
size = cursor.getLong(sizeIndex)
}
val inputStream: InputStream? = fileUri?.let { it1 ->
contentResolver.openInputStream(
it1
)
}
val fileData = inputStream?.readBytes()
val mimeType = fileUri.let { returnUri ->
returnUri.let { retUri ->
if (retUri != null) {
contentResolver.getType(retUri)
}
}
}
fileData?.let {
val MAX_SUB_SIZE = 4194304 // 4*1024*1024 == 4MB
var start = 0 // From 0
var end = MAX_SUB_SIZE // To MAX_SUB_SIZE bytes
var subData: ByteArray // 4MB Sized Array
val max = fileData.size
if (max > 0) {
while (end < max) {
subData = fileData.copyOfRange(start, end)
start = end
end += MAX_SUB_SIZE
if (end >= max) {
end = max
}
println("file handling" + subData.size)
}
end-- // To avoid a padded zero
subData = fileData.copyOfRange(start, end)
println("file handling" + subData.size)
}
}
}
all actions will be made in:
private val filesReceiver =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
}
}
so I won't have any file path in normal way. Anyway I think I did something wrong.
UPDATE
right now I have such file uploading from inputStream:
private fun doSomeNetworkStuff(file:InputStream, name:String) {
GlobalScope.launch(Dispatchers.IO) {
val client = OkHttpClient()
.newBuilder()
.protocols(listOf(Protocol.HTTP_1_1))
.connectTimeout(10, TimeUnit.MINUTES)
.readTimeout(10, TimeUnit.MINUTES)
.build()
val mediaType: MediaType? = "text/plain".toMediaTypeOrNull()
val body: RequestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(
"data", name,
file.readBytes().toRequestBody(mediaType)
)
.build()
val request: Request = Request.Builder()
.url("https://.../upload.php")
.method("POST", body)
.build()
val response: Response = client.newCall(request).execute()
println(response.body)
}
}
and receive such error:
java.lang.OutOfMemoryError: Failed to allocate a 173410912 byte allocation with 25165824 free bytes and 89MB until OOM, max allowed footprint 199761800, growth limit 268435456
but I can upload with this code file with size about 90mb
The retrofit multipart stuff has a member that takes an Uri for a request body.
You try to use the one for a File instance.
Have you set log in loggingInterceptor or restadapter ?
if yes then try to set it NONE.
I have a token like this:
hereeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyYWRvciI6eyJpZCI6NzAsIm5vbWUiOiJERUlWSVRJIiwidXN1YXJpbyI6IkRFSVZJVEkifSwiaWF0IjoxNjI5ODEyNDA1fQ.JqzQnFSbG6gFsnlJu3-bezxZ_N5e5FEzc9QvpRGu0u4
hide it:
alg: "HS256",
typ: "JWT"
}.
operador: {
id: 20,
nome: "JOAO",
usuario: "JOAO"
},
iat: 1629812405
}
Question is how do I get on android kotlin only user id to use in certain tasks?
You could use this,
https://github.com/auth0/JWTDecode.Android
Assuming the iat value is the user id,
var jwt: JWT = JWT(YOUR_TOKEN_STRING)
var claim: Claim = jwt.getClaim("iat")
//or as a string
var claim: String = jwt.getClaim("iat").asString()
I just fix the issue thanks to this:
private fun decodeToken(jwt: String): String {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return "Requires SDK 26"
val parts = jwt.split(".")
return try {
val charset = charset("UTF-8")
val header = String(Base64.getUrlDecoder().decode(parts[0].toByteArray(charset)), charset)
val payload = String(Base64.getUrlDecoder().decode(parts[1].toByteArray(charset)), charset)
"$header"
"$payload"
} catch (e: Exception) {
"Error parsing JWT: $e"
}
}
Then :
val mDecode = decodeToken(mToken)
val test = JSONObject(mDecode).getString("operador")
val mDecodeTokenOk = JSONObject(test).getString("id")
/** SALVANDO ID_OPERADOR */
mSharedPreferences.saveString(WmsConstantes.ID_OPERADOR,mDecodeTokenOk)
Log.e("------------------>", mDecodeTokenOk.toString());
You don't have to install any libraries. You can try something like this.
Class(s) reflecting your JWT payload
data class JwtPayload(
#SerializedName("iat")
val iat: Int,
#SerializedName("operador")
val operador: Operador
)
data class Operador(
#SerializedName("id")
val id: Int,
#SerializedName("nome")
val nome: String,
#SerializedName("usuario")
val usuario: String
)
You can use this class as a wrapper for your token
class Jwt(private val token: String) {
private val userData: JsonObject by lazy {
val userData = String(Base64.decode(token.split(".")[1], Base64.DEFAULT), StandardCharsets.UTF_8)
JsonParser.parseString(userData).asJsonObject
}
fun getUserData(): JwtPayload{
gson.toJson(userData, Jwt::class.java)
return gson.fromJson(userData, JwtPayload::class.java)
}
fun isExpired(): Boolean {
return userData.asJsonObject.get("exp").asLong < (System.currentTimeMillis() / 1000)
}
companion object {
#JvmStatic
private val gson = Gson()
}
}
Usage
val token = Jwt("YOUR_TOKEN")
val operatorID = token.operator.id
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>
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)
}