Rest API

Kay-Uwe Janssen edited this page Oct 9, 2016 · 65 revisions

Since AndroidAnnotations 2.2

Introduction

The REST API allows you to quickly write REST clients using clean interfaces.

It is a wrapper around the great Spring Android RestTemplate Module. As such, if you want to use the REST API, you should make sure that your application has the right Jars or Maven dependencies set.

You should also read the Spring Android RestTemplate Module Documentation, to know more about entity serialization / deserialization and how the RestTemplate works.

Since AndroidAnnotations 3.3

From this release, you can use Spring for Android version 2.0. Only 1.0 can be used with older AndroidAnnotations releases.

Since AndroidAnnotations 4.0.0

Since this release AA is modularized, and the REST functionality can be found in a plugin. To use the annotations listed below, you must add the rest-spring plugin your project.

Create your REST API interface

The Rest API works with a @Rest annotated interface. It's the entry point.

Converters

You MUST define converters field on this @Rest annotation, which corresponds to the Spring HttpMessageConverters that will be provided to the RestTemplate.

@Rest(converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

If multiple converters are defined, then Spring will run through them until it finds one able to handle the requests mime type. For example:

@Rest(converters = { MappingJackson2HttpMessageConverter.class, StringHttpMessageConverter.class })

Will first try to map the request using the MappingJackson2HttpMessageConverter which can only handle application/json. If the MappingJackson2HttpMessageConverter cannot map the request, then it will be passed to StringHttpMessageConverter which accepts all mime types.

Important note: Keep in mind that HttpMessageConverters are provided by Spring Android RestTemplate, but it DOESN'T contains the implementation of the converter. For example : MappingJackson2HttpMessageConverter is using Jackson as converter but you still have to add this library in the classpath of you project.

Root url

You will usually define a ROOT_URL in a @Rest annotation,

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

but you can omit the ROOT_URL and define a complete URL on each method.

@Rest(converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
    @Get("http://company.com/ajax/services/events")
    EventList getEvents();
}

Interceptors

Since AndroidAnnotations 3.0

It may be useful to add an interceptor to your Rest client. You can do that with the interceptors field in @Rest annotation.

@Rest(converters = { MappingJackson2HttpMessageConverter.class }, interceptors = { HttpBasicAuthenticatorInterceptor.class })
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

public class HttpBasicAuthenticatorInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] data, ClientHttpRequestExecution execution) throws IOException {
        // do something
        return execution.execute(request, data);
    }
}

For example, this can be used to log each request or to handle custom authentication.

Before AndroidAnnotations 3.0

Before this version you can manualy add interceptors into RestTemplate instance by adding a getRestTemplate method in your interface. More information about this on this page.

Since AndroidAnnotations 3.1

It is possible to inject an [@EBean](Enhance custom classes) interceptor easily. Just add your annotated class to the interceptors parameter, and AA will inject the interceptor with all its dependencies.

@EBean
public class MyInterceptor implements ClientHttpRequestInterceptor {
    @RootContext
    Context context;

    // ...
}


@Rest(converters = { MappingJackson2HttpMessageConverter.class }, interceptors = { MyInterceptor.class })
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

RequestFactory

Since AndroidAnnotations 3.1

You can also set the RequestFactory of your choice. Just as for Interceptors, you can inject an [@EBean](Enhance custom classes) request factory.

@EBean
public class MyRequestFactory implements ClientHttpRequestFactory {
    @RootContext
    Context context;

    // ...
}


@Rest(converters = { MappingJackson2HttpMessageConverter.class }, requestFactory = MyRequestFactory.class)
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

ResponseErrorHandler

Since AndroidAnnotations 4.0.0

You can also set the ResponseErrorHandler of your choice. Just as for RequestFactory, you can inject an [@EBean](Enhance custom classes) response error handler or a simple response error handler class which has no-argument constructor.

@EBean
public class MyResponseErrorHandlerBean implements ResponseErrorHandler {
    @RootContext
    Context context;

    // ...
}

@Rest(converters = { MappingJackson2HttpMessageConverter.class }, responseErrorHandler = MyResponseErrorHandlerBean.class)
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

public class MyResponseErrorHandler implements ResponseErrorHandler {
    public MyResponseErrorHandler() {}

    // ...
}

