The goal of this package is to make interaction with your REST APIs pleasant and concise. It currently supports JSON:API-compliant REST APIs, but can be extended to support more formats - and you're encouraged to contribute!
rest_data
is inspired by ember-data so it's based on 3 main abstractions:
Model
: represents a single REST resource, with its attributes and relationships.Serializer
: takes care of transformingModel
objects into your REST API payloads format (usually JSON), and vice-versa.Adapter
: provides methods to read/write model objects to your REST APIs; it maps toember-data
's Store but we choose to not adopt the nameStore
in order to avoid conflict with the popular dart packageredux
.
Create your Adapter
as follows:
Adapter adapter = JsonApiAdapter('host.example.com', '/path/to/rest/api');
This is designed to be a long-lived object: a good practice is to wrap it in a singleton class to be used across the whole app, or to make it available throughout your app via Dependency Injection.
For each of your REST API resources, you should define a model class extending JsonApiModel
which provides the following getters:
Map<String, dynamic> get attributes
see specsMap<String, dynamic> get relationships
see specsIterable<dynamic> get included
see specsIterable<dynamic> get errors
see specs
together with helper methods (e.g. idFor()
, idsFor()
, typeFor()
, setHasOne()
etc. - see the source for more details).
Example model follows:
class Address extends JsonApiModel {
// Constructors
Address(JsonApiDocument doc) : super(doc);
Address.init(String type) : super.init(type);
// Attributes
String get street => getAttribute<String>('street');
set street(String value) => setAttribute<String>('street', value);
String get city => getAttribute<String>('city');
set city(String value) => setAttribute<String>('city', value);
String get zip => getAttribute<String>('zip');
set zip(String value) => setAttribute<String>('zip', value);
// Has-One Relationships
String get countryId => idFor('country');
set country(Country model) => setHasOne('country', model);
}
class Country extends JsonApiModel {
Country(JsonApiDocument doc) : super(doc);
}
Invoking REST APIs is as simple as calling async
methods on your adapter
object. Such methods return one or more JsonApiDocument
objects, which can be used to build your model objects.
var address = Address(await adapter.find('addresses', '1'));
Will send the request: GET /addresses/1
.
var address = Address(await adapter.find('addresses', '1', queryParams: {'include', 'state'}));
Will send the request: GET /addresses/1?include=state
.
Iterable<Country> countries =
adapter.findAll('countries')
.map<Country>((jsonApiDoc) => Country(jsonApiDoc));
Will send the request: GET /countries
.
Iterable<Address> addresses =
(await adapter.findMany('addresses', ['1', '2', '3']))
.map<Address>((jsonApiDoc) => Address(jsonApiDoc));
GET /addresses?filter[id]=1,2,3
Iterable<Address> addresses =
(await adapter.query('addresses', {'q': 'miami'}))
.map<Address>((jsonApiDoc) => Address(jsonApiDoc));
GET /addresses?filter[q]=miami
You'll start with an empty model object, whose attributes and relationships will be set based on user input:
var address = Address.init();
address.street = '9674 Northwest 10th Avenue';
address.city = 'Miami';
address.zip = '33150';
address.country = Country.peek('US'); // Assume all countries are cached, see "Caching" section later
To persist your model on your REST API backend, just invoke the Adapter
's save()
method, which will return a new Address
object:
var savedAddress = Address(await adapter.save(endpoint, address.jsonApiDoc));
The above line will send the request: POST /addresses
with the address
model object serialized as a JSON:API Document.
Assume you have an existing model object, and you edit some attributes based on user input:
var address = Address(await adapter.find('addresses', '1'));
address.street = '9674 Northwest 10th Avenue';
address.zip = '33150';
To persist your model on your REST API backend, just invoke the Adapter
's save()
method, which will return a new Address
object:
var savedAddress = Address(await adapter.save(endpoint, address.jsonApiDoc));
The above line will send the request: PUT /addresses
with the address
model object serialized as a JSON:API Document.
JsonApiAdapter
comes with a basic caching mechanism built-in: a simple Map
in-memory. Models fetched from the backend are automatically cached on any read request, and the cached ones are returned on subsequent read requests for the same model id.
When you only want already cached data, you can use Adapter
's methods starting with the peek
prefix.
Invalidation must be handled manually, passing forceReload = true
to find*
methods.