brick_rest 3.0.4 brick_rest: ^3.0.4 copied to clipboard
RESTful API connector for Brick, a data persistence library. Includes annotations, adapter, model, and provider.
REST Provider #
Connecting Brick with a RESTful API.
Supported Query
Configuration #
providerArgs:
#
'request'
(RestRequest
) Specifies configurable information about the request like HTTP method or top level key
where:
#
RestProvider
does not support any Query#where
arguments. These should be configured on a model-by-model base by the RestSerializable#endpoint
argument.
Models #
@RestSerializable(requestTransformer:)
#
💡 requestTransformer
was added in Brick 3. For upgrading to Brick v3 from v2, please see the migration guide.
Every REST API is built differently, and with a fair amount of technical debt. Brick provides flexibility for inconsistent endpoints within any system. Endpoints can also change based on the query. The model adapter will query endpoint
for upsert
or get
or delete
.
Since Dart requires annotations to be constants, dynamic functions cannot be used. This is a headache. Instead, the a const
antized constructor tearoff can be used. The transformers permit dynamically defining the request (method, top level key, url, etc.) at runtime based on query params or if a Dart instance is available (upsert
and delete
only)
class UserRequestTransformer extends RestRequestTransformer {
final get = const RestRequest(url: '/users');
const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
requestTransformer: UserRequestTransformer.new;
)
)
class User extends OfflineFirstModel {}
Different provider calls will use different transformer fields:
class UserRequestTransformer extends RestRequestTransformer {
final get = const RestRequest(url: '/users');
final delete = RestRequest(url: '/users/${instance.id}');
const UserRequestTransformer(Query? query, Model? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
requestTransformer: UserRequestTransformer.new,
)
)
class User extends OfflineFirstModel {}
⚠️ If an RestRequestTransform
's method field (get
, upsert
, delete
) is null
or it's url
is null
, the request is skipped by the provider.
With Query#providerArgs
class UserRequestTransformer extends RestRequestTransformer {
RestRequest? get get {
if (query?.providerArgs.isNotEmpty && query.providerArgs['limit'] != null) {
return RestRequest(url: "/users?limit=${query.providerArgs['limit']}");
}
const RestRequest(url: '/users');
}
final delete = RestRequest(url: '/users/${instance.id}');
const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
requestTransformer: UserRequestTransformer.new,
)
)
class User extends OfflineFirstModel {}
With Query#where
class UserRequestTransformer extends RestRequestTransformer {
RestRequest? get get {
if (query?.where != null) {
final id = Where.firstByField('id', query.where)?.value;
if (id != null) return RestRequest(url: "/users/$id");
}
const RestRequest(url: '/users');
}
final delete = RestRequest(url: '/users/${instance.id}');
const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
requestTransformer: UserRequestTransformer.new,
)
)
class User extends OfflineFirstModel {}
💡 For ease of illustration, the code is provided as if the transformer and model logic live in the same file. It's strongly recommended to include the request transformer logic in its own, colocated file (such as user.model.request.dart
).
@RestRequest(topLevelKey:)
#
Data will most often be nested beneath a top-level key in a JSON response. The key is determined by the following priority:
- A
topLevelKey
inQuery#providerArgs['request']
with a non-empty value topLevelKey
if defined in aRestRequest
- The first discovered key. As a map is effectively an unordered list, relying on this fall through is not recommended.
class UserRequestTransformer extends RestRequestTransformer {
final get = const RestRequest(url: '/users', topLevelKey: 'users');
final upsert = const RestRequest(url: '/users', topLevelKey: 'user');
const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
requestTransformer: UserRequestTransformer.new
)
class User extends OfflineFirstModel {}
⚠️ If the response from REST is not a map, the full response is returned instead.
@RestSerializable(fieldRename:)
#
Brick reduces the need to map REST keys to model field names by assuming a standard naming convention. For example:
RestSerializable(fieldRename: FieldRename.snake_case)
// on from rest (get)
"last_name" => final String lastName
// on to rest (upsert)
final String lastName => "last_name"
Fields #
@Rest(enumAsString:)
#
Brick by default assumes enums from a REST API will be delivered as integers matching the index in the Flutter app. However, if your API delivers strings instead, the field can be easily annotated without writing a custom generator.
Given the API:
{ "user": { "hats": [ "bowler", "birthday" ] } }
Simply convert hats
into a Dart enum:
enum Hat { baseball, bowler, birthday }
...
@Rest(enumAsString: true)
final List<Hat> hats;
@Rest(name:)
#
REST keys can be renamed per field. This will override the default set by RestSerializable#fieldRename
.
@Rest(
name: "full_name" // "full_name" is used in from and to requests to REST instead of "last_name"
)
final String lastName;
@Rest(ignoreFrom:)
and @Rest(ignoreTo:)
#
When true, the field will be ignored by the (de)serializing function in the adapter.
GZipping Requests #
All requests to the API endpoint can be compressed with Dart's standard GZip library. All requests will (over)write the Content-Encoding
header to {'Content-Encoding': 'gzip'}
.
import 'package:brick_rest/gzip_http_client.dart';
final restProvider = RestProvider(client: GZipHttpClient(level: 9));
⚠️ Your API must be able to accept and decode GZipped requests.
Unsupported Field Types #
The following are not serialized to REST. However, unsupported types can still be accessed in the model as non-final fields.
- Nested
List<>
e.g.<List<List<int>>>
- Many-to-many associations