I have a service like below. I call the method Retroifit library. The service works when I send the paramter with #Body Test type but I want work with #Body String that i don't create a Test class and using this in #Body. I create a JSONObject and convert this to String but in this way, the program doesn't work! Can you help me or suggest a solution for this?
My Web API:
[Route("TestService"), HttpPost, IgnoreDataLog]
public async Task<Result<TestResult>> Add(Test pmDeviceObj)
{
var listResult = await pmService.AddAsync(pmDeviceObj);
return listResult;
}
Android part:
#POST("TestService")
Call<Result<TestResult>> TestService(#Header("Content-Type") String content_type,#Body String body);
Call Service in Android-> I get the StatusCode 400 with the below code
JSONObject jsonBody=new JSONObject();
try {
jsonBody.put("Id",73);
jsonBody.put("seri","55656573");
jsonBody.put("code","fc24009b9160");
jsonBody.put("sID",8);
}catch (JSONException ex){
ex.printStackTrace();
}
retrofit2.Call<Result<TestResult>> call1=service.TestService("application/json",jsonBody.toString());
If I use the below code in the Android part, everything works correct and I take the data.
#POST("TestService")
Call<Result<TestResult>> TestService(#Header("Content-Type") String content_type,#Body Test inputValue);
Test test=new Test(73,"556565","fc24009b9160",8);
retrofit2.Call<Result<TestResult>> call1=service.TestService("application/json",test);
This happens because Retrofit considers String as a 'normal' object that has to be converted to a JSON and is not aware that it already is a JSON represntation of object.
If you have HttpLogginInterceptor configured, you should see that (simplified example) your JSON string:
{"sId": "8"}
actually goes like:
"{\"sId\": \"8\"}"
To prevent this happen you need to use something like the comment suggests ScalarsConverterFactory. First you need to set dependencies for it:
For Gradle
dependencies {
implementation “com.squareup.retrofit2:converter-scalars:2.4.0”
}
or for Maven
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-scalars</artifactId>
<version>2.4.0</version>
</dependency>
After done that you need to add the converter factory to your Retrofit, like (added also logging to be easily tested):
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl(MY_URL)
// be sure to add this before gsonconverterfactory!
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory
.create())
.client(client).build();
myApi = retrofit.create(MY_API_CLASS);
Related
I'm trying Retrofit 2.4.0 in my android application. Where do I have a login activity. I'm trying POST request but it return response code 404 in onResponse method. But It is working perfectly with POSTMAN and I getting correct data in log using HttpLoggingInterceptor. I have tried many methods to overcome this problem. My service code is.
#POST("user_login")
#FormUrlEncoded
Call<ResponseBody> user_login(#Field("email") String email, #Field("password") String password);
#Multipart
#POST("user_login")
Call<ResponseBody> user_login(#PartMap() Map<String, RequestBody> bodyMap);
#Multipart
#POST("user_login")
Call<Status> user_login(#Part("email") RequestBody email, #Part("password") RequestBody password);
#POST("user_login")
Call<JSONObject> user_login(#Body String credentials);
#POST("user_login")
Call<User> user_login(#Body LoginCredentials credentials);
none of above methods aren't working.
I'm posting some other methods Which are I am using too.
#Provides
#Singleton
Cache provideOkHttpCache(TUK application) {
int cacheSize = 10 * 1024 * 1024; // 10 MB
return new Cache(application.getCacheDir(), cacheSize);
}
#Provides
#Singleton
Gson provideGson() {
GsonBuilder builder = new GsonBuilder();
// builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return builder.create();
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.cache(cache);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> Logger.wtf("AppModule", message));
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
return builder.build();
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient client) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(TUK.BASE_URL)
.client(client)
.build();
}
but I'm trying other URLs too with GET method are working perfectly.
my base url is like "http://www.example.com/api/"
and my #PartMap() code is
public static Map<String, RequestBody> getMap(LoginCredentials credentials) {
Map<String, RequestBody> map = new HashMap<>();
map.put("email", RequestBody.create(MultipartBody.FORM, credentials.getEmail()));
map.put("password", RequestBody.create(MultipartBody.FORM, credentials.getPassword()));
return map;
}
my HttpLoggingInterceptor logging data is
<-- 404 https://www.example.com/api/user_login (718ms)
content-type: application/json; charset=utf-8
.
.
.
.
{"status":false,"message":"Invalid User Email Or Password."}
<-- END HTTP (60-byte body)
and my onResponse methods logging data is
Response{protocol=h2, code=404, message=, url=https://www.example.com/api/user_login}
At the end my question is how to POST data using Retrofit.
I have stuck at this point. I have tried other solutions from stackoverflow but no help. Please guys help me out this problem. Any help will be appreciated. Thank you.
You dont need to use the multipart body for POST request and it can be easily done by
below code. For example I have an base URL like http://example.com/api/
#FormUrlEncoded
#POST("login")
Call<LoginResponse> login(#Field("email") String email,
#Field("password") String password);
The final call will be redirected to http://example.com/api/login in above case.
So in this case my call will be directed to http://example.com/api/login.
Or the other problem can be in your URL. As your base url is this "http://www.example.com/api/" and by joining the API which API name, it becomes this "http://www.example.com/api//api/user_login" which contains two "//" that can cause error in finding your API because of that it throws 404( which is not found). So please adjust your base URL accordingly. Hope that helps somehow.
Looks like a server mistake, 404 is the error code returned by server. Retrofit has requested success. But any request that the return code is not 200 will be considered a failed request. Retrofit will callback onFailure() method, you can get response in the following way.
#Override
public void onFailure(Call<News> call, Throwable t) {
if(t instanceof HttpException){
ResponseBody body=((HttpException) t).response().errorBody();
}
}
Tell your back end the meaning of 404,it should not be returned here
I have solved this problem by setting builder.setLenient();
complete code is
#Provides
#Singleton
Gson provideGson() {
GsonBuilder builder = new GsonBuilder();
builder.setLenient(); // this line help me to get response.
// builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return builder.create();
}
thanks for your help guys.
I am trying to fetch JSON from a REST API as a string.
I am using retrofit with scalarConverter for that purpose. I am able to pass the URL to fetch and my retrofit instance is also created successfully but I am not getting any response from the server.
PS: there is no request on the server, so that means my request is not going out from my machine.
I am new to android, kindly help me.
Retrofit instance creation:
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(base)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
jsonApi jsonapi=retrofit.create(jsonApi.class);
Call<String> stringcall=jsonapi.getStringResponse(speech);
jsonApi interface:
public interface jsonApi {
#GET
Call<String> getStringResponse(#Url String url);
}
base: it is the base URL
speech: it is a variable containing rest of the URL to be processed.
when I run, the app gets stuck here with this message being displayed in Run Tab:
W/OpenGLRenderer: Fail to change FontRenderer cache size, it already initialized
W/art: Before Android 4.1, method int android.support.v7.widget.DropDownListView.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView
With the below line
Call<String> stringcall=jsonapi.getStringResponse(speech);
you just get a Call object which is a representation of the HTTP request, and just by that, the request isn't executed. You need to use this object and call the execute method to make a synchronous request or the enqueue method to make an asynchronous request.
So in case you want to make a sync request, try the below:
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(base)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
jsonApi jsonapi=retrofit.create(jsonApi.class);
Call<String> stringcall=jsonapi.getStringResponse(speech);
try {
Response<String> response = stringcall.execute();
String result = response.body();
} catch (Exception ex) {
//handle exception
}
The documentation of Call interface is here, for your reference.
I am trying to consume the following HTTPS endpoints from Yahoo Weather Service:
Yahoo Weather Service API
I am doing some special query according to the API to get the current weather at some parametrized location.
#Service("weatherConditionService")
public class WeatherConditionServiceImpl implements WeatherConditionService {
private static final String URL = "http://query.yahooapis.com/v1/public/yql";
public WeatherCondition getCurrentWeatherConditionsFor(Location location) {
RestTemplate restTemplate = new RestTemplate();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(URL);
stringBuilder.append("?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22");
// TODO: Validate YQL query injection
stringBuilder.append(location.getName());
stringBuilder.append("%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys");
WeatherQuery weatherQuery = restTemplate.getForObject(stringBuilder.toString(), WeatherQuery.class);
// TODO: Test Json mapping response
Condition condition = weatherQuery.getQuery().getResults().getChannel().getItem().getCondition();
return new WeatherCondition(condition.getDate(), Integer.parseInt(condition.getTemp()), condition.getText());
}
Location is a class that provides the attribute "name" that is a String description of the location, such as "New York" or "Manila".
Condition an other classes just map the returning object.
When executing I get the following HTTP response:
org.springframework.web.client.HttpClientErrorException: 403 Forbidden
So this means I am not authorized to access the resource from what I understand.
The URL works great if I just copy & paste it in a web browser:
Yahoo Weather Query
I think that mapping is not a problem since I am not getting "400" (Bad Request) but "403" (Forbidden)
There must be some error on the way I use the RestTemplate object. I am researching but I can't find an answer.
The docs say you need an api key. But when I make a call like this:
fetch('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys')
.then(resp=> resp.json())
.then((res)=>console.log(res.query.results))
https://repl.it/NeoM
It works fine without one. Perhaps you've been blackisted for hitting the api too often.
Your code seems fine.
I finally found the answer. It finally WAS a Bad Request because I needed to pass the parameters differently (not as part of the URL).
I found the answer here. Here goes the code for my particular Yahoo Weather API call return a String (I still will have to do some work to use the mapping).
private static final String URL = "http://query.yahooapis.com/v1/public/yql";
public String callYahooWeatherApi() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(URL)
.queryParam("q", "select wind from weather.forecast where woeid=2460286")
.queryParam("format", "json");
HttpEntity<?> entity = new HttpEntity<>(headers);
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.GET,
entity,
String.class);
return response.getBody();
}
I want to send a POST with Retrofit 2. The url has some parameters:
#Headers({
"Accept: application/x-www-form-urlencoded;",
"User-Agent: my-app"
})
#FormUrlEncoded
#POST("server/directory/location.type")
`public Call<POJOStringValue> dataWithUr(#Path("arg1") String arg1, #Path("arg2"), String arg2);
The url looks like this
www.website.com/server/directory/location.type?arg1=value1&arg2=value2
I was requested to use a POST request. The values (value1 and value2) are dynamic at runtime. I started the project with Xamarin using HttpClient and now I'm rewriting it in Java native. In C# all I had to do was to concact the strings and send the resulting string in a single Post.
I tried to use #Path and the error was :
"server/directory/location.type" does not contain "{arg1}". (parameter #1)
Then, I tried to use #Query and the error was:
java.lang.IllegalArgumentException: Form-encoded method must contain at least one #Field.
Finally I tried with #Field the request never gets any response (I sette the connection timeout to 5 seconds)
Please help me, or tell me if I have to don't have any other choice but to use a GET request.
((EDIT))
Here is my code for the setup of the client:
private static void setupClient(){
final OkHttpClient client = new okhttp3.OkHttpClient.Builder()
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.build();
//define retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(iXUtils.getUrl_())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
this.client_ = retrofit.create(RequestInterface.class);
}
The get() method:
public static RequestInterface get(){
return this.client_;
}
Here is how I call it:
public String callFunctionDB(String arg1, String arg2){
setupClient();
Call<POJOStringValue> call = get().dataWithUrlString(arg1, arg2);
try {
POJOStringValue response = call.execute().body();
String value = response.getValue();
int test = 0;
} catch (IOException e) {
String value = "it failded";
e.printStackTrace();
}
return "test";
}
I put the test=0 to be able to put a breaking point, it never gets there. Plus I called the method "callFunctionDB" in a doInbackground to avoid the android.os.NetworkOnMainThreadException.
Retrofit requires you to have at least one form parameter if you request form encoding. You have answered your own question -- you are using query parameters instead of POST fields, so that annotation is not necessary. Remove the #FormUrlEncoded annotation, and change your parameters to #Query annotations.
My apiPath is fully dynamic. I am having items containing fields such us "ipAddress" and "SSLprotocol". Based on them I can build my url:
private String urlBuilder(Server server) {
String protocol;
String address = "";
if (AppTools.isDeviceOnWifi(activity)) {
address = serverToConnect.getExternalIp();
} else if (AppTools.isDeviceOnGSM(activity)) {
address = serverToConnect.getInternalIp();
}
if (server.isShouldUseSSL()) {
protocol = "https://";
} else {
protocol = "http://";
}
return protocol + address;
}
So my protocol + address can be: http:// + 192.168.0.01:8010 = http://192.168.0.01:8010
And I would like to use it like that:
#FormUrlEncoded
#POST("{fullyGeneratedPath}/json/token.php")
Observable<AuthenticationResponse> authenticateUser(
#Path("fullyGeneratedPath") String fullyGeneratedPath,
#Field("login") String login,
#Field("psw") String password,
#Field("mobile") String mobile);
So full path for authenticateUser would be http://192.168.0.01:8010/json/token.php - for example.
That means I don't need any basePath because I create whole basePath myself depending on server I want to connect to.
My retrofit setup is:
#Provides
#Singleton
Retrofit provideRetrofit(OkHttpClient okHttpClient,
Converter.Factory converterFactory,
AppConfig appConfig) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(appConfig.getApiBasePath())
.addConverterFactory(converterFactory)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
return builder.build();
}
If I remove baseUrl then I get error that this parameter is required. So I set my apiBasePath to:
public String getApiBasePath() {
return "";
}
And then I get error instantly after I create retrofit instance:
java.lang.IllegalArgumentException: Illegal URL:
How to solve it?
From source (New URL resolving concept) you can simply specify whole path in post request.
Moreover we also can declare a full URL in #Post in Retrofit 2.0:
public interface APIService {
#POST("http://api.nuuneoi.com/special/user/list")
Call<Users> loadSpecialUsers();
}
Base URL will be ignored for this case.
just use like this
public interface UserService {
#GET
public Call<ResponseBody> profilePicture(#Url String url);
}
source
With Retrofit 2 you have to put base url anyway. If it is not known then you just can put any url, usually, it is good to use http://localhost/.
retrofit version :2.9.0 | gson version / okhttp : auto-set by retrofit
The original solution did not worked for me, as my query was using GET . But here's something that might work for you :
interface DownloaderApi {
#GET(".")
fun getData(): Call<List<TeacherDto>>
}
and
class TeacherDataDownloader {
private val downloaderApi: DownloaderApi
init {
val convertor = Gson()
val convertorFactory = GsonConverterFactory.create(convertor)
val retrofit =
Retrofit.Builder().baseUrl(FULL_URL).addConverterFactory(convertorFactory).build()
downloaderApi = retrofit.create(DownloaderApi::class.java)
}
fun getListOfTeachersSync(): List<TeacherDto>? = downloaderApi.getData().execute().body()
}
This solution works for me partially , because some of the links that I use ends with / (eg: const val FULL_URL = "https://jsonplaceholder.typicode.com/users/" ). However this solution only works for APIs ending with a /
For URLS not ending with a / (eg : github gists) , you have to use the default methods and pass the end-path as parameter.
If you're okay with creating a new RestAdapter instance each time you want to make an api call, you could create a new adapter with the base url as a paramter.
Using absolute URLs with Retrofit
Yes you can do. I have done like this:
public APIHelper(BaseActivity activity) {
String url = Common.getAPIUrl();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(url)
.build();
methods = restAdapter.create(IAppService.class);
}
Basically you need to create object of retrofit once you have known the endpoint url. I guess no more explanation is needed as code is self explanatory.
Assuming that only base url is getting changed.
My way to use retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.8'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
And here i have a generic class and inside i have my method
public static AppServiceApi setMyRetrofit(){
Retrofit retrofit = null;
AppServiceApi appServices = null;
if (retrofit == null){
Gson gson = new GsonBuilder()
.setLenient()
.create(); //
retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(getClient())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
appServices = retrofit.create(AppServiceApi.class);
}
return appServices;
}
public static OkHttpClient getClient(){
HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
/*.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)*/.build();
return client;
}
where AppServiceApi interface have all my end urls and method type