How to get String in response using Retrofit 2? - java

I would like to understand how Retrofit works, but the official documentation is very weak.
I need to make a very simple GET request and get the response as a String.
Now I use standard HTTPUrlConnection and it works nicely, just request - response
Can anyone tell me how to get a String response without converting it to an object or something like that?

You can use ScalarsConverterFactory for strings and both primitives and their boxed types to text/plain bodies.
Add this dependency to your build.gradle file:
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
Try this:
public interface ExampleService {
#GET("/users/{user}/repos")
Call<String> listRepos(#Path("user") String user);
}
And add ScalarsConverterFactory to your builder:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
You can then retrieve this string like this:
Call<String> call = exampleService.listRepos(user);
Response<String> response = call.execute();
String value = response.body();

Related

Retrofit2 responseBodyConverter: I need request too

In a not so exotic situation I need access to the original request when I handle the response in the responseBodyConverter. For example, right now I need the original url -- e.g. I want to adjust relative urls in the response HTML. And perhaps I will need some request header, like a cookie.
But I don't know how to get the request data.
Here is what I have:
HttpLoggingInterceptor hlog = ses.http_logger(servername);
if (hlog != null) {
builder.addInterceptor(hlog);
}
OkHttpClient okhttpClient = builder
.build();
return new Retrofit.Builder()
.baseUrl(base_url)
.client(okhttpClient)
.addConverterFactory(
new Converter.Factory() {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
return responseBody -> {
BufferedSource buffer = Okio.buffer(responseBody.source());
String html = buffer.readUtf8();
System.out.println(html);
};
}
})
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
;
I found this solution https://github.com/square/retrofit/issues/2267/
But I can not understand what it does or whether it is too complex or any side effects.
Can I get access to the request data in Retrofit2 while processing the response in responseBodyConverter and how?
Edit: the above works as expected, but a simpler way is preferred.

Upload a file to AWS S3 pre-signed URL using Retrofit2