@Rest(converters = { MappingJackson2HttpMessageConverter.class }, responseErrorHandler = MyResponseErrorHandler.class)
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
}

Error handler

Since AndroidAnnotations 3.0

By default, if an api call fails for any reason, an exception is thrown. Managing general exceptions like service unavailable or bad request for every call can be really tedious. Not doing it would make your app crash for all unexpected error. It's a good practice to override the default behavior, and this is done using a RestErrorHandler.

Implement your RestErrorHandler (it doesn't have to be an @EBean).

import org.androidannotations.api.rest.RestErrorHandler;

@EBean
public class MyErrorHandler implements RestErrorHandler {
    @Override
    public void onRestClientExceptionThrown(NestedRuntimeException e) {
        // Do whatever you want here.
    }
}

Make sure your rest client interface extends RestClientErrorHandling.

@Rest
public interface MyRestClient extends RestClientErrorHandling {
    ...
}

Then set the error handler where you use your rest client.

@RestService
MyRestClient myRestClient;

@Bean
MyErrorHandler myErrorHandler;

@AfterInject
void afterInject() {
    myRestClient.setRestErrorHandler(myErrorHandler);
}

Add REST API methods

Notes

Let's clarify some points before starting with REST API methods.

Placeholders

For each type of REST call you can add URL parameters by writing the parameter names in brackets and you MUST define them all as annotated parameters of your method.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  // OK
  @Get("/events/{year}/{location}")
  EventList getEventsByYearAndLocation(@Path int year, @Path String location);

  // OK
  @Get("/events/{year}/{location}")
  EventList getEventsByLocationAndYear(@Path String location, @Path int year);

  // WRONG
  @Get("/events/{year}/{location}")
  EventList getEventsByLocation(@Path String location); // Wrong, "year" must be defined with `@Path`.
}

@Path annotation

Since AndroidAnnotations 4.0.0

You can annotate your method parameters with the @Path annotation, to explicitly mark them as path variables. You MUST add the corresponding placeholder in the URL. You can also set a different URL variable name than the method parameter name, by using the @Path annotation parameter.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Get("/events/{year}/{location}")
  EventList getEventsByYearAndLocation(@Path int year, @Path String location);

  @Get("/events/{year}/{location}")
  EventList getEventsByLocationAndYear(String @Path("location") place, @Path int year);
}

Parameterized class

Since AndroidAnnotations 3.0

You can now return parameterized class (such as List<Event>) in your REST API methods.

Before AndroidAnnotations 3.0

Before this version you can't directly return a parameterized class but you can use a class extending of it.

@Get("/events/{year}/{location}")
EventList getEvents(@Path String location, @Path int year);

With

public class EventList extends List<Event> {
}

ResponseEntity

You can also return a ResponseEntity parameterized by the expected result type, which gives you access to to the response context. For instance, it could be useful to check the HTTP headers of the response.

@Get("/events/{year}/{location}")
ResponseEntity<Event> getEvents(@Path String location, @Path int year);

@Body

Since AndroidAnnotations 4.0.0

This annotation can be used to add a http message body parameter to the request from a method parameter. This annotation only can be used with a method which be annotated with @Post, @Put, @Delete or @Patch. Only one @Body parameter is acceptable in the method.

@Rest(rootUrl = "http://myserver", converters = MappingJackson2HttpMessageConverter.class)
public interface RestClient {

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Body String string);

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Body Event event);
}

@Field

Since AndroidAnnotations 4.0.0

This annotation can be used to add a form-encoded parameter to the request from a method parameter. The annotation value should be the name of the form-encoded parameter, if not specified, the method parameter name will be used as the name of the form-encoded parameter. This annotation only can be used with a method which be annotated with @Post, @Put or @Patch. To use this annotation, you must add FormHttpMessageConverter to the list of converters.

@Rest(rootUrl = "http://myserver", converters = FormHttpMessageConverter.class)
public interface RestClient {

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Field String eventName);

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Field("name") String eventName);
}

@Part

Since AndroidAnnotations 4.0.0

This annotation can be used to add a multi-part parameter to the request from a method parameter. The annotation value should be the name of the multi-part parameter, if not specified, the method parameter name will be used as the name of the multi-part parameter. This annotation only can be used with a method which be annotated with @Post, @Put or @Patch. To use this annotation, you must add FormHttpMessageConverter to the list of converters.

