NotRetrofit


Source link: https://github.com/yongjhih/NotRetrofit

NotRetrofit (Experiment)

Contributors..

NotRetrofit turns your REST API into a Java interface.

square/retrofit is a great project. So, why reinvent the wheel? NotRetrofit is the first to implement the full stack with generated code. The guiding principle is to generate code that mimics the code that traceable and performant as it can be.

google/dagger2 has also re-implemented square/dagger.

NotRetrofit is a compile-time evolution approach to rest api conversion. Taking the approach started in Retrofit 1.x to its ultimate conclusion, NotRetrofit eliminates all reflection, and improves code clarity.

NotRetrofit has implemented almost retrofit’s features. And bonus:

For retrofit1 users: Migration.

And here is Live Demo.

Table of Contents

Usage

@Retrofit("https://api.github.com") public abstract class GitHub {

@GET("/users/{
user
}
/repos")
public abstract List<Repo> repos(@Path("user") String user);

 public static GitHub create() {

  return new Retrofit_GitHub();

}
 
}
GitHub github = GitHub.create();

Each call on the generated instance of GitHub makes an HTTP request to the remote webserver.

List<Repo> repos = github.repos("octocat");

Use annotations to describe the HTTP request:

  • URL parameter replacement and query parameter support
  • Object conversion to request body (e.g., JSON, protocol buffers)
  • Multipart request body and file upload

API Declaration

Annotations on the interface methods and its parameters indicate how a request will be handled.

REQUEST METHOD

Every method must have an HTTP annotation that provides the request method and relative URL. There are five built-in annotations: GET, POST, PUT, DELETE, and HEAD. The relative URL of the resource is specified in the annotation.

@GET("/users/list")

You can also specify query parameters in the URL.

@GET("/users/list?sort=desc")

URL MANIPULATION

A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and } . A corresponding parameter must be annotated with @Path using the same string.

@GET("/group/{
id
}
/users") abstract Observable<List<User>> groupList(@Path("id") int groupId);

Query parameters can also be added.

@GET("/group/{
id
}
/users") abstract Observable<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

For complex query parameter combinations a Map can be used.

@GET("/group/{
id
}
/users") abstract Observable<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

REQUEST BODY

An object can be specified for use as an HTTP request body with the @Body annotation.

@POST("/users/new") abstract Observable<User> createUser(@Body User user);

The object will also be converted using the converter.

FORM ENCODED AND MULTIPART

Methods can also be declared to send form-encoded and multipart data.

Form-encoded data is sent when @FormUrlEncoded is present on the method. Each key-value pair is annotated with @Field containing the name and the object providing the value.

@FormUrlEncoded @POST("/user/edit") abstract Observable<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

Multipart requests are used when @Multipart is present on the method. Parts are declared using the @Part annotation.

@Multipart @PUT("/user/photo") abstract Observable<User> updateUser(@Part("photo") TypedFile photo, @Part("description") TypedString description);

Multipart parts use the converter. In progress: or they can implement TypedOutput to handle their own serialization.

HEADER MANIPULATION

You can set static headers for a method using the @Headers annotation.

@Headers("Cache-Control: max-age=640000") @GET("/widget/list") abstract Observable<List<Widget>> widgetList();
@Headers({

  "Accept: application/vnd.github.v3.full+json",
  "User-Agent: Retrofit2" 
}
) @GET("/users/{
username
}
") abstract Observable<User> getUser(@Path("username") String username);

Note that headers do not overwrite each other. All headers with the same name will be included in the request.

A request Header can be updated dynamically using the @Header annotation. A corresponding parameter must be provided to the @Header. If the value is null, the header will be omitted. Otherwise, toString will be called on the value, and the result used.

@GET("/user") Observable<User> getUser(@Header("Authorization") String authorization);

Global Headers

Headers that need to be added to every request can be specified using @Headers on your service. The following code uses @Headers that will add a User-Agent header to every request.

