In preparation for Brick 4, Query is migrating away from loosely-defined arguments in favor of standardized fields that can be easily deprecated and discovered by analysis.
providerArgs will be supported until Brick 4 is officially released.
It is still recommended you migrate to the new Query for new features and long-term support.
Query#orderBywill support association ordering and multiple valuesQueryis constructed withconstQuery#offsetno longer requires companionlimitparameterbrick_sqliteandbrick_supabasesupport association ordering. For example,Query(orderBy: [OrderBy.desc('assoc', associationField: 'name')])onDemoModelwill produce the following SQL statement:'SELECT DISTINCT `DemoModel`.* FROM `DemoModel` ORDER BY `DemoModelAssoc`.name DESC'brick_supabasesupports advanced limiting. For example,Query(limitBy: [LimitBy(1, evaluatedField: 'assoc'))is the equivalent of.limit(1, referencedTable: 'demo_model')
| Old | New | Notes |
|---|---|---|
Query(providerArgs: {'limit':}) |
Query(limit:) |
limit and limitBy may be used together, however, limitBy will only limit evaluatedField: associations |
Query(providerArgs: {'offset':}) |
Query(offset:) |
|
Query(providerArgs: {'orderBy':}) |
Query(orderBy:) |
orderBy is now defined by a class that permits multiple commands. For example, 'orderBy': 'name ASC' becomes [OrderBy('name', ascending: true)]. First-class Brick providers (SQLite and Supabase) also support association-based querying by declaring a associationField:. This associationField is optional in Supabase but required for SQLite. |
| Old | New | Notes |
|---|---|---|
Query(providerArgs: {'context':}) |
Query(forProviders: [GraphqlProviderQuery(context:)]) |
|
Query(providerArgs: {'operation':}) |
Query(forProviders: [GraphqlProviderQuery(operation:)]) |
| Old | New | Notes |
|---|---|---|
Query(providerArgs: {'request':}) |
Query(forProviders: [RestProviderQuery(request:)]) |
| Old | New | Notes |
|---|---|---|
Query(providerArgs: {'collate':}) |
Query(forProviders: [SqliteProviderQuery(collate:)]) |
|
Query(providerArgs: {'having':}) |
Query(forProviders: [SqliteProviderQuery(having:)]) |
|
Query(providerArgs: {'groupBy':}) |
Query(forProviders: [SqliteProviderQuery(groupBy:)]) |
| Old | New | Notes |
|---|---|---|
Query(providerArgs: {'limitReferencedTable':}) |
Removed in favor of Query(limitBy:) |
|
Query(providerArgs: {'orderByReferencedTable':}) |
Removed in favor of Query(orderBy:) |
Brick 3 removes the abstract packages since Sqflite has abstracted its Flutter dependency to a "common" API.
brick_offline_first_with_graphql_abstract, brick_offline_first_with_rest_abstract, brick_sqlite_abstract, and brick_offline_first_abstract will remain on pub.dev since publishing is forever. While this change is internal, parent packages no longer export the contents of child packages. Some adjustments may need to be made.
- Primary package files are renamed in line with
pub.devstandards.for FILE in $(find "lib" -type f -name "*.dart"); do sed -i '' 's/package:brick_offline_first\/offline_first.dart/package:brick_offline_first\/brick_offline_first.dart/g' $FILE sed -i '' 's/package:brick_offline_first_with_rest\/offline_first_with_rest.dart/package:brick_offline_first_with_rest\/brick_offline_first_with_rest.dart/g' $FILE sed -i '' 's/package:brick_offline_first_with_graphql\/offline_first_with_graphql.dart/package:brick_offline_first_with_graphql\/brick_offline_first_with_graphql.dart/g' $FILE sed -i '' 's/package:brick_rest\/rest.dart/package:brick_rest\/brick_rest.dart/g' $FILE sed -i '' 's/package:brick_sqlite\/sqlite.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE sed -i '' 's/package:brick_graphql\/graphql.dart/package:brick_graphql\/brick_graphql.dart/g' $FILE done
brick_offline_first/offline_first.dartis nowbrick_offline_first/brick_offline_first.dartbrick_offline_first_with_rest/offline_first_with_rest.dartis nowbrick_offline_first_with_rest/brick_offline_first_with_rest.dartbrick_offline_first_with_graphql/brick_offline_first_with_graphql.dartis nowbrick_offline_first_with_graphql/brick_offline_first_with_graphql.dartbrick_graphql/graphql.dartis nowbrick_rest/brick_graphql.dartbrick_rest/rest.dartis nowbrick_rest/brick_rest.dartbrick_sqlite/sqlite.dartis nowbrick_sqlite/brick_sqlite.dart
brick_sqlite_abstract/db.dartis nowbrick_sqlite/db.dart.brick_sqlite_abstract/sqlite_model.dartandbrick_sqlite_abstract/annotations.dartare now exported bybrick_sqlite/brick_sqlite.dartfor FILE in $(find "lib" -type f -name "*.dart"); do sed -i '' 's/package:brick_sqlite_abstract\/annotations.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE sed -i '' 's/package:brick_sqlite_abstract\/sqlite_model.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE sed -i '' 's/package:brick_sqlite_abstract\/db.dart/package:brick_sqlite\/db.dart/g' $FILE done
RestAdapter.firstWhereOrNullandGraphqlAdapter.firstWhereOrNullhave been removed. Insteadimport 'package:collection/collection.dart';and use the bundledIterableextension.firstWhereOrNullRestAdapter.enumValuesByNameandGraphqlAdapter.enumValuesByNamehave been removed. Instead use Dart 2.15's built-in<enum>.values.byName- The minimum Dart version has been increased to 2.18
providerArgsin Brick Rest have changed:'topLevelKey'and'headers'and'supplementalTopLevelData'have been removed (use'request') and'request'now accepts aRestRequestinstead of the HTTP method string.providerArgsin Brick Graphql have changed:'document'and'variables'have been removed. Instead, use'operation'.analyzeris now>= 5
FieldRename,GraphqlGraphqlProvider, andGraphqlSerializableare no longer exported byoffline_first_with_graphql.dart. Instead, import these file frompackage:brick_graphql/brick_graphql.dart
FieldRename,Rest,RestProvider, andRestSerializableare no longer exported byoffline_first_with_rest.dart. Instead, import these file frompackage:brick_rest/brick_rest.dartOfflineFirstWithRestRepository#reattemptForStatusCodeshas been removed from instance-level access. The constructor argument forwards to theRestOfflineQueueClient, where it can be accessed if needed.OfflineFirstWithRestRepository#throwTunnerNotFoundExceptionshas been removed. This value was duplicated fromofflineQueueManager; the queue manager is where the property exclusively lives now.
- Listen for SQLite changes via
OfflineFirstWithRestRepository#subscribe
This breaking change migration is less automatable. The script below is a best attempt and should be manually confirmed after running.
for FILE in $(find "lib" -type f -name "*.dart"); do
# `sed` regex capture may work for you; it didn't on Mac for me
# sed -i '' "s/\'document\': (.*)/\'operation\': GraphqlOperation\(document: \1\) \/\/ TODO verify migration to GraphqlOperation /g" $FILE
perl -0777 -i -pe "s/'document': (.*)/'operation': GraphqlOperation\(document: \1\) \/\/ TODO verify migration to GraphqlOperation /igs" $FILE
# sed -i '' "s/\'variables\': (.*)/\'operation\': GraphqlOperation\(variables: \1\) \/\/ TODO verify migration to GraphqlOperation /g" $FILE
perl -0777 -i -pe "s/'variables': (.*)/'operation': GraphqlOperation\(variables: \1\) \/\/ TODO verify migration to GraphqlOperation /igs" $FILE
doneThis has been consolidated to 'operation'. For example: providerArgs: { 'operation': GraphqlOperation(document: r'''mutation UpdateUser(id: ....)''')}.
This has been consolidated to 'operation'. For example: providerArgs: { 'operation': GraphqlOperation(variables: {'id': '1'}) }.
This breaking change migration is less automatable. The script below is a best attempt and should be manually confirmed after running.
for FILE in $(find "lib" -type f -name "*.dart"); do
# `sed` regex capture may work for you; it didn't on Mac for me
# sed -i '' "s/\'request\': (.*)/\'request\': RestRequest\(method: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
perl -0777 -i -pe "s/'request': (.*)/'request': RestRequest\(method: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
# sed -i '' "s/\'headers\': (.*)/\'request\': RestRequest\(headers: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
perl -0777 -i -pe "s/'headers': (.*)/'request': RestRequest\(headers: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
# sed -i '' "s/\'topLevelKey\': (.*)/\'request\': RestRequest\(topLevelKey: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
perl -0777 -i -pe "s/'topLevelKey': (.*)/'request': RestRequest\(topLevelKey: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
# sed -i '' "s/\'supplementalTopLevelData\': (.*)/\'request\': RestRequest\(supplementalTopLevelData: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
perl -0777 -i -pe "s/'supplementalTopLevelData': (.*)/'request': RestRequest\(supplementalTopLevelData: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
doneThis key now accepts a RestRequest class instead of an HTTP method name.
This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(headers: {'Authorization': 'Bearer'})}.
This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(topLevelKey: 'myKey' )}.
This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(supplementalTopLevelData: {'myKey': {'myData': 1}}) }.
RestSerializable'sfromKeyandtoKeyhave been consolidated toRestRequest(topLevelKey:)RestSerializable(endpoint:)has been replaced in this release byRestSerializable(requestTransformer:). It will be painful to upgrade though with good reason.
- Strongly-typed classes.
endpointwas a string, which removed analysis in IDEs, permitting errors to escape during runtime. With endpoints as classes,Queryandinstanceobjects will receive type hinting. - Fine control over REST requests. Define on a request-level basis what key to pull from or push to. Declare specific HTTP methods like
PATCHin a class that manages request instead of in distributedproviderArgs. - Future-proof development. Enhancing REST's configuration will be on a class object instead of in untyped string keys on
providerArgs. The REST interface is consolidated to this subclass.
Since all APIs are different, and endpoint used stringified code, the migration cannot be scripted for all users. Instead, examples are provided below to illustrate how to refactor from Brick 2's endpoint to Brick 3's requestTransformer. Some examples:
// BEFORE
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
endpoint: '"/users";'
fromKey: 'users',
)
)
// AFTER
class UserRequestTransformer extends RestRequestTransformer {
final get = const RestRequest(url: '/users', topLevelKey: 'users');
const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
requestTransformer: UserRequestTransformer.new,
)
)Some cases are more complex:
// BEFORE
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(
endpoint: r'''{
if (query?.action == QueryAction.delete) return "/users/${instance.id}";
if (query?.action == QueryAction.get &&
query?.providerArgs.isNotEmpty &&
query?.providerArgs['limit'] != null) {
return "/users?limit=${query.providerArgs['limit']}";
}
return "/users";
}''';
)
)
// AFTER
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,
)
)💡 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).
Brick 2 focuses on Brick problems encountered at scale. While the primary refactor was the abstraction of domain-specific code from generalized domains, this major release also includes a new GraphQL domain, resolution of community pain points, and a few neat tricks.
- Brick no longer expects
lib/app; it now expectslib/brick.mv -r lib/app lib/brick
- Models are no longer discovered in
lib/app/models; they are now discovered via*.model.dart. They can live in any directory withinliband have any prefix. (#38)for FILENAME in lib/brick/models/*; do mv $FILENAME "${FILENAME/dart/model.dart}"; done
brick_offline_firstis now, fundamentally,brick_offline_first_with_rest.brick_offline_firstnow serves as an abstract bedrock for offline domains.sed -i '' 's/brick_offline_first:/brick_offline_first_with_rest:/g' pubspec.yaml for FILE in $(find "lib" -type f -name "*.dart"); do sed -i '' 's/package:brick_offline_first/package:brick_offline_first_with_rest/g' $FILE; done
brick_offline_first_abstractis nowbrick_offline_first_with_rest_abstractsed -i '' 's/brick_offline_first_abstract:/brick_offline_first_with_rest_abstract:/g' pubspec.yaml for FILE in $(find "lib" -type f -name "*.dart"); do sed -i '' 's/package:brick_offline_first_abstract/package:brick_offline_first_with_rest_abstract/g' $FILE; done
restproperties have been removed fromOfflineFirstException. UseOfflineFirstWithRestExceptioninstead frombrick_offline_first_with_rest.OfflineFirstRepository#get(requireRemote:andOfflineFirstRepository#getBatched(requireRemote:has been removed. Instead, usepolicy: OfflineFirstGetPolicy.alwaysHydrateOfflineFirstRepository#get(hydrateUnexisting:has been removed. Instead, usepolicy: OfflineFirstGetPolicy.awaitRemoteWhenNoneExist(this is the default).OfflineFirstRepository#get(alwaysHydrate:has been removed. Instead, usepolicy: OfflineFirstGetPolicy.alwaysHydrate.
- Utilize
OfflineFirstDeletePolicy,OfflineFirstGetPolicy, andOfflineFirstUpsertPolicyto override default behavior. Specific policies will throw an exception when the remote responds with an error (and throw that error) or skip the queue. Existing default behavior is maintained. OfflineFirstRepository#deletenow supports requiring a successful remote withOfflineFirstDeletePolicy.requireRemote. If the app is offline, normally handled exceptions (ClientExceptionandSocketException) arerethrown. (#182)OfflineFirstRepository#upsertnow supports requiring a successful remote withOfflineFirstUpsertPolicy.requireRemote. If the app is offline, normally handled exceptions (ClientExceptionandSocketException) arerethrown.
brick_graphql. TheGraphqlProviderinterfaces with a GraphQL backend. It uses gql's Link system to integrate with other community-supported functionality. That, and all your variables are autogenerated on every request.brick_graphql_generators. The perfect companion tobrick_graphql, this thin layer aroundbrick_rest_generatorsbattle-tested core compiles adapters for the GraphQL domain.brick_json_generators. The experienced core separated frombrick_rest_generatorspermits more code reuse and package creation for JSON-serving remote providers.brick_offline_first_build. Abstracted from the experienced core ofbrick_offline_first_with_rest_build, these helper generators and utils simplify adding offline capabilites to a domain.brick_offline_first_with_graphql. Utilize the GraphQL provider with SQLite and Memory cache. This is a near mirror ofbrick_offline_first_with_rest, save for a few exceptions. First, the OfflineQueueLink must be inserted in the appropriate position in your client's Link chain. Second,OfflineFirstWithGraphqlRepository#subscribepermits streaming updates, including notifications after local providers are updated.brick_offline_first_with_graphql_abstract. Annotations for the GraphQL domain without including Flutter.brick_offline_first_with_graphql_build. The culmination ofbrick_graphql_generatorsandbrick_offline_first_build.
- Because
requiredis now a reserved Dart keyword,requiredinWherePhrase,WhereCondition,And,Or, andWherehas been renamed toisRequired. - Field types in models
Set<Future<OfflineFirstModel>>,List<Future<OfflineFirstModel>>, andFuture<OfflineFirstModel>are no longer supported. Instead, useSet<OfflineFirstModel>,List<OfflineFirstModel>, andOfflineFirstModel(the adapters willawaiteach). StubOfflineFirstWithRestis functionally changed. SQLiteFFI has satisfied much of the original stubbing required for this class, and http's testing.dart library is sufficient to not require Mockito. Therefore,verifycalls will no longer be effective in testing on the client. Instead, passStubOfflineFirstWithRest.clientto yourRestProvider#clientwith the response values.StubOfflineFirstWithRestModelhas been removed. Please review Offline First Testing for implementation examples.
brick_offline_first: Priority for the next job to process from the queue - when processing requests in serial - has changed from'$HTTP_JOBS_CREATED_AT_COLUMN ASC, $HTTP_JOBS_ATTEMPTS_COLUMN DESC, $HTTP_JOBS_UPDATED_AT ASC'to'$HTTP_JOBS_CREATED_AT_COLUMN ASC'; this uses the job column introduced in 0.0.7 (26 May 2020) and will not affect any implementations using 0.0.7 or higher.brick_offline_first:RequestSqliteCacheno longer queries cached requests based on headers; requests are rediscovered based on their encoding, URL, request method, and body. Rehydrated (reattempted) requests will be hydrated with headers from the original request.- Every package is null safe. There is one outstanding dependency -
build_config- that needs to be migrated, so the generators are not technically "null safe". However, these are dev dependencies andbuild_configisn't imported into Dart code, so upgrading it will be changing numbers.