How to upload image to server with Retrofit on Android - android

In my application I should upload image to server.
For server requests I used retrofit 2
I write upload codes, but show me error for validation and say me media field is empty.
Upload image request from PostMan : Click to see image
In postman everything is ok and not any problem and image upload successfully!
But in my code show me validation error!
My api interface code :
#Multipart
#POST("media/")
fun uploadImage(
#Header(AUTHORIZATION) auth: String, #Header(ACCEPT) accept: String, #Header(CONTENT_TYPE) contentType: String,
#Part media: MultipartBody.Part
): Single<Response<ResponseModelUploadImage>>
Upload image code :
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
photoEasy.onActivityResult(
requestCode, resultCode
) { thumbnail ->
val imgFile = bitmapToFile(thumbnail, "myImageNameIsThisTest.jpeg")
Log.e("filePath",imgFile.toString())
val reqFile = RequestBody.create(MediaType.parse("multipart/form-data"), imgFile)
val filePart = MultipartBody.Part.createFormData("media", imgFile?.name, reqFile)
presenter.callUploadImage(userToken, APPLICATION_JSON, APPLICATION_JSON, filePart)
}
}
How can I fix it?
URL

This is an example of an upload function in an Android app (in Kotlin) that sends a picture to a server (in this case is a web application developed with Java and Spring Boot) with Retrofit as multipart/form-data:
private suspend fun sendPicture(sessionId: UUID, p: Picture): Boolean {
try {
val data_part = p.data.toRequestBody("multipart/form-data".toMediaTypeOrNull())
val data_multi_part =
MultipartBody.Part.createFormData("picture", p.description, data_part)
val sessionId_part =
sessionId.toString().toRequestBody("multipart/form-data".toMediaTypeOrNull())
val id_part = p.id.toString().toRequestBody("multipart/form-data".toMediaTypeOrNull())
val categoryId_part =
p.categoryId?.toString()?.toRequestBody("multipart/form-data".toMediaTypeOrNull())
val response = api.sendPicture(sessionId_part, id_part, categoryId_part, data_multi_part)
if (!response.isSuccessful) {
#Suppress("BlockingMethodInNonBlockingContext")
val msg = response.errorBody()?.string() ?: textHelper(
R.string.error_send_picture,
p.id.toString()
)
Logger.trace(EventCode.SerializationError, msg, EventSeverity.Error)
}
return response.isSuccessful
} catch (e: Exception) {
Logger.trace(p, EventCode.SerializationError, e.localizedMessage, Action.Send, EventSeverity.Exception)
}
return false
}
And the generated API that uses Retrofit 2.6 is declared as follows:
#Multipart
#POST("SendPicture")
suspend fun sendPicture(#Part("sessionId") sessionId: RequestBody, #Part("pictureId") id: RequestBody, #Part("categoryId") categoryId: RequestBody?, #Part picture: MultipartBody.Part): Response<Void>
Take into account that Picture is an Android Room (ORM) entity and p.data it's the image representation as a byte array (val data: ByteArray).
sessionId = is a session ID (it's just a custom value of type UUID)
pictureId = is the ID of the picture (type: UUID)
categoryId = is the ID of the category the picture belongs to (type: UUID)
These 3 values are custom (you don't need them), they are there just to show how to pass more data to the server together with the image.
The function is written in Kotlin.
I think you have everything you need to extract the code that better suits your needs. This example is taken from the code of a project of mine and it was tested. It sends pictures stored in the local database to the server.
You can easily port the code to Java.

Related

Simple method of calling an API from Kotlin

In my quest to learn Kotlin I am trying to write an Android app that will take 3 photos and upload them in binary format to my database via an API.
Thanks to the help of people here and the Google documentation, I have been able to get my 3 photos and display a nice thumbnail image for each one. I have also worked out how to save the image to a physical file. I don't want to physically save the file first (not least because I'd still have to upload it to the database somehow). What I'm struggling with though is (a) converting the file to binary and (b) calling an API.
I get the file(s) into a variable using:
val imageBitmap = data?.extras?.get("data") as Bitmap
This is within:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
So, at this point I have a variable (imageBitmap) which is holding the photo. My API;
http://api.domain.com/api/photos?key={APIkey}&ipaddress=1.1.1.1&activity=new&imgdate=2021-05-04&img=x0x0x0x0x0
is structured as above (img is the aspect that I need to pass the photo image through). Is it as simple as:
urlString = "http://api.domain.com/api/photos?key={APIkey}&ipaddress=1.1.1.1&activity=new&imgdate=2021-05-04&img=" + imageBitmap
Would the above not be invalidly trying to concatenate a string and an object?
The API is written in C# through Visual Studio and calls a stored proc in my database. The img parameter in the proc is varbinary(max) so base64 encoded.
I found a lot of references to using OKHttp3, but I can't find an example that is clear enough for a beginner. What I was envisaging was that I'd call a function that encompasses the call and returns the json response. I took the code below from an example, and whilst it doesn't error, it doesn't make a call either:
fun doAPICall(url: String, body: String): String {
val apiClient = OKHttpClient()
var resp: String = ""
val request = Request.Builder().url(url).build()
//Define the body if present
apiClient.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {
resp = response.body()?.string()!!
}
})
}
So, I have two problems that I'm struggling to understand here. Firstly how do I convert imageBitmap into a binary string that I can pass in the API as a part of the url parameter, and secondly, how do I call the API and get the response back?
I tried a very simple example to see if the code worked by just calling an API and trying to write the response value to a TextView:
txtResponse.text = doAPICall("http://api.domain.com/api/photos?key={APIkey}&ipaddress=1.1.1.1&activity=getName", "")
This doesn't throw an error, but doesn't put the response into the textview either. The response for this should be:
{
"APIResult": [
{
"id": 200,
"status_message": "Success",
"name": "Testing"
}
]
}
Can someone provide some pointers for me please?