@Rest(rootUrl = "http://myserver", converters = FormHttpMessageConverter.class)
public interface RestClient {

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Part FileSystemResource image);

       @Post("/events/{id}")
       EventList addEvent(@Path String id, @Part("eventImage") FileSystemResource image);
}

@Get

Send a request with GET HTTP Method. The example shows the different possible return types.

Your method can return nothing,

@Get("/events/{id}")
void getEvent(@Path long id);

or a class representing the object contained in the HTTP response. The binding from JSON/XML to Object is automatically done by the RestTemplate according the defined converters, please check the related documentation.

@Get("/events/{id}")
Event getEvent(@Path long id);

@Post

Send a request with POST HTTP method.

The POST HTTP method is used to add objects to a REST resource. You only need to add a parameter representing the object to your annotated method. The allowed return types are the same as @Get.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Post("/events")
  void addEvent(@Body Event event);

  @Post("/events/{id}")
  void addEventById(@Body Event event, @Path long id);

  @Post("/events")
  Event addAndReturnEvent(@Body Event event);

  @Post("/events")
  ResponseEntity<Event> addAndReturnResponseEntity(@Body Event event);

}

Of course, you can send a POST request without sending any entity.

  @Post("/events")
  void addEvent();

A form can be posted by using FormHttpMessageConverter as a converter and passing a org.springframework.util.LinkedMultiValueMap containing the form data to the @Post method.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { FormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Post("/events/{id}")
  void submitEventForm(@Path long id, @Body LinkedMultiValueMap<String, String> formData);
}

A multipart POST request can be created in the following way, for example file upload:

@Rest(rootUrl = "http://company.com/ajax/services", converters = FormHttpMessageConverter.class)
public interface UploadClient extends RestClientHeaders {
    @Post("/Image/{id}")
    @RequiresHeader(HttpHeaders.CONTENT_TYPE)  
    String uploadImage(@Path int id, @Body MultiValueMap<String, Object> data);     
}
MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();

FileSystemResource image = new FileSystemResource("path_to_image");
String description = "description";
String title = "title";

data.set("image", image);
data.set("description", description);
data.set("title", title);

client.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);

client.uploadImage(1, data);

Note: you can use the @Part annotation to pass the parts to individual method parameters.

@Put

Send a PUT HTTP Method request. The allowed return types are the same as @Get.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Put("/events")
  void updateEvent(@Body Event event);

  @Put("/events/{id}")
  Event updateEventById(@Body Event event, @Path long id);

  @Put("/events")
  void updateEventNoEntity();

  @Put("/events/{id}")
  void updateEventNoEntityById(@Path long id);

}

@Patch

Send a request with PATCH HTTP method.

The PATCH HTTP method is used to add partial objects to a REST resource. You only need to add a parameter representing the object to your annotated method. The allowed return types are the same as @Get.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Patch("/events/{id}")
  void addEventById(@Body Event event, @Path long id);

}

@Delete

Send a DELETE HTTP Method request. Quite similar to the @Put annotation. The allowed return types are the same as @Get.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Delete("/events")
  void deleteEvents();

  @Delete("/events/{id}")
  Event deleteEventById(@Path long id);

  @Delete("/events/{id}")
  void deleteEventWithEntityById(@Body Event event, @Path long id);
}

@Options

Send a OPTIONS HTTP Method request. @Options annotated methods must return a set of HttpMethod.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Options("/events")
  Set<HttpMethod> getEventOptions();

  @Options("/events/{year}/{location}")
  Set<HttpMethod> getEventOptions(@Path String location, @Path int year);
}

@Head

Send a HEAD HTTP Method request. @Head annotated methods must return HttpHeaders.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Head("/events")
  HttpHeaders getEventHeader();
    
  @Head("/events/{year}/{location}")
  HttpHeaders getEventHeader2(@Path String location, @Path int year);

}

Configure your REST API methods

On each method you're able to add some interfaces to customize HTTP calls.

@Accept

You can negotiate the response format expected by your REST client (JSON, XML, TEXT, HTML...).

@Accept can only be used on @Get and @Post methods.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {

  @Get("/events/{id}")
  @Accept(MediaType.APPLICATION_JSON)
  Event getEvent(@Path long id);

  @Post("/entity")
  @Accept(MediaType.APPLICATION_XML)
  Event addEvent(@Body Event event);

}