I'm trying to upload a file to Amazon's S3 using a pre-signed URL. I get the URL from a server which generates the URL & sends it to me as part of a JSON object. I get the URL as a String, something like this:
https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/ImageName?X-Amz-Security-Token=xxfooxx%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fxxbarxx%3D&X-Amz-Algorithm=xxAlgoxx&X-Amz-Date=20170831T090152Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=xxcredxx&X-Amz-Signature=xxsignxx
Unfortunately, when I pass this to Retrofit2, it modifies the String attempting to make it into a URL. I've set encoding=true which took care of most of the problem but not completely. I know the String works as it is. I've tried it in Postman & get a successful response.
1st I tried just putting the String (except for what I cut out as baseUrl) as a whole into the Path
public interface UpdateImageInterface {
#PUT("{url}")
Call<Void> updateImage(#Path(value="url", encoded=true) String url, Body RequestBody image);
}
The calling code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is "ImageName..."
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
This works mostly except the the '?' (after "ImageName") get converted to "%3F". This causes a Bad Request / 400.
My next attempt was to create a query with Retrofit2 but then dump the whole String (with multiple queries) into the query.
public interface UpdateImageInterface {
#PUT("ImageName")
Call<Void> updateProfilePhoto(#Query(value="X-Amz-Security-Token", encoded = true) String token, #Body RequestBody image);
}
The calling code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is "xxfooxx..."
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
This gets the '?' rendered correctly but all of the '&' get changed to "%26"
Lastly I tried passing the whole String in baseUrl() but that gives an IllegalArgumentException for not having '/' on the end.
I know that I could parse the pre-signed URL to make multiple queries & assemble them in Retrofit2 as queries should be done but I'd like to avoid that processing.
To restate the question:
Is there a way to easily (without heavy String parsing) upload a file to S3 with a pre-signed URL using Retrofit2?
With help from a colleague, this is the solution.
public interface UpdateImageInterface {
#PUT
Call<Void> updateImage(#Url String url, #Body RequestBody image);
}
Calling code:
String CONTENT_IMAGE = "image/jpeg";
File file = new File(localPhotoPath); // create new file on device
RequestBody requestFile = RequestBody.create(MediaType.parse(CONTENT_IMAGE), file);
/* since the pre-signed URL from S3 contains a host, this dummy URL will
* be replaced completely by the pre-signed URL. (I'm using baseURl(String) here
* but see baseUrl(okhttp3.HttpUrl) in Javadoc for how base URLs are handled
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.dummy.com/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is the String as received from AWS S3
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
Javadoc for info on #Url (class Url) &
baseUrl() (class Retrofit.Builder)
MediaType is a class in the OkHttp library that is often used with Retrofit (both from Square). Info about constants passed to the parse method can be found in the Javadoc.
Use the following while uploading directly to S3 using presigned URL.
#Multipart
#PUT
#Headers("x-amz-acl:public-read")
Call<Void> uploadFile(#Url String url, #Header("Content-Type") String contentType, #Part MultipartBody.Part part);

How to get raw response and request using Retrofit 2.0

I am trying to get the raw response using Retrofit2.0.2.
So far I tried to print the response using following line of code but it prints the address and not the exact response body.
Log.i("RAW MESSAGE",response.body().toString());
compile 'com.squareup.retrofit2:retrofit:2.0.2'
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
GitApi gitApi = retrofit.create(GitApi.class);
Call<Addresses> call = gitApi.getFeed(user);
call.enqueue(new Callback<Addresses>() {
#Override
public void onResponse(Response<Addresses> response, Retrofit retrofit) {
try {
mDisplayDetails.setText(response.body().getSuburbs().get(0).getText());
**Log.i("RAW MESSAGE",response.body().toString());**
} catch (Exception e) {
mDisplayDetails.setText(e.getMessage());
}
mProgressBar.setVisibility(View.INVISIBLE);
}
#Override
public void onFailure(Throwable t) {
mDisplayDetails.setText(t.getMessage());
mProgressBar.setVisibility(View.INVISIBLE);
}
});
That's because it's already converted to an object by converter. To get the raw json, you need an interceptor on your Http Client. Thankfully you don't need to write your own class, Square already provide HttpLoggingInterceptor class for you.
Add this on your app-level gradle
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
And use it in your OkHttpClient
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor).build();
Don't forget to change your HttpClient in Retrofit.
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("https://yourapi.com/api/")
.build();
In the Log Cat you'll see the raw json response. Further information is available on Square's OkHttp github.
Caution!
Don't forget to remove Interceptors (or change Logging Level to NONE) in production! Otherwise people will be able to see your request and response on Log Cat.
You have to use "ResponseBody" from okhttp3 in your call. Then, get "response.body().string()" to get the JSONObject as your server gives to you.
It's a good way to catch errors if there are any errors parsing server response to your model object.
Simply use:
Log.i("RAW MESSAGE", response.raw().body().string());
Or:
Log.i("RAW MESSAGE", response.body().string());
Guess you want to see the raw response body for debugging purpose. There are two ways to do this.
Using okhttp3.logging.HttpLoggingInterceptor as #aldok mentioned.
If you want to check some properties of the response object, or convert the json(response body) to POJO manually, you may want to do it like this:
Don't use the addConverterFactory while initializing the retrofit client, comment this line.
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
//.addConverterFactory(GsonConverterFactory.create())
.build();
Then consuming the response like this:
Call<ResponseBody> topRatedResponseCall = apiService.getTopRatedMoves(API_KEY);
topRatedResponseCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.d(LOG_TAG, response.body().string());
int code = response.code();
testText.setText(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
Hope this would help~

Retrofit: Unable to create #Body converter for class

I need to send next json via retrofit 2:
{
"Inspection": {
"UUID": "name",
"ModifiedTime": "2016-03-09T01:13",
"CreatedTime": "2016-03-09T01:13",
"ReviewedWith": "name2",
"Type": 1,
"Project": {
"Id": 41
},
"ActionTypes": [1]
}
}
With Header: Authorization: access_token_value
I tried this:
//header parameter
String accessToken = Requests.getAccessToken();
JsonObject obj = new JsonObject();
JsonObject inspection = new JsonObject();
inspection.addProperty("UUID","name");
inspection.addProperty("ModifiedTime","2016-03-09T01:13");
inspection.addProperty("CreatedTime","2016-03-09T01:13");
inspection.addProperty("ReviewedWith","name2");
inspection.addProperty("Type","1");
JsonObject project = new JsonObject();
project.addProperty("Id", 41);
inspection.add("Project", project);
obj.add("Inspection", inspection);
Retrofit restAdapter = new Retrofit.Builder()
.baseUrl(Constants.ROOT_API_URL)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build();
IConstructSecureAPI service = restAdapter.create(IConstructSecureAPI.class);
Call<JsonElement> result = service.addInspection(accessToken, obj);
JsonElement element = result.execute().body();
But everytime i recieved exception: java.lang.IllegalArgumentException: Unable to create #Body converter for class com.google.gson.JsonObject (parameter #2)
How can I send it ? Or any another idea how I can do it. You can even offer me with parameter as simple String with json inside. It will suit for me
Solution:
declare body value in your interface with next:
#Body RequestBody body
and wrap String JSON object:
RequestBody body = RequestBody.create(MediaType.parse("application/json"), obj.toString());
There is chance of you kept same #SerializedName("") for multiple vairable/fields/tags
You can specify a Converter when you create the Retrofit like this
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseurl)
.client(okHttpClient)
.build();
If this is due to #SerializedName make sure it is not dulplicated.
For e.g. This error will be thrown in below case: (Note: bookingId is passed twice)
#SerializedName(value="bookingId", alternate={"id", "bookingId"})
But, this is the correct:
#SerializedName(value="bookingId", alternate={"id", "someOtherId", "whateverId"})
Body uses a single request object, declare your request object as following
class Inspection {
String UUID;
//..... add your fields
Project project;
}
class Product
{
int Id;
//....... add your fields
}
I assume your service IConstructSecureAPI endpoint is:
#GET(...) // change based on your api GET/POST
Call<Response> addInspection(
#Header("Authorization") String accesstoken,
#Body Inspection request
);
and you can declare your desire Response.
Check this answer, it uses HashMap instead of class.
I kept getting this error when I upgraded to Java 17, still working fine on Java 11.
Here's what worked for me
To go deeper into the Exception I put a debug point in the Utils.java found in retrofit stack trace.
Doing so led me to the narrower cause being: java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte java.time.LocalTime.hour accessible: module java.base does not "opens java.time" to unnamed module #35e2d654
Googling down further from here led me to https://github.com/mockk/mockk/issues/681#issuecomment-959646598 which in a nutshell suggests to add --add-opens java.base/java.time=ALL-UNNAMED as a JVM argument.
Boom, It worked.
In my case I simply forgot to apply the kotlinx.serialization plugin for Gradle, so no code for the serializers was generated. Fix it via:
plugins {
kotlin("plugin.serialization")
}
You can use an Interceptor to send Authorization Header in each request
class AuthorizationInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String authorizationToken = AuthenticationUtils.getToken();
Request authorizedRequest = originalRequest.newBuilder()
.header("Authorization", authorizationToken)
.build();
return chain.proceed(authorizedRequest);
}
}
I am using Moshi and Retrofit and my problem was I forgot to add #JsonSerializable annotation for the DTO class for #body.
you should add this annotation like this:
#JsonSerializable
data class RegisterDTO(
#field:Json(name = "device_id") val deviceId: String,
)

Prevent retrofit from encoding my http request body

I'm trying to pass a string of the format below as the body of a http post request.
param1=PARAM1&param2=PARAM2&param3=PARAM3
But retrofit encodes my body so that = becomes \u003d and & becomes \u0026. And I end up with a string which actually looks like this:
param1\u003dPARAM1\u0026param2\u003dPARAM2\u0026param3\u003dPARAM3
How can I prevent that?
My retrofit rest api is defined as follows.
public interface RestAPI {
#POST("/oauth/token")
public void getAccessToken(#Body String requestBody, Callback<Response> response);
}
If you have a serialized class (like a HashMap) in the request body and you want to prevent encoding that (like in vezikon's and my problem), you can create a custom Gson with disabled escaping using:
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
Pass this converter to your rest adapter:
yourRestAdapter = new RestAdapter.Builder()
.setEndpoint(.....)
.setClient(.....)
.setConverter(new GsonConverter(gson))
.build();
This way the "=" characters in the post body stay intact while submitting.
To answer the question directly, you can use TypedString as the method parameter type. The reason the value is being changed is because Retrofit is handing the String to Gson in order to encode as JSON. Using TypedString or any TypedOutput subclass will prevent this behavior, basically telling Retrofit you will handle creating the direct request body yourself.
However, that format of payload is called form URL encoding. Retrofit has native support for it. Your method declaration should actually look like this:
#FormUrlEncoded
#POST("/oauth/token")
void getAccessToken(
#Field("param1") String param1,
#Field("param2") String param2,
#Field("param3") String param3,
Callback<Response> callback);
Using Kotlin
For Retrofit 2 you can initialize retrofit with a Gson converter factory.
val builder = GsonBuilder().disableHtmlEscaping().create()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(builder))
.client(monoOkHttpClient())
.build()
This builder should remove escaping from your json output.
Gradle file dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
This issue can be fixed with below workaround.
#POST("yourString")
Call<YourResponseModel> yourCallMethod(#Query("yourKey") String yourValue,
#Query("yourKey") String yourValue,
#Query("yourKey") String yourValue);
Note : Don't use "#FormUrlEncoded" for this case.
Reference Here - https://github.com/square/retrofit/issues/1407

Categories

Resources