restio_retrofit 0.3.0 restio_retrofit: ^0.3.0 copied to clipboard
HTTP client generator for Restio inspired by Retrofit.
Retrofit #
HTTP client generator for Restio inspired by Retrofit.
Installation #
In pubspec.yaml
add the following dev dependencies:
dev_dependencies:
restio_retrofit: ^0.3.0
build_runner: ^1.10.0
Usage #
Define and Generate your API #
import 'package:restio/restio.dart';
import 'package:restio_retrofit/restio_retrofit.dart' as retrofit;
part 'httpbin.g.dart';
@retrofit.Api('https://httpbin.org')
abstract class HttpbinApi {
factory HttpbinApi({Restio client, String baseUri}) = _HttpbinApi;
@retrofit.Get('/get')
Future<String> get();
}
It is highly recommended that you prefix the retrofit library.
Then run the generator
# Dart
pub run build_runner build
# Flutter
flutter pub run build_runner build
Use It #
import 'package:restio/restio.dart';
import 'httpbin.dart';
void main(List<String> args) async {
final client = Restio();
final api = HttpbinApi(client: client);
final data = await api.get();
print(data);
}
Examples #
API Declaration #
Annotations on the methods and its parameters indicate how a request will be handled.
Http Methods #
Every method must have an HTTP Method annotation that provides the request method and relative URL. There are eight built-in annotations: Method
, Get
, Post
, Put
, Patch
, Delete
, Options
and Head
. The relative URL of the resource is specified in the annotation.
@retrofit.Get('/get')
You can also specify query parameters in the URL.
@retrofit.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 name surrounded by {
and }
. A corresponding parameter must be annotated with Path
. If the Path
name is omitted the parameter name is used instead.
@retrofit.Get('group/{id}/users')
Future<List<User>> groupList(@retrofit.Path('id') int groupId);
Query parameters can also be added.
@retrofit.Get('group/{id}/users')
Future<List<User>> groupList(@retrofit.Path('id') int groupId, @retrofit.Query() String sort);
For complex query parameter combinations a Map<String, ?>
, Queries
, List<Query>
can be used. In this case, annotate the parameters with Queries
.
You can set static queries for a method using the Query
annotation.
@retrofit.Query('sort' ,'desc')
Future<List<User>> groupList(@retrofit.Path('id') int groupId);
Note that queries do not overwrite each other. All queries with the same name will be included in the request.
@retrofit.Get('group/{id}/users')
Future<List<User>> groupList(@retrofit.Path('id') int groupId, @retrofit.Queries() Map<String, dynamic> options);
If you desire add queries with no values, you can use List<String>
.
@retrofit.Get('group/{id}/users')
Future<List<User>> groupList(@retrofit.Path('id') int groupId, @retrofit.Queries() List<String> flags);
Request Body #
An object can be specified for use as an HTTP request body with the Body
annotation.
@retrofit.Get('group/{id}/users')
Future<void> createUser(@retrofit.Body() User user);
The object will also be converted using a converter specified on the Api instance. If no converter is added, only File
, String
, List<int>
, Stream<List<int>>
or RequestBody
can be used.
Form and Multipart #
Methods can also be declared to send form-encoded and multipart data.
Form-encoded data is sent when Form
annotation is present on the method. Each key-value pair (field) is annotated with Field
containing the name (optional, the parameter name will be sed instead) and the object providing the value.
@retrofit.Form()
@retrofit.Post('user/edit')
Future<User> updateUser(@retofit.Field("first_name") String first, @retrofit.Field("last_name") String last);
You can set static field for a method using the Field
annotation.
@retrofit.Field('first_name' ,'Tiago')
@retrofit.Field('last_name' ,'Melo')
Future<User> updateUserEmail(@retofit.Field() String email);
Note that fields do not overwrite each other. All fields with the same name will be included in the request.
Multipart requests are used when Multipart
annotation is present on the method. Parts are declared using the Part
annotation.
@retrofit.Multipart()
@retrofit.Put('user/photo')
Future<User> updateUser(@retrofit.Part() File photo);
The parameter type can be only File
, String
, Part
or List<Part>
. For File
parameter type you can set the filename
and contentType
properties.
Header Manipulation #
You can set static headers for a method using the Header
annotation.
@retrofit.Header('Accept' ,'application/vnd.github.v3.full+json')
@retrofit.Header('User-Agent' ,'Retrofit')
@retrofit.Get('users/{username}')
Future<User> getUser(@retrofit.Path() 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 name is omitted, the parameter name will be used instead.
@retrofit.Get('user')
Future<User> getUser(@retrofit.Header('Authorization') String authorization);
Similar to query parameters, for complex header combinations a Map<String, ?>
, Headers
, List<Header>
can be used. In this case, annotate the parameters with Headers
.
@retrofit.Get('user')
Future<User> getUser(@retrofit.Headers() Map<String, dynamic> headers);
Headers that need to be added to every request can be specified using an interceptor.
Authentication #
Basic Authentication
A Basic authentication can be provided dynamically annotating a parameter with BasicUsername
or BasicPassword
annotation.
@retrofit.Get('/basic-auth/{user}/{passwd}')
Future<dynamic> basicAuth(
@retrofit.Path() @retrofit.BasicUsername() String user,
@retrofit.Path('passwd') @retrofit.BasicPassword() String password,
);
You can set static authentication for a method using the BasicAuth
annotation.
@retrofit.Get('/basic-auth/restio/1234')
@retrofit.BasicAuth('restio', '1234')
Future<dynamic> basicAuth();
Digest Authentication
A Digest authentication can be provided dynamically annotating a parameter with DigestUsername
or DigestPassword
annotation.
@retrofit.Get('/digest-auth/auth/{user}/{passwd}/MD5')
Future<dynamic> digestAuth(
@retrofit.Path() @retrofit.DigestUsername() String user,
@retrofit.Path('passwd') @retrofit.DigestPassword() String password,
);
You can set static authentication for a method using the DigestAuth
annotation.
@retrofit.Get('/digest-auth/auth/restio/1234/MD5')
@retrofit.DigestAuth('restio', '1234')
Future<dynamic> digestAuth();
Bearer Authentication
A Bearer authentication can be provided dynamically annotating a parameter with BearerToken
or BearerPrefix
annotation.
@retrofit.Get('/bearer')
Future<dynamic> bearerAuth(
@retrofit.Path() @retrofit.BearerToken() String token,
);
You can set static authentication for a method using the BearerAuth
annotation.
@retrofit.Get('/bearer')
@retrofit.BearerAuth('1234')
Future<dynamic> bearerAuth();
Hawk Authentication
In the same way you can provide Hawk authentication dynamically using HawkKey
, HawkId
, HawkAlgorithm
or HawkExt
annotation to the corresponding parameter or statically annotating a method with HawkAuth
.
HTTP2 #
To indicate that a method will use HTTP2 connection, annotate one with Http2
.
@retrofit.Get('/get')
@retrofit.Http2()
Future<dynamic> http2();
Converter #
You should use the BodyConverter
class from restio
package.
class FlutterBodyConverter extends BodyConverter {
const FlutterBodyConverter();
@override
Future<String> encode<T>(
T value,
MediaType contentType,
) {
final mimeType = contentType.mimeType;
if (mimeType == 'application/json') {
return compute(...);
} else {
throw RestioException('Content type $mimeType not supported');
}
}
@override
Future<T> decode<T>(
String source,
MediaType contentType,
) {
final mimeType = contentType.mimeType;
if (mimeType == 'application/json') {
final data = await super.decode(source, contentType);
if (isType<T, User>()) {
return compute(...);
// return User.fromJson(data) as T;
} else if (isType<T, List<User>>()) {
return compute(...);
// return [for (final item in data) User.fromJson(item)] as T;
}
return data;
} else {
throw RestioException('Content type $mimeType not supported');
}
}
}
/// Checks whether [T1] is a type or subtype of [T2].
bool isType<T1, T2>() => <T1>[] is List<T2>;
// Set the custom converter.
Restio.bodyConverter = const FlutterBodyConverter();
Response #
The method return type can be:
Future<List<int>>
for decompressed data (gzip, deflate or brotli)Future<String>
for String dataFuture<dynamic>
for JSON decoded data (provided by Converter)Future<int>
for the response code onlyFuture<Response>
for get response classStream<List<int>>
for Stream dataFuture<?>
for get complex data (provided by Converter)Future<Result<?>>
for get any data along with response code, response message, headers and cookies
You can get the uncompressed data annotating the method with Raw
e using Future<List<int>>
.
@retrofit.Raw()
@retrofit.Get('/gzip')
Future<List<int>> gzip();
If you use Future<Response>
or Stream<List<int>>
you are responsible for closing the response.
Result
You can use Result<T>
as return type for get any data along with response code, response message, headers and cookies. Use Result<void>
when no data is returned.
@retrofit.Get('/users/{id}')
Future<Result<User>> getUser(@retrofit.Path() int id);
@retrofit.Post('/users')
Future<Result<void>> createUser(
@retrofit.Body(contentType: 'application/json') User user);
Response Status Exception #
For default, if the response status is not between 200-299, will be throw the HttpStatusException
.
You can specify the status code range annotating the method with Throws
.
@retrofit.Throws(400, 600)
@retrofit.Get('/users/{id}')
Future<User> getUser(@retrofit.Path() int id)
There are seven built-in Throws annotations:
@retrofit.Throws.only(int code)
: Specify the status code that throws the exception.@retrofit.Throws.not(int code)
: Specify the status code that not throws the exception.@retrofit.Throws.redirect()
: Throws the exception only if the response code is from redirects.@retrofit.Throws.notRedirect()
: Throws the exception only if the response code is not from redirects.@retrofit.Throws.error()
: Throws the exception only if the response code is a client error or server error.@retrofit.Throws.clientError()
: Throws the exception only if the response code is a client error.@retrofit.Throws.serverError()
: Throws the exception only if the response code is a server error.
You are responsible for closing the response on catch block.
try {
final user = await api.getUser(0);
} on HttpStatusException catch(e) {
final code = e.code;
final response = e.response;
await response.close();
}
Future<int>
, Future<Response>
and Future<Result<?>>
do not throw the exception. For other response types, if you want to prevent the exception from being thrown, annotate the method with NotThrows
.
Request Options & Extra #
If you want pass RequestOptions
to the request, just add the parameter to the method.
@retrofit.Get('/')
Future<dynamic> get(RequestOptions options);
If you want pass Map<String, dynamic>
as extra property to the request, annotate the parameter with Extra
.
@retrofit.Get('/')
Future<dynamic> get(@retrofit.Extra() Map<String, dynamic> extra);