@Retrofit("https://api.github.com") @Headers({

  "Accept: application/vnd.github.v3.full+json",
  "User-Agent: Retrofit2" 
}
) abstract class GitHub {

  // .. 
}

SYNCHRONOUS VS. ASYNCHRONOUS VS. OBSERVABLE

Methods can be declared for either synchronous or asynchronous execution.

A method with a return type will be executed synchronously.

@GET("/user/{
id
}
/photo") Photo getUserPhoto(@Path("id") int id);

Asynchronous execution requires the last parameter of the method be a Callback.

@GET("/user/{
id
}
/photo") void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
  // YourApp.java:  service.getUserPhoto(0, new Callback<Photo>() {

@Override public void success(Photo photo, Response response) {

}

@Override public void failure(RetrofitError e) {

}
 
}
);

Callbacks will happen on the same thread that executed the HTTP request.

Retrofit also integrates RxJava to support methods with a return type of rx.Observable

@GET("/user/{
id
}
/photo") Observable<Photo> getUserPhoto(@Path("id") int id);
  // YourApp.java:  service.getUserPhoto(0).subscribe(photo -> {

}
, e -> {

}
, () -> {

}
);

Observable requests are subscribed asynchronously and observed on the same thread that executed the HTTP request. To observe on a different thread (e.g. Android's main thread) call observeOn(Scheduler) on the returned Observable.

RESPONSE OBJECT TYPE

HTTP responses are automatically converted to a specified type using the RestAdapter's converter which defaults to JSON. The desired type is declared as the method return type or using the Callback or Observable.

@GET("/users/list") List<User> userList();
@GET("/users/list") void userList(Callback<List<User>> cb);
@GET("/users/list") Observable<List<User>> userList();

For access to the raw HTTP response use the Response type.

@GET("/users/list") Response userList();
@GET("/users/list") void userList(Callback<Response> cb);
@GET("/users/list") Observable<Response> userList();

Target Configuration

Retrofit\_TARGET is the class through which your API interfaces are turned into callable objects. By default, Retrofit2 will give you sane defaults for your platform but it allows for customization.

JSON CONVERSION

Retrofit2 uses LoganSquare by default to convert HTTP bodies to and from JSON. If you want to specify behavior that is different from Gson's defaults (e.g. naming policies, date formats, custom types), provide a new Gson instance with your desired behavior when building a Retrofit_TARGET. Refer to the Gson documentation for more details on customization.

CUSTOM GSON CONVERTER EXAMPLE

The following code creates a new Gson instance that will convert all fields from lower case with underscores to camel case and vice versa. It also registers a type adapter for the Date class. This DateTypeAdapter will be used anytime Gson encounters a Date field.

The gson instance is passed as a parameter to GsonConverter, which is a wrapper class for converting types.

public static class DateGsonConverter extends GsonConverter {

  public DateGsonConverter() {

super(new com.google.gson.GsonBuilder()

 .setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)

 .registerTypeAdapter(java.util.Date.class, new com.google.gson.internal.bind.DateTypeAdapter())

 .create());

  
}
 
}
  @Retrofit("https://api.github.com") @Converter(DateGsonConverter.class) abstract class GitHub {

  // .. 
}

Each call on the generated GitHub will return objects converted using the Gson implementation provided to the Retrofit_GitHub.

CUSTOM CONVERTER FOR METHOD

Specify another converter instance for one of methods by the following code:

@Retrofit("https://api.github.com") @Converter(DateGsonConverter.class) abstract class GitHub {

  @GET("/users/{
username
}
")
  @Converter(LoganSquareConverter.class)
  abstract Observable<User> getUser(@Path("username") String username);

// .. 
}

CONTENT FORMAT AGNOSTIC

In addition to JSON, Retrofit can be configured to use other content formats. Retrofit provides alternate converters for XML (using Simple) and Protocol Buffers (using protobuf or Wire). Please see the retrofit-converters directory for the full listing of converters.

The following code shows how to use SimpleXMLConverter to communicate with an API that uses XML