You can directly annotate the @Rest interface but only @Get and @Post will use it.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
@Accept(MediaType.APPLICATION_XML)
public interface MyService {
}

Since AndroidAnnotations 2.7

@Accept accepts any String, so you can use custom formats e.g. @Accept("foo/bar").

@RequiresHeader / setHeader / getHeader

Since AndroidAnnotations 3.0

Inject a header in the request based on the given key.

To set the header's value you have to create a void setHeader(String name, String value) method in your REST API interface. It will be automaticaly handle and implemented by AA.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
  @Get("/events/{id}")
  @RequiresHeader("myHeader")
  Event getEvent(@Path long id);
  
  void setHeader(String name, String value);
  String getHeader(String name);
 }

In this example your code should call setHeader("myHeader", "My Value") before calling getEvent(…). AA will is also handle String getHeader(String name) method to let you retrieve a specific header's value.

@Header / Headers

Since AndroidAnnotations 4.0.0

You can add static headers to your requests by annotating them with the @Headers. If you just want to add one header, you can use @Header instead.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
  @Get("/events/{id}")
  @Header(name = "Cache-Control", value = "max-age=640000")
  Event getEvent(@Path long id);

  @Get("/events/")
  @Headers({
          @Header(name = "Cache-Control", value = "max-age=640000"),
          @Header(name = "keep-alive", value = "360")})
  Event getEvents();
  
  void setHeader(String name, String value);
  String getHeader(String name);
 }

@RequiresCookie / setCookie / getCookie

Since AndroidAnnotations 3.0

Inject a cookie in the request based on the given key.

To set the cookie's value you have to create a void setCookie(String name, String value) method in your REST API interface. It will be automaticaly handle and implemented by AA.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
  @Get("/events/{id}")
  @RequiresCookie("session")
  Event getEvent(@Path long id);
  
  void setCookie(String name, String value);
  String getCookie(String name);
 }

In this example your code should call setCookie("session", "MyUID") before calling getEvent(…). AA will is also handle String getCookie(String name) method to let you retrieve a specific cookie's value.

@RequiresCookieInUrl

Since AndroidAnnotations 3.0

Works exactly as @RequiresCookie but it will inject the cookie in the url instead of in headers. It could be helpful in some cases.

@RequiresAuthentication / setAuthentication / setHttpBasicAuth / setBearerAuth

Since AndroidAnnotations 3.0

Use the previously configured org.springframework.http.HttpAuthentication to inject authentication value in the Authorization request header.

To set the authentication object you have to create void setAuthentication(HttpAuthentication auth) and/or setHttpBasicAuth(String username, String password) and/or void setBearerAuth(String token) methods in your REST API interface. The first is a generic method, the second is just a shortcut for setAuthentication(new HttpBasicAuthentication(username, password)), the last is a shortcut for setAuthentication(new HttpAuthentication { public String getHeaderValue() { return ("Bearer "+ token); } } ). They will be automatically handled and implemented by AA.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
  @Get("/events/{id}")
  @RequiresAuthentication
  Event getEvent(@Path long id);
  
  setHttpBasicAuth(String username, String password);
 }

In this example your code should call setHttpBasicAuth("username", "pass") before calling getEvent(...)

@SetsCookie

Since AndroidAnnotations 3.0

This annotation is used to retrieve and store cookies from the HTTP response. After being set, the can be retrieve by @RequiresCookie and @RequiresCookieInUrl annotation and getCookie method.

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
  @Get("/events/{id}")
  @SetsCookie({"xt"})
  Event getEvent(@Path long id);
 }

Customize your REST API interface

AndroidAnnotation @Rest will automaticaly detect and handle some methods in your @Rest annotated interface. Some of them has already been described earlier and works with an annotation. Here are some other methods.

RootUrl

Since AndroidAnnotations 2.6

If you need to dynamicaly set ROOT_URL values, you can add a void setRootUrl(String rootUrl) method to the interface.

@Rest(converters = { MappingJackson2HttpMessageConverter.class })
public interface MyRestClient {
    @Get("/events")
    EventList getEvents();
    void setRootUrl(String rootUrl);
}

This can be useful if you need to create builds for different environments.

Since AndroidAnnotations 3.0

