Issue
I have this retrofit method for an API:
@POST("/searchCity/byname")
Call<Cities> searchCityByName(@Query("name") String name);
It being called this way from the code:
final String cityName ="City"
restClient.getApiService().searchCityByName(cityName);
And whenever I send a request with retrofit 2.1.0 the url is the following one (correct):
searchCity/byname?name=City
However, when I send the same request using retrofit 2.2.0 and above the url changes to (incorrect):
searchCity/byname?name=%22City%22
To that point, the server fails when processing the request due to the encoded double quotes (%22 decodes to ").
I have followed the program flow and I found nothing... What could be happening?
I need to use retrofit 2.3.0 while having the first URL (the one without the quotes). Is there any way to achieve it?
EDIT - 03/Apr/2018:
Here are the used classes:
RetrofitClient.java
@EBean(scope = EBean.Scope.Singleton)
public class RetrofitClient {
private static volatile Retrofit retrofit;
private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl("https://exampleserver.com/");
private PrivateApi apiService;
public static <S> S createService(Class<S> serviceClass) {
final GsonBuilder gsonBuilder = new GsonBuilder();
final Gson gson = gsonBuilder.create();
retrofit = builder.client(new WebClient().getClient())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit.create(serviceClass);
}
@AfterInject
public void privateRestClientAfterInject() {
final PrivateApi apiService = createService(PrivateApi.class);
final PrivateApiServiceInvocationHandler publicApiServiceInvocationHandler = new
PrivateApiServiceInvocationHandler(apiService);
this.apiService = (PrivateApi) Proxy.newProxyInstance(PrivateApi.class.getClassLoader(),
new Class[]{PrivateApi.class}, publicApiServiceInvocationHandler);
}
public PrivateApi getApiService() {
if (apiService == null) {
privateRestClientAfterInject();
}
return apiService;
}
}
WebClient.java
public class WebClient {
public OkHttpClient getClient() {
final HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor
.Level.BODY);
final OkHttpClient.Builder builder = new OkHttpClient.Builder().readTimeout
(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.addInterceptor(logging)
.followRedirects(false)
.followSslRedirects(false);
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
final Request originalRequest = chain.request();
final HttpUrl newurl = originalRequest.url().newBuilder().build();
final Request newRequest = originalRequest.newBuilder()
.url(newurl)
.headers(originalRequest.headers())
.header("Connection", "close")
.header("User-Agent",
getUserAgentAndKeyboard())
.method(originalRequest.method(), originalRequest.body())
.build();
return chain.proceed(newRequest);
}
});
return builder.build();
}
/**
* Returns the standard user-agent with the default input method brand name
*
* @return The concatenated "user-agent; keyboard" string
*/
private static String getUserAgentAndKeyboard() {
// WEWE-857
ContentResolver contentResolver = BaseApplication.getInstance().getContentResolver();
if (contentResolver != null) {
String keyboard = Settings.Secure.getString(contentResolver, Settings.Secure
.DEFAULT_INPUT_METHOD);
return String.format(Locale.getDefault(), "%s;%s", System.getProperty("http.agent"),
keyboard);
}
return System.getProperty("http.agent");
}
}
RetrofitInvocationHandler.java
public class RetofitInvocationHandler implements InvocationHandler {
private final Object target;
public RetofitInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
PrivateApi.java
public interface PrivateApi {
@Headers({
"Content-Type:application/json"
})
@POST("/searchCity/byname")
Call<Cities> searchCityByName(@Query("name") String name);
}
Solution
Finally I got the solution:
Just had to add a custom converter to re-write the query params:
public static <S> S createService(Class<S> serviceClass) {
final GsonBuilder gsonBuilder = new GsonBuilder();
final Gson gson = gsonBuilder.create();
retrofit = builder.client(new WebClient().getClient())
.addConverterFactory(GsonConverterFactory.create(gson))
// custom interceptor
.addConverterFactory(new StringConverterFactory(GsonConverterFactory.create(gson)))
// custom interceptor
.build();
return retrofit.create(serviceClass);
}
StringCoverterFactory.java
public class DateStringConverterFactory extends Converter.Factory {
private final Converter.Factory delegateFactory;
DateStringConverterFactory(Converter.Factory delegateFactory) {
super();
this.delegateFactory = delegateFactory;
}
@Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit
retrofit) {
for (final Annotation annotation : annotations) {
if (annotation instanceof Query) {
final Converter<?, RequestBody> delegate = delegateFactory
.requestBodyConverter(type, annotations, new Annotation[0], retrofit);
return new DelegateToStringConverter<>(delegate);
}
}
return null;
}
static class DelegateToStringConverter<T> implements Converter<T, String> {
private final Converter<T, RequestBody> delegate;
DelegateToStringConverter(Converter<T, RequestBody> delegate) {
this.delegate = delegate;
}
@Override
public String convert(T value) throws IOException {
final okio.Buffer buffer = new okio.Buffer();
delegate.convert(value).writeTo(buffer);
if(value instanceof String){
return value.toString();
}
else {
return buffer.readUtf8();
}
}
}
}
Thanks to all that tried to contribute!!
Answered By - Rodri de Blas
Answer Checked By - Terry (JavaFixing Volunteer)