Special characters are encoded in POST request in android. How to avoid this?

I am calling a post API with some parameters. one the parameters is
activatecode="aaaaaa$rr"
when the API call is made, it is sent as
activatecode=aaaaaa%24rr
The $ is encoded as %24. How to avoid this and send the special character as it is?
I am using Retrofit 2.9.0.
I have this service :
interface WordpressService {
#GET("wp-json/wp/v2/posts")
suspend fun getPosts(
#Query("page") page: Int,
#Query("per_page") limit: Int,
#Query("_fields", encoded = true) fields: String = "date,link,title,content,excerpt,author"
): List<Post>
}
Without putting encode = true, I end up with this request :
GET http://example.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date%2Clink%2Ctitle%2Ccontent%2Cexcerpt%2Cauthor
With encode = true, I get :
GET http://motspirituel.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date,link,title,content,excerpt,author
So in my case, adding encode = true in annotation solved my problem.

Uploading a file in an array of object using Retrofit 2

I use Retrofit2 and I need to upload various files using file in an array of objects Media like this :
{
"state" = "done",
"medias" = [
{
"file" = THE_FILE1
},
{
"file" = THE_FILE2
},
{
"file" = THE_FILE3
}
]
}
This is the function of my Interface :
#Multipart
#POST("api/exercice/{id}")
fun submitExercice(
#Path("id") id: Int,
#Header("Authorization") token: String,
#Body data: AnswerExercice
): Call<Void>
And this is my object Media :
data class AnswerExercice(
val state: String = "done",
val medias: List<Media>
) : Serializable {
data class Media(
#Part val file: MultipartBody.Part
) : Serializable
}
But I have this error :
#Body parameters cannot be used with form or multi-part encoding.
(parameter #3)
What am i not doing well?
This is what the API documentation say :
The result have to be like this :
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)

How insert image in room persistence library?

I am using room persistence library for my android application, Now I have to insert image in my db. I successfully define #Entity for the primitive data type. and also through converter class, i stored all object, date, time. Now I have to store Image. I am not able to understand how we define Column info and entity and how we insert that data as well as read data from the table.
What is the maximum size of data which inserted into the single row? What is max and min size of data in one field in Android SQLite?
It is usually not recommended to store image data into the database.
But however if it is required for your project then you can do so.
Image data are usually stored into db using BLOB data type, Room also provide support for BLOB data type Documentation
You can declare your entity class as mentioned below to store Image data.
#Entity(tableName = "test")
public class Test{
#PrimaryKey
#ColumnInfo(name = "_id")
private int id;
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
private byte[] image;
}
As Pinakin mentioned, it is not recommended to store an image into database and file path would be better but if it is required to store image I would suggest compress the image to below 2 MB (here is an example) to avoid breaking app. Room supports BLOB for image.
Entity class in kotlin:
ImageTest.kt
#Entity
class ImageTest {
#PrimaryKey(autoGenerate = true)
var id: Int = 1
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
var data: ByteArray? = null
}
ImageDao.kt
#Dao
interface ImageTestDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun upsertByReplacement(image: List<ImageTest>)
#Query("SELECT * FROM image")
fun getAll(): List<ImageTest>
#Query("SELECT * FROM image WHERE id IN (:arg0)")
fun findByIds(imageTestIds: List<Int>): List<ImageTest>
#Delete
fun delete(imageTest: ImageTest)
}
Databse.kt
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
#Database(entities = arrayOf(ImageTest::class), version = 1)
#TypeConverters(DataConverters::class)
abstract class Database : RoomDatabase() {
abstract fun getImageTestDao(): ImageTestDao
}
In DatabaseHelper something like
class DatabaseHelper(context: Context) {
init {
DatabaseHelper.context = WeakReference(context)
}
companion object {
private var context: WeakReference<Context>? = null
private const val DATABASE_NAME: String = "image_test_db"
private var singleton: Database? = null
private fun createDatabase(): Database {
return Room.databaseBuilder(context?.get() ?:
throw IllegalStateException("initialize by calling
constructor before calling DatabaseHelper.instance"),
Database::class.java,
DATABASE_NAME)
.build()
}
val instance: Database
#Synchronized get() {
if (null == singleton)
singleton = createDatabase()
return singleton as Database
}
fun setImage(img: Bitmap){
val dao = DatabaseHelper.instance.getImageTestDao()
val imageTest = ImageTest()
imageTest.data = getBytesFromImageMethod(image)//TODO
dao.updsertByReplacement(imageTest)
fun getImage():Bitmap?{
val dao = DatabaseHelper.instance.getImageTestDao()
val imageByteArray = dao.getAll()
return loadImageFromBytes(imageByteArray[0].data)
//change accordingly
}
Correct me if I am wrong. Hope this helps someone out there
Save the image as a file and save the file path Uri to Room
As seen in CameraX's image capture use case, when a photo is successfully taken, the File path reference Uri, savedUri, can be retrieved safely.
Then, the Uri can be converted to a string with savedUri.toString(), and saved to Room.
It's important to ensure the Room file reference is also updated if the file is moved or deleted.
The image String saved in Room may need to be converted back into a Uri to be displayed with an image library such as Glide with Uri.parse(someString).
In the CameraX sample, an image path's Uri can safely be obtained in onImageSaved.
It would then be saved into Room off of the main thread using Kotlin Coroutines or RxJava, preferably in a ViewModel or somewhere that handles the business logic separate from the view logic.
Getting Started with CameraX > 5. Implement ImageCapture use case
private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
This strategy is outlined in Saving image in Room database on Reddit.
Cloud Storage
Creating a file for the image and saving the file path in Room covers local storage. In order to ensure the images are saved across multiple devices or when if data cache and data are cleared, a form of Cloud Storage is needed to upload the files to and to download and sync with the local storage.

Twitter API 401's on /account/verify_credentials

I am trying to make use of the twitter api, and am setting up a handler to deal with twitter api requests.
To do this I am using an HTTPUrlConnection and following the Twitter api docs
I've managed to get authenticated using the 3-legged OAuth process, but am stuck when actually trying to make requests with the oauth token.
Here is an example of what my auth headers look like:
Accept=*/*,
Connection=close,
User-Agent=OAuth gem v0.4.4,
Content-Type=application/x-www-form-urlencoded,
Authorization=
OAuth oauth_consumer_key=****&
oauth_nonce=bbmthpoiuq&
oauth_signature=*****%3D&
oauth_signature_method=HMAC-SHA1&
oauth_timestamp=1570586135&
oauth_token=*****&
oauth_version=1.0,
Host=api.twitter.com
for each header in the auth header I add it to my HTTP GET call like this:
urlConnection.setRequestProperty(header.key, header.value)
Note that Authorization points to one string
OAuth oauth_consumer_key=****&oauth_nonce=bbmthpoiuq&oauth_signature=*****%3Doauth_signature_method=HMAC-SHA1oauth_timestamp=1570586135&oauth_token=*****&oauth_version=1.0,Host=api.twitter.com
The following params are collected as follows:
oauth_consumer_key is my application API key
oauth_signature is computed by the hmacSign function below
oauth_token is the "oauth_token" received in the response from /oauth/access_token
The hmacSign function:
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
private fun makeURLSafe(url: String) : String {
return url.replace("/", "%2F")
.replace(",", "%2C")
.replace("=", "%3D")
.replace(":", "%3A")
.replace(" ", "%2520")
}
protected fun getURLString(params: Map<String, Any>) : String {
if (params.isEmpty()) return ""
return params.toList().fold("?") { total, current ->
var updated = total
updated += if (total == "?")
"${current.first}=${current.second}"
else
"&${current.first}=${current.second}"
updated
}
}
In the GET call I'm referring to, tokenSecret would be the oauth secret received from /oauth/access_token
After i make the call I get a 400: Bad Request
Is there anything obvious I'm doing wrong?
Update: By putting the params at the end of the url like ?key=value&key2=value2... instead of a 400 I get a 401. So I'm not sure which is worse, or which is the right way to do it.
Okay, finally got it working
So using the suggestion in the comments, I downloaded postman and copied all my info into postman - when i made the request there, I got a 200!
So then i looked back and tried to figure out what was different and there were a few things:
First the hmac sign function was missing an &
New function (added another & after the url):
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url&${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
Next I noticed my auth header had its params seperated with & but they all should've been replaced with , - i also needed to surround each of my values in ""
So it looked like:
OAuth oauth_consumer_key="****",oauth_nonce="bbmthpoiuq",oauth_signature="*****%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1570586135",oauth_token="*****",oauth_version="1.0",Host="api.twitter.com"
After these changes i started getting 200!
Hopefully this helps someone else (though im sure its unlikely considering how specific these issues were)

Categories

Resources