You can also retrieve the current ROOT_URL value by adding a String getRootUrl() method to the interface.

RestTemplate

The generated implementation of your annotated interface uses Spring Android RestTemplate. If you want to configure the RestTemplate to some specific needs, you can add getRestTemplate() and setRestTemplate() methods:

@Rest("http://company.com/ajax/services")
public interface MyService {
	RestTemplate getRestTemplate();
	void setRestTemplate(RestTemplate restTemplate);
}

Uses "bundle" interfaces

Since AndroidAnnotations 3.0

AndroidAnnotation provide some interfaces your REST API interface can extends of. Each of them provide handled methods and let you clean your code by using extends composition instead of writing methods.

@Rest("http://company.com/ajax/services")
public interface MyService extends RestClientRootUrl, RestClientSupport {
	…
}

RestClientRootUrl

Provide getRootUrl and setRootUrl methods. See above for more information.

RestClientSupport

Provide getRestTemplate and setRestTemplate methods. See above for more information.

RestClientHeaders

Provide setHeader, getHeader, getHeader, setHeader, setAuthentication, setHttpBasicAuth and setBearerAuth methods. See above for more information.

Using your REST client

Once your REST interface has been created you can inject it in any @Exxx annotated class with @RestService annotation.

@EActivity
public class MyActivity extends Activity {

	@RestService
	MyRestClient myRestClient; //Inject it

	@AfterViews
	void afterViews() {
		myRestClient.getEvents("fr", 2011); //Play with it
	}

}

Method based injection

Since AndroidAnnotations 4.0.0

@EActivity
public class MyActivity extends Activity {

  @RestService
  void setMyRestClient(MyRestClient myRestClient){
    // do something with myRestClient
  }

}

Handling errors

Spring Rest Template throws runtime exceptions when there's an error. Checked exceptions would add unwanted boilerplate code to every interface/implementation of REST clients.

If you want to handle HTTP errors from outside of your interface (to show error dialogs or to retry, for example), you can catch the RestClientException. (See documentation)

try {
  myRestClient.getEvents("fr", 2011);
} catch (RestClientException e) {
  // handle error for Rest client exceptions
}

If you just want to log the error for every calls you can just add an interceptor to @Rest (See above).

A Full REST API example

@Rest(rootUrl = "http://company.com/ajax/services", converters = { MappingJackson2HttpMessageConverter.class })
// if defined, the url will be added as a prefix to every request
public interface MyService extends RestClientHeaders {

	// url variables are mapped to method parameter names.
	@Get("/events/{year}/{location}")
	@Accept(MediaType.APPLICATION_JSON)
	EventList getEvents(@Path String location, @Path int year);

	// The response can be a ResponseEntity<T>
	@Get("/events/{year}/{location}")
	/*
	 * You may (or may not) declare throwing RestClientException (as a reminder,
	 * since it's a RuntimeException), but nothing else.
	 */
	ResponseEntity<EventList> getEvents2(@Path String location, @Path int year) throws RestClientException;

	@Post("/events/")
	@Accept(MediaType.APPLICATION_JSON)
	Event addEvent(@Body Event event);

	@Post("/events/{year}/")
	Event addEvent(@Body Event event, int year);

	@Post("/events/")
	ResponseEntity<Event> addEvent2(@Body Event event);

	@Post("/events/{year}/")
	@Accept(MediaType.APPLICATION_JSON)
	ResponseEntity<Event> addEvent2(@Body Event event, @Path int year);

	@Put("/events/{id}")
	void updateEvent(@Body Event event, @Path int id);

	// url variables are mapped to method parameter names.
	@Delete("/events/{id}")
	@RequiresAuthentication
	void removeEvent(@Path long id);

	@Head("/events/{year}/{location}")
	HttpHeaders getEventHeaders(@Path String location, @Path int year);

	@Options("/events/{year}/{location}")
	Set<HttpMethod> getEventOptions(@Path String location, @Path int year);

	// if you need to add some configuration to the Spring RestTemplate.
	RestTemplate getRestTemplate();
	
	void setRestTemplate(RestTemplate restTemplate);
}

14/10/2018 The 4.5.1 release is out !

Using AndroidAnnotations

Questions?

Enjoying AndroidAnnotations

Improving AndroidAnnotations

Extending AndroidAnnotations

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.