A Flutter library to make Rest API clients more easily. Inspired by Java Feing.
Features
- Facilitated JSON encode and decode using common interfaces.
- Facilitated http errors handle.
- Facilitated http header control.
- Dynamic generation of urls with path and queries params.
- Configurable retry attempsts on http error.
Getting started
Just add the package and follow the above instructions
dependencies:
hermes_http: ^1.0.0
Usage
Create a client class and provide default configuration and request templates on constructor;
class FruitClient {
late HermesHttpClient _client;
FruitClient() {
//Creates a http client with base url and the common headers
_client = HermesHttpClient("https://www.fruityvice.com/");
_client.addHeader("Content-Type", "application/json");
_client.addHeader("Connection", "keep-alive");
}
}
Then create the data classes of Requests and Responses, and one implementation of the interfaces JsonDecoder, JsonEncoder for each class.
//classes that implements json parser and json encoder,
//its not mandatory parser and encoder interfaces are implemented by the data class itself.
//the interface can be implemented by another classes
class Fruit implements JsonDecoder<Fruit>, JsonEncoder<Fruit> {
String genus = "";
String name = "";
int id = 0;
String family = "";
String order = "";
Nutrition nutritions = Nutrition();
@override
Fruit fromJson(dynamic jsonMap) {
Fruit fruit = Fruit();
fruit.genus = jsonMap['genus'];
fruit.name = jsonMap['name'];
fruit.id = jsonMap['id'];
fruit.family = jsonMap['family'];
fruit.order = jsonMap['order'];
fruit.nutritions = NutritionDecoder().fromJson(jsonMap['nutritions']);
return fruit;
}
@override
Map<String, dynamic> toJson(Fruit obj) {
Map<String, dynamic> map = <String, dynamic>{};
map['genus'] = obj.genus;
map['name'] = obj.name;
map['id'] = obj.id;
map['family'] = obj.family;
map['order'] = obj.order;
map['nutritions'] = NutritionEncoder().toJson(obj.nutritions);
return map;
}
}
class Nutrition {
num carbohydrates = 0;
num protein = 0;
num fat = 0;
num calories = 0;
num sugar = 0;
}
class NutritionDecoder extends JsonDecoder<Nutrition> {
@override
Nutrition fromJson(jsonMap) {
Nutrition nutrition = Nutrition();
nutrition.carbohydrates = jsonMap['carbohydrates'];
return nutrition;
}
}
class NutritionEncoder extends JsonEncoder<Nutrition> {
@override
Map<String, dynamic> toJson(Nutrition obj) {
Map<String, dynamic> map = <String, dynamic>{};
map['carbohydrates'] = obj.carbohydrates;
return map;
}
}
Using the data classes create the requests templates.
For each request template provide an HermesRequest<Request, Response> reference, either the request or response object can be void (if you want to ignore response void just pass void to).
After that instatiate the request template on constructor
The parameters are:
- hermes http client (or a custom implementation of the IHermesHttpClient)
- http method lowercase
- path with any params inside brackets ( /api/fruit/{fruitName} , /api/fruit/nutrition?min={minumunValue}&max={maximumValue} )
- an implementation of JsonEncoder interface (use VoidJsonEncoder() for void values)
- an implementation of JsonDecoder interface (use VoidJsonDecoder() for void values)
- optional named parameter maxAttempts for configure retry (default 3)
- optional custom headers for the request ( Map<String,String> )
class FruitClient {
late HermesHttpClient _client;
//declares a request with the request body type and the response body type
late HermesRequest<void, Fruit> _getFruit;
late HermesRequest<void, List<Fruit>> _getAllFruit;
late HermesRequest<Fruit, void> _addFruit;
FruitClient() {
//Creates a http client with base url and the common headers
_client = HermesHttpClient("https://www.fruityvice.com/");
_client.addHeader("Content-Type", "application/json");
_client.addHeader("Connection", "keep-alive");
//Creates the request instance
_getFruit = HermesRequest(_client, 'get', '/api/fruit/{fruitName}', VoidJsonEncoder(), Fruit());
//Create the request using the class ListJsonDecoder to parse the json list
_getAllFruit = HermesRequest(_client, 'get', '/api/fruit/all', VoidJsonEncoder(), ListJsonDecoder<Fruit>(Fruit()));
//Create the request setting the maxAttemps (retry) to only 1 (defaults 3)
_addFruit = HermesRequest(_client, 'put', '/api/fruit', Fruit(), VoidJsonDecoder(), maxAttempts: 1);
}
}
Then just finish exposing methods of the client
class FruitClient {
late HermesHttpClient _client;
//declares a request with the request body type and the response body type
late HermesRequest<void, Fruit> _getFruit;
late HermesRequest<void, List<Fruit>> _getAllFruit;
late HermesRequest<Fruit, void> _addFruit;
FruitClient() {
//Creates a http client with base url and the common headers
_client = HermesHttpClient("https://www.fruityvice.com/");
_client.addHeader("Content-Type", "application/json");
_client.addHeader("Connection", "keep-alive");
//Creates the request instance
_getFruit = HermesRequest(_client, 'get', '/api/fruit/{fruitName}', VoidJsonEncoder(), Fruit());
//Create the request using the class ListJsonDecoder to parse the json list
_getAllFruit = HermesRequest(_client, 'get', '/api/fruit/all', VoidJsonEncoder(), ListJsonDecoder<Fruit>(Fruit()));
//Create the request setting the maxAttemps (retry) to only 1 (defaults 3)
_addFruit = HermesRequest(_client, 'put', '/api/fruit', Fruit(), VoidJsonDecoder(), maxAttempts: 1);
}
//Creates a call to request
//Pass any kind of param (path, query) in the path as a map
Future<Fruit> getFruit(String fruitName) async {
return await _getFruit.call(pathParams: { 'fruitName': fruitName });
}
Future<List<Fruit>> getAllFruit() async {
_getAllFruit.addHeader("Accept", "application/json"); //dinamically set a header to the request
return await _getAllFruit.call();
}
Future<void> addFruit(Fruit fruit) async {
await _addFruit.call(body: fruit);
}
}
The full example can be found on github exemples folder.
If the http call return http status diferent of the 2xx family the following exception will be thrown
class HermesRequestError implements Exception {
int status = 0;
String body = "";
String uri = "";
String method = "";
HermesRequestError(this.status, this.method, this.uri, this.body);
}
Additional information
Fell free to contribute.