@Retrofit("https://api.github.com") @Converter(SimpleXMLConverter.class) abstract class GitHub {

  // .. 
}

CUSTOM CONVERTERS

If you need to communicate with an API that uses a content-format that Retrofit does not support out of the box (e.g. YAML, txt, custom format) or you wish to use a different library to implement an existing format, you can easily create your own converter. Create a class that implements the Converter interface and pass in an instance when building your adapter.

CUSTOM ERROR HANDLING

If you need custom error handling for requests, you may provide your own ErrorHandler. The following code shows how to throw a custom exception when a response returns a HTTP 401 status code

@Retrofit("https://api.github.com") @ErrorHandler(MyErrorHandler.class) class GitHub {

  // .. 
}
public class MyErrorHandler implements ErrorHandler {

  @Override public Throwable handleError(RetrofitError cause) {

Response r = cause.getResponse();

if (r != null && r.getStatus() == 401) {

 return new RuntimeException("401", cause);

}

return cause;
  
}
 
}

Note that if the return exception is checked, it must be declared on the interface method. It is recommended that you pass the supplied RetrofitError as the cause to any new exceptions you throw.

LOGGING

If you need to take a closer look at the requests and responses you can easily add logging levels to the Retrofit_GitHub with the LogLevel property. The possible logging levels are BASIC, FULL, HEADERS, and NONE.

The following code shows the addition of a full log level which will log the headers, body, and metadata for both requests and responses.

@Retrofit("https://api.github.com") @LogLevel(LogLevel.FULL) abstract class GitHub {

  // .. 
}

Support @RetryHeaders

Experiment feature

For Retry Stale example:

@Retrofit("https://api.github.com") @RetryHeaders("Cache-Control: max-age=640000") abstract class GitHub {

  // .. 
}

Retry the request with cache if network issue.

Support @RequestInterceptor

@Retrofit("https://api.github.com") @RequestInterceptor(MyRequestInterceptor.class) abstract class GitHub {

  // .. 
}

Dynamic URL

@Retrofit("https://api.github.com") public abstract class GitHub {

@GET("/repos/{
owner
}
/{
repo
}
/contributors")
public abstract Observable<List<Contributor>> contributorList(

 @Path("owner") String owner,

 @Path("repo") String repo);

 @GET("{
url
}
")
public abstract Observable<List<Contributor>> contributorListPaginate(@Path("url") String url);

//.. 
}

Authentication for android

@Retrofit("https://api.github.com") public abstract class GitHub {

@RequestInterceptor(GitHubAuthInterceptor.class)
@GET("/repos/{
owner
}
/{
repo
}
/contributors")
public abstract Observable<List<Contributor>> contributorList(

 @Path("owner") String owner,

 @Path("repo") String repo);

//.. 
}
@Singleton public class GitHubAuthInterceptor extends AuthenticationInterceptor {

@Override
  public String accountType() {

//return context().getString(R.string.account_type);


return "com.github";
  
}

@Override
  public String authTokenType() {

//return context().getString(R.string.auth_token_type);


return "com.github";
  
}

@Override
  public void intercept(String token, RequestFacade request) {

if (token != null) request.addHeader("Authorization", "Bearer " + token);

  
}
  
}

Migration

  1. Add @Retrofit("https://api.github.com") line
  2. Change interface GitHub to abstract class GitHub
  3. Add public static GitHub create() { return new Retrofit_GitHub(); }
  4. Use @Retrofit.Body, @Retrofit.Part, @Retrofit.Field instead of @Body, @Part, @Field.

For example:

@Retrofit("https://api.github.com") // 1. Add this line abstract class GitHub {
 // 2. Change to abstract class
@GET("/users/{
user
}
/repos")
List<Repo> listRepos(@Path("user") String user);

public static GitHub create() {
 return new Retrofit_GitHub();
 
}
 // 3. Add creator 
}
  Github github = GitHub.create();

Another way:

@Retrofit("https://api.github.com") abstract class GitHubClient implements GitHub {

public static GitHubClient create() {
 return new Retrofit_GitHubClient();
 
}
 
}
  GitHubClient github = GitHubClient.create();

Cache (Experiment)

@Cache(SimpleCache.class)  public SimpleCache extends Cache {

  public SimpleCache() {

super.Cache(application.getExternalCacheDir(), 10 * 1024 * 1024);

  
}
 
}

or

@Cache(dir = "", cacheSize = 10 * 1024 * 1024)

Builder (Experiment)

@Retrofit("https://api.github.com") abstract class GitHub {

// ..
 @Builder
public abstract static class Builder {

  public abstract Builder baseUrl(String baseUrl);

  public abstract Builder converter(Converter converter);

  public abstract Builder requestInterceptor(RequestInterceptor requestInterceptor);

  public abstract Builder errorHandler(ErrorHandler errorHandler);

  public abstract Builder headers(String... headers);

  public abstract Builder retryHeaders(String... headers);

  public abstract Builder logLevel(LogLevel logLevel);

  public abstract Builder context(Object context);

  public abstract Builder cache(Cache cache);
 // if no OkHttpClient be set
  public abstract Builder okHttpClient(OkHttpClient client);

  public abstract GitHub build();

}

 public static Builder builder() {

  return new Retrofit_GitHub.Builder();

}
 
}

@Auth

@BaseUrl

@Mock

@HttpStack

@Trust

@Retry

@Retry(3) abstract Observable<Repo> repos();

@Timeout

@Timeout(1000) abstract Observable<Repo> repos();

@RetryPolicy

@RetryPolicy(timeout = 1000, retry = 3, backoff = 1.3f) abstract Observable<Repo> repos();

@OkHttpClient

@OkHttpClient(AllTrustedOkHttpClienter.class) abstract class GitHub {

// ... 
}
  public class AllTrustedOkHttpClienter implements OkHttpClienter {

@Override OkHttpClient get() {

  // ...
  return okHttpClient;

}
 
}

Call

TODO

Installation

via jcenter:

repositories {

  jcenter() 
}
  dependencies {

  compile 'com.infstory:retrofit:2.0.5'
  apt 'com.infstory:retrofit-processor:2.0.5'
  //compile 'com.infstory:retrofit-android:2.0.5' // optional, retrofit.android.AuthenticationInterceptor;

compile 'com.bluelinelabs:logansquare:1.1.0'
  apt 'com.bluelinelabs:logansquare-compiler:1.1.0' 
}

or via jitpack:

repositories {

  jcenter()
  maven {
 url "https://jitpack.io" 
}
 
}
  dependencies {

  compile 'com.github.yongjhih.NotRetrofit:retrofit:-SNAPSHOT'
  apt 'com.github.yongjhih.NotRetrofit:retrofit-processor:-SNAPSHOT'
  //compile 'com.github.yongjhih.NotRetrofit:retrofit-android:-SNAPSHOT' // optional, retrofit.android.AuthenticationInterceptor;

compile 'com.bluelinelabs:logansquare:1.1.0'
  apt 'com.bluelinelabs:logansquare-compiler:1.1.0' 
}

Deploy

./gradlew bintrayUpload

Live Demo

Test

Test github client:

./gradlew clean :retrofit2-github:testDebug

All tests:

./gradlew clean test

Github sample app:

./gradlew clean :retrofit2-github-app:assembleDebug

Development

References

See Also

Credit

  • Square, Inc.

License

Copyright 2013 Square, Inc. Copyright 2015 8tory, Inc.  Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 

Resources

A circular seekbar for android that looks like analog contorls in equalizers and other audio equipments.

Loupe effect (Magnifying glass). Nice zoom for your images.

A library for delayed scrolling.

Android reactive MVP stack.

The library encourages developers to use Android Clean MVP architecture pattern.

An abstract image loader for Android.

You don't need to type the whole text with this custom version of EditText. You just type some and it will suggest the remaining part.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes