I would like to upload image to api. I have headers for request and also headers for Part with files:
#Multipart
#POST("/api")
suspend fun uploadFile(
#Part("payload") file: RequestBody,
#Header("Mobile-Client-Type") mobileClientType: String,
#Header("Mobile-Client-Version") version: String,
#Header("Authorization") authorization: String,
): retrofit2.Response<Any>
and
internal class RetrofitFileUpload {
private val fileUploadService = FileUploadService.create()
suspend fun requestWithPayload(
urlString: String,
file: File,
myHeaders:
pl.probs.uploadapp.jsonObjects.Headers,
payload: Payload
): retrofit2.Response<Any> {
val mediaType = "multipart/form-data".toMediaTypeOrNull()
val fileBody = RequestBody.create(mediaType, file)
val body: RequestBody = MultipartBody.Builder().addPart(
Headers.headersOf(
"type",
payload.type,
"identifier",
payload.identifier,
"omit_package_creation",
payload.omitPackageCreation.toString(),
"file_path",
payload.filePath.toString()
), fileBody
).build()
return fileUploadService.uploadFile(
body,
myHeaders.mobileClientType,
myHeaders.mobileClientVersion,
myHeaders.authorization,
)
}
}
Result looks like I have 2 parts instead of one:
Content-Type: multipart/form-data; boundary=eac4d249-196d-421e-9ffe-923b472a5f58
....other headers...........
--eac4d249-196d-421e-9ffe-923b472a5f58
Content-Disposition: form-data; name="payload"
Content-Transfer-Encoding: binary
Content-Type: multipart/mixed; boundary=55d4b971-d171-42a7-a82a-5deb1bccac98
Content-Length: 2421
--55d4b971-d171-42a7-a82a-5deb1bccac98
type: myType
identifier: myID
omit_package_creation: true
file_path: /data/0...
Content-Type: multipart/form-data
Content-Length: 2072
{[Content from file]}
--55d4b971-d171-42a7-a82a-5deb1bccac98--
WHat should I change to have only 1 part?
Related
So I'm still in the process of learning android dev and I'm currently working on an app which is supposed to show students their grades. Right now I am stuck at getting login to a service from which grades are collected. For that process I am using https://eduo-ocjene-docs.vercel.app/ api (documentation is in Croatian).
This is what curl request for logging in looks like:
curl --location --request GET 'https://ocjene.eduo.help/api/login' \--header 'Content-Type: application/json' \--data-raw '{ "username":"ivan.horvat#skole.hr", "password":"ivanovPassword123"}'
Here are screenshots of what I have tried until now
Here is how I build retrofit
object ApiModule {
private const val BASE_URL = "https://ocjene.eduo.help/"
lateinit var retrofit: EdnevnikApiService
private val json = Json { ignoreUnknownKeys = true }
fun initRetrofit() {
val okhttp = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.client(okhttp).build().create(EdnevnikApiService::class.java)
}
}
The login method
interface EdnevnikApiService {
#HTTP(method = "get", path = "/api/login", hasBody = true)
fun login(#Body request: LoginRequest): Call<LoginResponse>
}
This is what happens when the login button is clicked
fun onLoginButtonClicked(email: String, password: String) {
val request = LoginRequest(email, password)
ApiModule.retrofit.login(request).enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
loginResultLiveData.value = response.isSuccessful
val body = response.body()
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
loginResultLiveData.value = false
}
})
}
and this is what kotlin request and kotlin response data classes look like
#kotlinx.serialization.Serializable
data class LoginRequest(
#SerialName("username") val username: String,
#SerialName("password") val password: String,
)
#kotlinx.serialization.Serializable
data class LoginResponse(
#SerialName("LoginSuccessful") val isSuccessful: Boolean,
)
Oh and this is what I get from the interceptor when I send the request
My guess is server is responding with 400 Bad Request due to unsupported method type. When I replaced method = "get" with method = "GET" in your sample code, I received:
java.lang.IllegalArgumentException: method GET must not have a request body.
which makes sense. Luckily, the /login API you shared works with POST method type, so you can try using:
#HTTP(method = "POST", path = "/api/login", hasBody = true,)
I checked at my end and I received the following response:
<-- 200 https://ocjene.eduo.help/api/login (1390ms)
access-control-allow-origin: *
access-control-allow-credentials: true
set-cookie: epicCookie=f69fbd6d4f10b5cc38e038b5da0843b356776c58c4fb32aed24dbcc49026778724bc25e21448c05a29df9f4b5558b254011fb3f8a992710f9901f23c53be5eaadaa799f3f5ac9e18de191bed02ef3e96030b83042ee8392755b03dd785edca6a;
content-type: application/json; charset=utf-8
etag: "bkrbkvg0eo6c"
vary: Accept-Encoding
date: Thu, 10 Nov 2022 03:07:08 GMT
server: Fly/b1863e2e7 (2022-11-09)
via: 2 fly.io
fly-request-id: 01GHFR2T56X9K0GFN3DH1Z9JYV-sin
{"LoginSuccessful":false,"token":"f69fbd6d4f10b5cc38e038b5da0843b356776c58c4fb32aed24dbcc49026778724bc25e21448c05a29df9f4b5558b254011fb3f8a992710f9901f23c53be5eaadaa799f3f5ac9e18de191bed02ef3e96030b83042ee8392755b03dd785edca6a"}
<-- END HTTP (228-byte body)
object ApiModule {
private const val BASE_URL = "https://ocjene.eduo.help/"
lateinit var retrofit: EdnevnikApiService
private val json = Json { ignoreUnknownKeys = true }
fun initRetrofit() {
val okhttp = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.client(okhttp).build().create(EdnevnikApiService::class.java)
}
}
I want to get altitude from api service and:
base url is https://api.open-elevation.com/
end point is api/v1/lookup?
and get two args lat and long like this
https://api.open-elevation.com/api/v1/lookup?locations=42,35
output is a json
How can i send get request and get data and please help
Hm. unfortunately you can't send a request with varargs through retrofit. But, you can do it like this.
Do something like this..
lifecycleScope.launch {
val latLong = arrayOf(41.161758, -8.583933)
val result = withContext(Dispatchers.IO) {
RetroClient.getInstance().lookup(latLong.joinToString())
}.awaitResponse()
if (result.isSuccessful && result.code() == 200) {
// If success do your sutff here
}
}
RetroClient
object RetroClient {
private val httpInterceptor by lazy {
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
private val client by lazy {
OkHttpClient.Builder()
.addInterceptor(httpInterceptor)
.callTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
private val gson by lazy {
GsonBuilder().setLenient().create()
}
fun getInstance(): RetroInterface {
return Retrofit.Builder()
.baseUrl("https://api.open-elevation.com/api/v1/")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson)).build()
.create(RetroInterface::class.java)
}
}
RetroInterface
interface RetroInterface {
#GET("lookup")
fun lookup(
#Query("locations") locations: String,
): Call<LocationsModel?>
}
LocationsModel
data class LocationsModel(
var results: List<Result>?
)
data class Result(
var elevation: Int?,
var latitude: Double?,
var longitude: Double?
)
RESULTS
I/okhttp.OkHttpClient: <-- 200 OK https://api.open-elevation.com/api/v1/lookup?locations=41.161758%2C%20-8.583933 (1411ms)
I/okhttp.OkHttpClient: Server: nginx/1.21.1
I/okhttp.OkHttpClient: Date: Sat, 27 Aug 2022 18:33:55 GMT
I/okhttp.OkHttpClient: Content-Type: application/json
I/okhttp.OkHttpClient: Transfer-Encoding: chunked
I/okhttp.OkHttpClient: Connection: keep-alive
I/okhttp.OkHttpClient: Access-Control-Allow-Origin: *
I/okhttp.OkHttpClient: Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS
I/okhttp.OkHttpClient: Access-Control-Allow-Headers: Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token
I/okhttp.OkHttpClient: Strict-Transport-Security: max-age=31536000; includeSubDomains
I/okhttp.OkHttpClient: {"results": [{"latitude": 41.161758, "longitude": -8.583933, "elevation": 117}]}
I/okhttp.OkHttpClient: <-- END HTTP (80-byte body)
Hope you understand. - Thank you.
Uploading to S3 signed URL's with a file as an origin works without problems, but trying to do it with an Uri I get from my ActivityResult is not working for me.
Here is what I try:
I'm using a InputStreamRequestBody class like described here: https://commonsware.com/blog/2020/07/05/multipart-upload-okttp-uri.html
class InputStreamRequestBody(
private val contentType: MediaType,
private val contentResolver: ContentResolver,
private val uri: Uri
) : RequestBody() {
override fun contentType() = contentType
override fun contentLength(): Long = -1
#Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val input = contentResolver.openInputStream(uri)
input?.use { sink.writeAll(it.source()) }
?: throw IOException("Could not open $uri")
}
}
My Retrofit UploadAPI interface:
interface UploadAPI {
#PUT
suspend fun uploadFile(
#Header("x-amz-acl") contentType: String,
#Url uploadUrl: String,
#Body file: RequestBody
): Response<Unit>
companion object {
const val BASE_URL = "https://....com"
}
}
And here my Retrofit Singleton in AppModule:
#Provides
#Singleton
fun provideUploadApi(): UploadAPI {
return Retrofit.Builder()
.baseUrl(UploadAPI.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(UploadAPI::class.java)
}
And then calling the InputStreamRequestBody class with my Uri (in my repository) and calling the retrofit instance and upload function:
val resolver = context.contentResolver
val type = ("image/jpeg").toMediaType()
val contentPart = InputStreamRequestBody(type, resolver, content)
uploadAPI.uploadFile(
"public-read",
mySignedUrl,
contentPart
)
this is what my logginInterceptor tells me:
2022-05-19 22:16:48.844 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: --> PUT https://app-content.ams3.digitaloceanspaces.com/Files/Test/profilbild.webp?Content-Type=image%2Fwebp&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CENSORED-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read
2022-05-19 22:16:48.844 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: Content-Type: image/jpeg
2022-05-19 22:16:48.844 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: x-amz-acl: public-read
2022-05-19 22:16:48.896 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: --> END PUT (binary -1-byte body omitted)
2022-05-19 22:16:49.503 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: <-- 411 Length Required https://app-content.ams3.digitaloceanspaces.com/Files/Test/profilbild.webp?Content-Type=image%2Fwebp&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CENSORED-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read (606ms)
2022-05-19 22:16:49.504 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: content-length: 239
2022-05-19 22:16:49.504 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: x-amz-request-id: tx000000000000004541b27-00622f61-20f93ecc-ams3c
2022-05-19 22:16:49.505 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: accept-ranges: bytes
2022-05-19 22:16:49.505 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: content-type: application/xml
2022-05-19 22:16:49.505 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: date: Thu, 19 May 2022 15:16:49 GMT
2022-05-19 22:16:49.505 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: cache-control: max-age=60
2022-05-19 22:16:49.506 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: strict-transport-security: max-age=15552000; includeSubDomains; preload
2022-05-19 22:16:49.506 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: connection: close
2022-05-19 22:16:49.506 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: <?xml version="1.0" encoding="UTF-8"?><Error><Code>MissingContentLength</Code><BucketName>app-content</BucketName><RequestId>tx00000000000000fffb27-0062865f61-20f93ecc-ams3c</RequestId><HostId>20f93ecc-ams3c-ams3-zg03</HostId></Error>
2022-05-19 22:16:49.507 8469-8703/com.havanasun.loginplayground I/okhttp.OkHttpClient: <-- END HTTP (239-byte body)
I have the strong feeling that I cannot just pass the instance of InputStreamRequestBody as a requestBody eventhough this class inherrits from RequestBody.
I am trying to call a post API with Retrofit using coroutine but after hours of searching I am unable to find the mistake. There is no clear information about the error.
Below is the simple JSON I should be getting after posting the data.
{
"status": "OK",
"data": 5,
"message": null,
"code": "200"
}
api call from fragment
lifecycleScope.launchWhenStarted {
sharedPreferences.getString(AppConstant.token, "")?.let { authToken ->
viewModelSubProperty.sendDetail(authToken,propertyData)
}
}
where propertyData is the values I am send to the api.
ViewModel
suspend fun sendDetail(token: String,data: SubPropertyData) {
viewModelScope.launch(Dispatchers.IO) {
val responseResult = repository.sendDetail(token = token,data)
withContext(Dispatchers.Main) {
if(responseResult is Result.Success) {
Log.e("Viewmodel", "${responseResult.data}")
Log.e("Viewmodel", "$responseResult")
}
else if (responseResult is Result.Error && responseResult.exception.message == AppConstant.INTERNET_ERR_MSG)
EventBus.getDefault().post(WebServiceErrorEvent(null, true))
else if (responseResult is Result.Error)
EventBus.getDefault().post(WebServiceErrorEvent(responseResult))
}
}
}
DataSource
suspend fun sendDetail(token: String, data: SubPropertyData): Result<AddUpdateDataResponse> {
val serverResponse: Response<AddUpdateDataResponse>
return try {
serverResponse = serverApi.sendDetail(token,data)
if(serverResponse.isSuccessful)
Result.Success(serverResponse.body()!!)
else {
Log.e("what-code ", ""+serverResponse.errorBody())
Log.e("what-code ", serverResponse.errorBody()?.charStream().toString())
Result.Success(serverResponse.body()!!)
}
} catch (e: Throwable) {
if(e is NoConnectivityException)
Result.Error(IOException(AppConstant.INTERNET_ERR_MSG))
else
Result.Error(IOException(e.localizedMessage))
}
}
Api Call
#POST("api/addOrUpdateSubjectPropertyDetail")
suspend fun sendDetail(
#Header("Authorization") Authorization:String,
#Body data: SubPropertyData
) : Response<AddUpdateDataResponse>
Pojo class
data class AddUpdateDataResponse(
#SerializedName("code")
val code: String?,
#SerializedName("data")
val data: Int?,
#SerializedName("message")
val message: String? = null,
#SerializedName("status")
val status: String?
)
Logs
I/okhttp.OkHttpClient: --> POST https://abc/api/AddOrUpdateSubjectPropertyDetail
Content-Type: application/json; charset=UTF-8
Content-Length: 398
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiI0IiwiaHR0cDovL3NjaGVtYXnzK5gv0k4
I/okhttp.OkHttpClient: {"address":{"city":"Karachi","countryId":1,"countryName":"Pak","countyId":1,"countyName ":"SSS","stateId":11,"stateName":"Sindh","street":"akl","unit":"00","zipCode":"123"},"appraisedPropertyValue":100000.0,"floodInsurance":0.0,"homeOwnerInsurance":200.0,"isMixedUseProperty":true,"loanApplicationId":5,"mixedUsePropertyExplanation":"ashh","occupancyTypeId":1,"propertyTax":100.0,"propertyTypeId":1}
--> END POST (398-byte body)
--> END POST (398-byte body)
I/okhttp.OkHttpClient: <-- 500
https://abc/api/AddOrUpdateSubjectPropertyDetail (167ms)
I/okhttp.OkHttpClient: server: Microsoft-IIS/10.0
I/okhttp.OkHttpClient: x-powered-by: ASP.NET
date: Mon, 08 Nov 2021 22:31:31 GMT
I/okhttp.OkHttpClient: <-- END HTTP (0-byte body)
E/what-code: null
null
okhttp3.ResponseBody$BomAwareReader#3554188
I'm interested to send push notification via Firebase from android app.
Firebase documentation states that payload json must be sent to https://fcm.googleapis.com/fcm/send
So I using with Retrofit to send a json but the request fails, and if I sending a request without Retrofit it's works perfect.
What's the difference between the tow implementations and why it's not works with Retrofit.
Here are the two implementations and the error when using Retrofit.
val notificationBody = NotificationBody(notificationData, "/topics/all")
val rb = Gson().toJson(notificationBody).toRequestBody("application/json".toMediaTypeOrNull())
val responeNoti = RetrofitManager.notificationServiceApi.postNotification(rb)
responeNoti.enqueue(object : Callback<ResponseBody>{
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d(TAG, t.message)
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
Log.d(TAG, response.toString())
}
})
NotificationServiceApi
private const val FCM_URL = "https://fcm.googleapis.com/fcm/"
val notificationServiceApi: ServiceApi by lazy{
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val okHttpClientBuilder = OkHttpClient.Builder()
okHttpClientBuilder.addInterceptor(object : Interceptor{
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val request: Request = chain.request().newBuilder().
addHeader("Content-Type", "application/json").
addHeader("Authorization","key=*my firebase key*").build()
return chain.proceed(request)
}
}).addInterceptor(interceptor)
val notificationRetrofit = Retrofit.Builder()
.client(okHttpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(FCM_URL)
.build()
notificationRetrofit.create(ServiceApi::class.java)
}
ServiceApi
#POST("send/")
fun postNotification(#Body notificationBody: RequestBody) : Call<ResponseBody>
The log Http
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: --> POST
https://fcm.googleapis.com/fcm/send/
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: Content-
Length: 117
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: Content-Type: application/json
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: Authorization: key=my firebase key
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: {"data":{"imageUrl":"https://i.imgur.com/KA1jrU7.jpg","textBody":"my content","title":"my title"},"to":"/topics/all"}
2020-09-06 00:12:27.991 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: --> END POST (117-byte body)
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: <-- 404 https://fcm.googleapis.com/fcm/send/ (1206ms)
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: content-type: text/plain; charset=utf-8
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: x-content-type-options: nosniff
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: x-frame-options: SAMEORIGIN
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: x-xss-protection: 0
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: date: Sat, 05 Sep 2020 21:12:29 GMT
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: content-length: 135
2020-09-06 00:12:29.198 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
2020-09-06 00:12:29.199 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: A valid push subscription endpoint should be specified in the URL as such: https://fcm.googleapis.com/wp/dHIoDxE7Hdg:APA91bH1Zj0kNa...
2020-09-06 00:12:29.199 32059-32244/com.bagi.soreknetmanager I/okhttp.OkHttpClient: <-- END HTTP (135-byte body)
2020-09-06 00:12:29.201 32059-32059/com.bagi.soreknetmanager D/MainActivity: Response{protocol=h2, code=404, message=, url=https://fcm.googleapis.com/fcm/send/}
The implementation that works great without using Retrofit
val notificationBody = NotificationBody(notificationData, "/topics/all")
val jsonPayload = createJsonFromObject(notificationBody)
val thread = Thread(Runnable {
try {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
var client = OkHttpClient().newBuilder().addInterceptor(interceptor)
.build()
var mediaType = "application/json".toMediaTypeOrNull()
var body: RequestBody = RequestBody.create(
mediaType,
jsonPayload
)
var request: Request = Request.Builder()
.url("https://fcm.googleapis.com/fcm/send")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader(
"Authorization",
"key=my firebase key"
)
.build()
var response: okhttp3.Response = client.newCall(request).execute()
} catch (e: Exception) {
e.printStackTrace()
}
})
thread.start()
Error is in your URL..
Check your FCM_URL, it is incomplete, you forgot to append "send" word
FCM_URL = "https://fcm.googleapis.com/fcm/"
Correct URL-
FCM_URL ="https://fcm.googleapis.com/fcm/send"