Retrofit 2 - How to make request without Call object - java

I use retrofit 2 and I have UserService with rest methods which return objects Call.
I would like to invoke these methods and return just data object.
I have this:
#GET("users")
Call<List<UserDTO>> getUsers();
but I want:
#GET("users")
List<UserDTO> getUsers();
I know that was possible by default in retrofit 1.9 but i couldn't find solution for this problem.
I dont want invoke method, execute call, get body and make try..catch every time when I use it.
When I invoke method from my second example I receive error:
Could not locate call adapter for java.util.List<>
Is it possible to handle this case in any adapter? And how to do it ?

I resolved this problem like that:
public class CustomCallAdapter<T> implements CallAdapter<T, T> {
private Type returnType;
public CustomCallAdapter(Type returnType) {
this.returnType = returnType;
}
#Override
public Type responseType() {
return returnType;
}
#Override
public T adapt(Call<T> call) {
try {
return call.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static class Factory extends CallAdapter.Factory {
#Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new CustomCallAdapter(returnType);
}
}
}

Related

How to mock the static method with mockito to do unit test

I have a method like this.
public Response post(String json) {
EventList list = Recorder.getRecorders();
if (null == list || list.isEmpty()) {
throw new ServiceUnavailableException("An Recorder is either not configured");
}
String targetIUrl = list.getNext().getBase();
String targetV2Url = targetUrl + "/v2";
// other processes......
}
I want to mock Recorder.getRecorder() and do something like when(Recorder.getRecorder()).thenReturn(null) and test if throw a 503 exception. But getRecorder() is a static method. I know Mockito cannot mock the static method, but I still wanna know if it is possible to change some code made this testable without using Powermock or other libraries.
If I mock Recorder, do I have to change the method to post(String json, Recorder recorder)? Otherwise, how can I make this mock interact with the method?
If you want to mock the getRecorders() behaviour without using a library for mocking static methods (such as Powermock) then you'll have to extract the static call from inside post(). There are a few options for this:
Pass the EventList into post()
public post(String json, EventList list) {
...
}
Inject the EventList into the class which contains post()
public class TheOneThatContainsThePostMethod {
private EventList eventList;
public TheOneThatContainsThePostMethod(EventList eventList) {
this.eventList = eventList;
}
public post(String json) {
if (null == this.eventList || this.eventList.isEmpty()) {
throw new ServiceUnavailableException("An Recorder is either not configured");
}
}
}
Hide the static method call inside another class and inject an instance of that class into post() or the class which contains post(). For example:
public class RecorderFactory {
public EventList get() {
return Recorder.getRecorders();
}
}
public class TheOneThatContainsThePostMethod {
private RecorderFactory recorderFactory;
public TheOneThatContainsThePostMethod(RecorderFactory recorderFactory) {
this.recorderFactory = recorderFactory;
}
public post(String json) {
EventList list = recorderFactory.getRecorders();
...
}
}
// Or ...
public post(String json, RecorderFactory recorderFactory) {
EventList list = recorderFactory.getRecorders();
...
}
With the first two approaches your test can simply invoke post() providing (1) a null EventList; (2) an empty EventList ... thereby allowing you to test the 'throw a 503 exception' behaviour.
With the third approach you can use Mockito to mock the behaviour of the RecorderFactory to return (1) a null EventList; (2) an empty EventList ... thereby allowing you to test the 'throw a 503 exception' behaviour.

Android: Generic Retrofit 2

i try this for just 1 time create retrofit but i have error
i want call my retrofit class and give endPoint of url and body class , and get body from server clearly
ApiClient
public class ApiClient {
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(App.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
ApiService
public interface ApiService {
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
}
Retrofit Object
public class Request<C,T> {
private C c = null;
public C rest(T body, String urlEndPoint) {
ApiService apiService = ApiClient.getClient().create(ApiService.class);
Call<C> call = apiService.request(body, urlEndPoint);
call.enqueue(new Callback<C>() {
#Override
public void onResponse(Call<C> call, Response<C> response) {
if (response.isSuccessful())
c = response.body();
else
Toaster.shorter(App.context.getString(R.string.serverError));
#Override
public void onFailure(Call<C> call, Throwable t) {
Toaster.shorter(App.context.getString(R.string.connectionError));
}
});
return c;
}
}
calling method:
private void requestForCode() {
Request request = new Request();
int i = (int) request.rest(App.car, "/Rest/ReturnActivationCode");
if (i == 0)
Toaster.longer(App.context.getString(R.string.validateYourNumber));
else
Toaster.shorter(App.context.getString(R.string.serverError));
}
error:
12-05 12:18:04.119 773-907/? E/ConnectivityService: RemoteException caught trying to send a callback msg for NetworkRequest [ id=535, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
12-05 12:18:09.575 10359-10359/com.rayanandisheh.peysepar E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rayanandisheh.peysepar, PID: 10359
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<C>
for method ApiService.request
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:746)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:229)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.request(Unknown Source)
retrofit don't support generic objects???
It seems that you're trying to minimize your boilerplate by having a generic function to be called, but there's a better way to do this.
First, you're encapsulating the retrofit setup with your:
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
And then you're calling it with the function you created:
request.rest(App.object1, "endpoint");
But actually, this will just make things complicated and the code is very tightly coupled. You will still need to call the same method on every different APIs (request.rest(App.object2, "endpoint2"), request.rest(App.object3, "endpoint3")). This also limits the capability of retrofit (such as multiple params, customize headers, etc). What you can do is just follow the setup of retrofit:
#POST("yourendpoint")
Call<YourObjectResp> saveObject(#Body YourObjectParam param)
And to minimize your boilerplate, I suggest to make it functional:
Call<YourObjectResp> call = apiService.saveObject(new YourObjectParam());
call.enqueue(new ApiServiceOperator<>(new
ApiServiceOperator.OnResponseListener<YourObjectResp>() {
#Override
public void onSuccess(YourObjectResp body) {
// do something with your response object
}
#Override
public void onFailure(Throwable t) {
// here, you can create another java class to handle the exceptions
}
}));
And for your ApiServiceOperator.java:
/**
* Handles retrofit framework response.
* Extract the body if success, otherwise throw an exception.
*/
public class ApiServiceOperator<T> implements Callback<T> {
interface OnResponseListener<T> {
void onSuccess(T body);
void onFailure(Throwable t);
}
private OnResponseListener<T> onResponseListener;
public ApiServiceOperator(OnResponseListener<T> onResponseListener) {
this.onResponseListener = onResponseListener;
}
#Override
public void onResponse(#NonNull Call<T> call, #NonNull Response<T> response) {
if (response.isSuccessful()) { // here, do the extraction of body
onResponseListener.onSuccess(response.body());
} else {
onResponseListener.onFailure(new ServerErrorException());
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
onResponseListener.onFailure(new ConnectionErrorException());
}
// these exception can be on a separate classes.
public static class ServerErrorException extends Exception {
}
public static class ConnectionErrorException extends Exception {
}
}
With these setup, you still minimize your boilerplate and also, it makes thing reusable, scalable, and testable. ApiServiceOperator also is loosely couple with Android Context and instead, throws a plain java exception, in which, you can create a function that knows Android Context to get the appropriate message base on the exception thrown.

Retrofit 2 unwrap envelope error

I am using an API that has basically 2 returns:
A single object:
{
"data": {Foo}
}
Or a list of objects:
{
"data": [
{Bar},
{Bar},
...
]
}
So, I have created 2 envelope class
class Envelope<T> {
private T data;
public T getData() {
return data;
}
}
class EnvelopeList<T> {
private List<T> data;
public List<T> getData() {
return data;
}
}
and the service interface
#GET(PATH)
Call<Envelope<Foo>> get();
#GET(PATH_LIST)
Call<EnvelopeList<Bar>> getList();
Using the retrofit 2 config
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
everything is working fine...
If I remove the envelope from service like this
#GET(PATH)
Call<Foo> get();
#GET(PATH_LIST)
Call<List<Bar>> getList();
the first that return only an object still working but the one that returns a List gives the error java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
So, I tried to create a converter
public class EnvelopeListConverterFactory extends Converter.Factory {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(
final Type type,
Annotation[] annotations,
Retrofit retrofit) {
Type envelopeType = new ParameterizedType() {
#Override
public Type[] getActualTypeArguments() {
return new Type[]{type};
}
#Override
public Type getRawType() {
return null;
}
#Override
public Type getOwnerType() {
return EnvelopeList.class;
}
};
Converter<ResponseBody, EnvelopeList> delegate =
retrofit.nextResponseBodyConverter(this, envelopeType, annotations);
return new EnvelopeListConverter(delegate);
}
}
public class EnvelopeListConverter<T> implements Converter<ResponseBody, List<T>> {
final Converter<ResponseBody, EnvelopeList<T>> delegate;
EnvelopeListConverter(Converter<ResponseBody, EnvelopeList<T>> delegate) {
this.delegate = delegate;
}
#Override
public List<T> convert(ResponseBody responseBody) throws IOException {
EnvelopeList<T> envelope = delegate.convert(responseBody);
return envelope.getData();
}
}
and if I create the retrofit build like this
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(new EnvelopeListConverterFactory());
I still get the same error as before, but if invert the converter order like
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(new EnvelopeListConverterFactory())
.addConverterFactory(GsonConverterFactory.create());
the error change to java.lang.IllegalArgumentException: Unable to create a converter for class Bar for method Service.getList and the one that returns a single Object, start to give the same error too.
What can I do to make the service return the Objects without the envelope?
Update
I think I passed the wrong idea on my question. The problem is not that the request can return a single object or a list. I just trying to pass thru the envelope and get the data directly.
I trying to do this http://f2prateek.com/2017/04/21/unwrapping-data-with-retrofit-2/ but it is not working
I finally found how to create a correct converter class to not need to use the envelope/wrapper on every request.
https://hackernoon.com/retrofit-converter-for-wrapped-responses-8919298a549c

Optionally inject ContainerRequestContext

In my Jersey application, I'd like to have a ContainerRequestContext instance injected into various objects. In the case that the object in being created outside of the context of a request, I would like null to be injected.
I noticed HK2 has an #Optional annotation that you can annotate dependencies with, and I was hoping that would do the job for, unfortunately it doesn't change the behaviour at all.
public class MyObject {
private final ContainerRequestContext containerRequestContext;
#Inject
public MyObject(#Optional ContainerRequestContext containerRequestContext) {
this.containerRequestContext = containerRequestContext;
}
}
If this object is instantiated outside of a request scope (in my case, a Job run by a Quartz scheduler), then an exception like this gets thrown:
java.lang.IllegalStateException: Not inside a request scope.
It would massively simplify my code if Jersey would just inject null when outside of a request scope, any ideas how to do this?
I've figured out a way of doing it, but it's basically a hack. Instead of having ContainerRequestContext injected, you can instead try to explicitly get a ContainerRequestContext instance from the ServiceLocator, and handle the exception when the context is outside of a request scope.
public class MyObject {
private final Optional<ContainerRequestContext> containerRequestContext;
#Inject
public MyObject(ServiceLocator serviceLocator) {
this.containerRequestContext = getContainerRequestContext(serviceLocator);
}
private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
try {
return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
} catch (MultiException e) {
if (e.getCause() instanceof IllegalStateException) {
return Optional.empty();
} else {
throw new ExceptionInInitializerError(e);
}
}
}
}
It's then possible to go one step further and create your own OptionalContainerRequestContext type.
public class OptionalContainerRequestContext {
private final Optional<ContainerRequestContext> containerRequestContext;
#Inject
public OptionalContainerRequestContext(ServiceLocator serviceLocator) {
this.containerRequestContext = getContainerRequestContext(serviceLocator);
}
public ContainerRequestContext get() {
return containerRequestContext.get();
}
public boolean isPresent() {
return containerRequestContext.isPresent();
}
private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
try {
return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
} catch (MultiException e) {
if (e.getCause() instanceof IllegalStateException) {
return Optional.empty();
} else {
throw new ExceptionInInitializerError(e);
}
}
}
}
You can then bind it:
bind(OptionalContainerRequestContext.class).to(OptionalContainerRequestContext.class);
And then inject it wherever you need:
public class MyObject {
private final OptionalContainerRequestContext optionalContainerRequestContext;
#Inject
public MyObject(OptionalContainerRequestContext optionalContainerRequestContext) {
this.optionalContainerRequestContext = optionalContainerRequestContext;
}
}
The simple way to deal with optional injection is to #Inject into javax.enterprise.inject.Instance<T>, and then to call instance.isUnsatisfied() before instance.get().

JAX-RS jersey ExceptionMappers User-Defined Exception

I am new to this, trying to achieve reading some docs but its not working, please bear with me.
I have created a UserNotFoundMapper using ExceptionMappers like this:
public class UserNotFoundMapper implements ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException ex) {
return Response.status(404).entity(ex.getMessage()).type("text/plain").build();
}
}
This in my service:
#GET
#Path("/user")
public Response getUser(#QueryParam("id") String id) throws UserNotFoundException{
//Some user validation code with DB hit, if not found then
throw new UserNotFoundException();
}
The UserNotFoundException is an User-Defined Exception.
I tried this:
public class UserNotFoundException extends Exception {
//SOME block of code
}
But when I invoke the service, the UserDefinedExceptionMapper is not getting invoked. It seems I might be missing something in the UserDefinedException. How to define this exception then?
Please let me know how to define the UserNotFoundException.
You need to annotate your exception mapper with #Provider, otherwise it will never get registered with the JAX-RS runtime.
#Provider
public class UserNotFoundMapper implements
ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException ex) {
return Response.status(404).entity(ex.getMessage()).type("text/plain")
.build();
}
}
What I usually do when creating APIs is create my own exception that extends from RuntimeException so I don't necessarily have to catch my exception.
Here's an example:
NOTE: I'm using JAX-RS with Jersey
First: create my own Exception that extends from RuntimeException.
public class ExceptionName extends RuntimeException {
private int code;
private String message;
public int getCode(){
return code;
}
public String getMessage(){
return message;
}
public ExceptionName(int code, String message) {
this.code = code;
this.message = message;
}
}
Also implement a ExceptionMapper
#Provider
public class ExceptionName implements ExceptionMapper<ExceptionName>{
#Override
public Response toResponse(ExceptionName exception) {
return Response.status(exception.getCode()).entity(exception.getMessage()).build();
}
}
And every time that I want to throw an exception I just do it like this anywhere, the exception mapper will take care of returning a response to the client consuming the API
throw new ExceptionName(500,"there was an error with something here");
One small remark , try to Use Response.Status.NOT_FOUND rather than using 404 etc. Code will be more readable and less prone to typos , the same goes for "text/plain". Below is the code that will handle exception as you mentioned.
Oh and one more thing remember to annotate your method #Produces(MediaType.TEXT_PLAIN) in your interface
public class UserNotFoundException extends Exception {
//...
}
public class UserServiceImpl implements UserService {
#Override
public Response getUser(#QueryParam("id") String id) {
final Response response;
try{
// call user method
//if everything is ok
response = Response.status(Response.Status.OK).entity(whateverYouWant).type(MediaType.TEXT_PLAIN).build();
} catch(UserNotFoundException ex) {
response = new UserNotFoundMapper().toResponse(ex);
}
return response;
}
}
In client slide you can check
public static boolean isUserExists(final Response serverResp) {
return serverResp != null && serverResp.getStatus() == Response.Status.NOT_FOUND.getStatusCode();
}

Categories