From 24de90564a484cf36b8bc9c66163104b3894c053 Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Fri, 1 Nov 2024 15:47:55 -0400 Subject: [PATCH] eng: add type generics to repository classes (#473) --- docs/home.md | 1 + docs/offline_first/models.md | 12 +--- .../offline_first_with_graphql_repository.md | 2 +- .../offline_first_with_rest_repository.md | 2 +- .../offline_first_with_supabase_repository.md | 2 +- docs/sqlite/memory_cache_provider.md | 6 -- packages/brick_offline_first/README.md | 13 ++-- packages/brick_offline_first/lib/mixins.dart | 1 + .../lib/src/mixins/get_first_mixin.dart | 7 +- .../CHANGELOG.md | 2 + .../README.md | 6 -- ...offline_first_with_graphql_repository.dart | 24 +++---- .../CHANGELOG.md | 2 + .../brick_offline_first_with_rest/README.md | 10 +-- .../offline_first_with_rest_repository.dart | 19 ++---- .../pubspec.yaml | 2 +- .../CHANGELOG.md | 2 + .../README.md | 6 -- ...ffline_first_with_supabase_repository.dart | 35 ++++------ packages/brick_sqlite/README.md | 66 +++++++++---------- .../lib/memory_cache_provider.dart | 14 ++-- .../brick_sqlite/lib/src/sqlite_provider.dart | 16 ++--- 22 files changed, 102 insertions(+), 148 deletions(-) diff --git a/docs/home.md b/docs/home.md index 36cf9163..28a8eb30 100644 --- a/docs/home.md +++ b/docs/home.md @@ -67,6 +67,7 @@ - Example: [Simple Associations using the OfflineFirstWithRest domain](https://github.com/GetDutchie/brick/blob/main/example_rest) - Example: [Simple Associations using the OfflineFirstWithSupabase domain](https://github.com/GetDutchie/brick/blob/main/example_supabase) - Tutorial: [Setting up a simple app with Brick](http://www.flutterbyexample.com/#/posts/2_adding_a_repository) +- Blog: [Building offline-first mobile apps with Supabase, Flutter and Brick](https://supabase.com/blog/offline-first-flutter-apps) ## Glossary diff --git a/docs/offline_first/models.md b/docs/offline_first/models.md index 26409d73..e5e59ffc 100644 --- a/docs/offline_first/models.md +++ b/docs/offline_first/models.md @@ -100,9 +100,9 @@ class Weight extends OfflineFirstSerdes, String> { Some regularly requested functionality doesn't exist in out-of-the-box Brick. This functionality does not exist in the core because it is dependent on remote data formatting outside the scope of Brick or it's non-essential. However, for convenience, these features are available in a mix-and-match support library. As this is not officially supported, please use caution determining if these mixins are applicable to your implementation. -| Mixin | Description | -|---|---| -| [`DeleteAllMixin`](lib/mixins/delete_all_mixin.dart) | Adds methods `#deleteAll` and `#deleteAllExcept` | +| Mixin | Description | +| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`DeleteAllMixin`](lib/mixins/delete_all_mixin.dart) | Adds methods `#deleteAll` and `#deleteAllExcept` | | [`DestructiveLocalSyncFromRemoteMixin`](lib/mixins/destructive_local_sync_from_remote_mixin.dart) | Extends `get` requests to force resync the `remoteProvider` to the local providers (also covered by new method `#destructiveLocalSyncFromRemote`) | ### General Usage @@ -111,9 +111,3 @@ Some regularly requested functionality doesn't exist in out-of-the-box Brick. Th import 'package:brick_offline_first/mixins.dart'; class MyRepository extends OfflineFirstRepository with DeleteAllMixin {} ``` - -## FAQ - -### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. diff --git a/docs/offline_first/offline_first_with_graphql_repository.md b/docs/offline_first/offline_first_with_graphql_repository.md index 9c75335c..9577c2dd 100644 --- a/docs/offline_first/offline_first_with_graphql_repository.md +++ b/docs/offline_first/offline_first_with_graphql_repository.md @@ -6,7 +6,7 @@ The `OfflineFirstWithGraphql` domain uses all the same configurations and annota ![OfflineFirst#get](https://user-images.githubusercontent.com/865897/72176226-cdd8ca00-3392-11ea-867d-42f5f4620153.jpg) -!> You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. +?> You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. ## GraphqlOfflineQueueLink diff --git a/docs/offline_first/offline_first_with_rest_repository.md b/docs/offline_first/offline_first_with_rest_repository.md index c09f0805..71af637a 100644 --- a/docs/offline_first/offline_first_with_rest_repository.md +++ b/docs/offline_first/offline_first_with_rest_repository.md @@ -6,7 +6,7 @@ The `OfflineFirstWithRest` domain uses all the same configurations and annotatio ![OfflineFirst#get](https://user-images.githubusercontent.com/865897/72176226-cdd8ca00-3392-11ea-867d-42f5f4620153.jpg) -:bulb: You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. +?> You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. ## Generating Models from a REST Endpoint diff --git a/docs/offline_first/offline_first_with_supabase_repository.md b/docs/offline_first/offline_first_with_supabase_repository.md index 581ee17b..c04c7e19 100644 --- a/docs/offline_first/offline_first_with_supabase_repository.md +++ b/docs/offline_first/offline_first_with_supabase_repository.md @@ -6,7 +6,7 @@ The `OfflineFirstWithSupabase` domain uses all the same configurations and annot ![OfflineFirst#get](https://user-images.githubusercontent.com/865897/72176226-cdd8ca00-3392-11ea-867d-42f5f4620153.jpg) -!> You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. +?> You can change default behavior on a per-request basis using `policy:` (e.g. `get(policy: OfflineFirstUpsertPolicy.localOnly)`). This is available for `delete`, `get`, `getBatched`, `subscribe`, and `upsert`. ## Packages diff --git a/docs/sqlite/memory_cache_provider.md b/docs/sqlite/memory_cache_provider.md index 45501092..ff1de1e9 100644 --- a/docs/sqlite/memory_cache_provider.md +++ b/docs/sqlite/memory_cache_provider.md @@ -9,9 +9,3 @@ MemoryCacheProvider([Hat]) ``` It is not recommended to use this provider with parent models that have child associations, as those children may be updated in the future without notifying the parent. - -## FAQ - -### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. diff --git a/packages/brick_offline_first/README.md b/packages/brick_offline_first/README.md index e1c912cf..69d83ebf 100644 --- a/packages/brick_offline_first/README.md +++ b/packages/brick_offline_first/README.md @@ -19,6 +19,7 @@ Using unique identifiers, `where:` can connect multiple providers. It is declare For a concrete example, SQLite is the local data source and REST is the remote data source: Given the API: + ```javascript { "assoc": { // These don't have to map to SQLite columns. @@ -118,9 +119,9 @@ class Weight extends OfflineFirstSerdes, String> { Some regularly requested functionality doesn't exist in out-of-the-box Brick. This functionality does not exist in the core because it is dependent on remote data formatting outside the scope of Brick or it's non-essential. However, for convenience, these features are available in a mix-and-match support library. As this is not officially supported, please use caution determining if these mixins are applicable to your implementation. -| Mixin | Description | -|---|---| -| [`DeleteAllMixin`](lib/mixins/delete_all_mixin.dart) | Adds methods `#deleteAll` and `#deleteAllExcept` | +| Mixin | Description | +| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`DeleteAllMixin`](lib/mixins/delete_all_mixin.dart) | Adds methods `#deleteAll` and `#deleteAllExcept` | | [`DestructiveLocalSyncFromRemoteMixin`](lib/mixins/destructive_local_sync_from_remote_mixin.dart) | Extends `get` requests to force resync the `remoteProvider` to the local providers (also covered by new method `#destructiveLocalSyncFromRemote`) | ### General Usage @@ -147,9 +148,3 @@ final client = RestOfflineQueueClient( ![OfflineQueue logic flow](https://user-images.githubusercontent.com/865897/72175823-f44a3580-3391-11ea-8961-bbeccd74fe7b.jpg) :warning: The queue ignores requests that are not `DELETE`, `PATCH`, `POST`, and `PUT` for REST. In GraphQL, `query` and `subscription` operations are ignored. Fetching requests are not worth tracking as the caller may have been disposed by the time the app regains connectivity. - -### FAQ - -#### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. diff --git a/packages/brick_offline_first/lib/mixins.dart b/packages/brick_offline_first/lib/mixins.dart index 47a18b9f..f1e6a541 100644 --- a/packages/brick_offline_first/lib/mixins.dart +++ b/packages/brick_offline_first/lib/mixins.dart @@ -1,2 +1,3 @@ export 'package:brick_offline_first/src/mixins/delete_all_mixin.dart'; export 'package:brick_offline_first/src/mixins/destructive_local_sync_from_remote_mixin.dart'; +export 'package:brick_offline_first/src/mixins/get_first_mixin.dart'; diff --git a/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart b/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart index 5933b45f..8a907a5a 100644 --- a/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart +++ b/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart @@ -1,13 +1,14 @@ import 'package:brick_offline_first/brick_offline_first.dart'; /// A convenience mixin for single-instance get operations. -mixin GetFirstMixin on OfflineFirstRepository { +mixin GetFirstMixin + on OfflineFirstRepository { /// Retrieves the first instance of [TModel] with certainty that it exists. /// If no instances exist, a [StateError] is thrown from within Dart's core /// `Iterable#first` method. It is recommended to use [getFirstOrNull] instead. /// /// Automatically applies `'limit': 1` to the query's `providerArgs` - Future getFirst({ + Future getFirst({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, Query? query, bool seedOnly = false, @@ -25,7 +26,7 @@ mixin GetFirstMixin on OfflineFirstRepository< /// according to the [query], but returns `null` if no instances exist. /// /// Automatically applies `'limit': 1` to the query's `providerArgs` - Future getFirstOrNull({ + Future getFirstOrNull({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, Query? query, bool seedOnly = false, diff --git a/packages/brick_offline_first_with_graphql/CHANGELOG.md b/packages/brick_offline_first_with_graphql/CHANGELOG.md index d4eab15e..8ceb3047 100644 --- a/packages/brick_offline_first_with_graphql/CHANGELOG.md +++ b/packages/brick_offline_first_with_graphql/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Allow a generic type argument for `OfflineFirstWithGraphqlRepository` + ## 3.2.0 - Add optional `onRequestException` callback function to `GraphqlOfflineQueueLink` diff --git a/packages/brick_offline_first_with_graphql/README.md b/packages/brick_offline_first_with_graphql/README.md index 91392cec..030dffb5 100644 --- a/packages/brick_offline_first_with_graphql/README.md +++ b/packages/brick_offline_first_with_graphql/README.md @@ -38,12 +38,6 @@ GraphqlProvider( class MyModel extends OfflineFirstModel {} ``` -### FAQ - -#### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. - ## Unsupported ### Field Types diff --git a/packages/brick_offline_first_with_graphql/lib/src/offline_first_with_graphql_repository.dart b/packages/brick_offline_first_with_graphql/lib/src/offline_first_with_graphql_repository.dart index 6d5596a1..83bbfca4 100644 --- a/packages/brick_offline_first_with_graphql/lib/src/offline_first_with_graphql_repository.dart +++ b/packages/brick_offline_first_with_graphql/lib/src/offline_first_with_graphql_repository.dart @@ -13,13 +13,9 @@ import 'package:meta/meta.dart'; /// from the [remoteProvider] pass through a seperate SQLite queue. If the app /// is unable to make contact with the [remoteProvider], the queue automatically retries in /// sequence until it receives a response. -/// -/// OfflineFirstWithGraphqlRepository should accept a type argument such as -/// <_RepositoryModel extends OfflineFirstWithGraphqlModel>, however, this causes a type bound -/// error on runtime. The argument should be reintroduced with a future version of the -/// compiler/analyzer. -abstract class OfflineFirstWithGraphqlRepository - extends OfflineFirstRepository { +abstract class OfflineFirstWithGraphqlRepository< + TRepositoryModel extends OfflineFirstWithGraphqlModel> + extends OfflineFirstRepository { /// The type declaration is important here for the rare circumstances that /// require interfacting with [GraphqlProvider]'s client directly. @override @@ -71,7 +67,7 @@ abstract class OfflineFirstWithGraphqlRepository } @override - Future delete( + Future delete( TModel instance, { Query? query, OfflineFirstDeletePolicy policy = OfflineFirstDeletePolicy.optimisticLocal, @@ -86,9 +82,9 @@ abstract class OfflineFirstWithGraphqlRepository } @override - Future> get({ + Future> get({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, - query, + Query? query, bool seedOnly = false, }) async { try { @@ -105,7 +101,7 @@ abstract class OfflineFirstWithGraphqlRepository } @override - Future exists({Query? query}) { + Future exists({Query? query}) { try { return super.exists(query: query); } on GraphQLError catch (e) { @@ -117,7 +113,7 @@ abstract class OfflineFirstWithGraphqlRepository @protected @override - Future> hydrate({ + Future> hydrate({ bool deserializeSqlite = true, Query? query, }) async { @@ -156,7 +152,7 @@ abstract class OfflineFirstWithGraphqlRepository /// with the assignment/subscription `.cancel()`'d as soon as the data is no longer needed. /// The stream will not close naturally. @override - Stream> subscribe({ + Stream> subscribe({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, Query? query, }) { @@ -207,7 +203,7 @@ abstract class OfflineFirstWithGraphqlRepository } @override - Future upsert( + Future upsert( TModel instance, { OfflineFirstUpsertPolicy policy = OfflineFirstUpsertPolicy.optimisticLocal, Query? query, diff --git a/packages/brick_offline_first_with_rest/CHANGELOG.md b/packages/brick_offline_first_with_rest/CHANGELOG.md index b716c7d4..9a51f39e 100644 --- a/packages/brick_offline_first_with_rest/CHANGELOG.md +++ b/packages/brick_offline_first_with_rest/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Allow a generic type argument for `OfflineFirstWithRestRepository` + ## 3.2.0 - Add optional `onRequestException` callback function to `RestOfflineQueueClient` diff --git a/packages/brick_offline_first_with_rest/README.md b/packages/brick_offline_first_with_rest/README.md index 96e4711c..61f0699e 100644 --- a/packages/brick_offline_first_with_rest/README.md +++ b/packages/brick_offline_first_with_rest/README.md @@ -135,13 +135,7 @@ setUpAll() async { } ``` -### FAQ - -#### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. - ## Unsupported Field Types -* Any unsupported field types from `RestProvider`, or `SqliteProvider` -* Future iterables of future models (i.e. `Future>>`. +- Any unsupported field types from `RestProvider`, or `SqliteProvider` +- Future iterables of future models (i.e. `Future>>`. diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart index f4e3c581..78fd672c 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart @@ -14,13 +14,8 @@ import 'package:meta/meta.dart'; /// is unable to make contact with the [remoteProvider], the queue automatically retries in /// sequence until it receives a response. Please note that a response may still be an error /// code such as `404` or `500`. The queue is **only** concerned with connectivity. -/// -/// OfflineFirstWithRestRepository should accept a type argument such as -/// <_RepositoryModel extends OfflineFirstWithRestModel>, however, this causes a type bound -/// error on runtime. The argument should be reintroduced with a future version of the -/// compiler/analyzer. -abstract class OfflineFirstWithRestRepository - extends OfflineFirstRepository { +abstract class OfflineFirstWithRestRepository + extends OfflineFirstRepository { /// The type declaration is important here for the rare circumstances that /// require interfacting with [RestProvider]'s client directly. @override @@ -100,7 +95,7 @@ abstract class OfflineFirstWithRestRepository } @override - Future delete( + Future delete( TModel instance, { OfflineFirstDeletePolicy policy = OfflineFirstDeletePolicy.optimisticLocal, Query? query, @@ -120,9 +115,9 @@ abstract class OfflineFirstWithRestRepository } @override - Future> get({ + Future> get({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, - query, + Query? query, bool seedOnly = false, }) async { try { @@ -165,7 +160,7 @@ abstract class OfflineFirstWithRestRepository /// [OfflineFirstException] for responses that include a code within `reattemptForStatusCodes`. /// Defaults `false`. @override - Future upsert( + Future upsert( TModel instance, { OfflineFirstUpsertPolicy policy = OfflineFirstUpsertPolicy.optimisticLocal, Query? query, @@ -186,7 +181,7 @@ abstract class OfflineFirstWithRestRepository @protected @override - Future> hydrate({ + Future> hydrate({ bool deserializeSqlite = true, Query? query, }) async { diff --git a/packages/brick_offline_first_with_rest/pubspec.yaml b/packages/brick_offline_first_with_rest/pubspec.yaml index ea3c9f47..d4ae3b24 100644 --- a/packages/brick_offline_first_with_rest/pubspec.yaml +++ b/packages/brick_offline_first_with_rest/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_offline_f issue_tracker: https://github.com/GetDutchie/brick/issues repository: https://github.com/GetDutchie/brick -version: 3.1.1 +version: 3.2.0 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/brick_offline_first_with_supabase/CHANGELOG.md b/packages/brick_offline_first_with_supabase/CHANGELOG.md index a21e3cee..6a942e7b 100644 --- a/packages/brick_offline_first_with_supabase/CHANGELOG.md +++ b/packages/brick_offline_first_with_supabase/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Allow a generic type argument for `OfflineFirstWithSupabaseRepository` + ## 1.1.0 - Added `OfflineFirstWithSupabaseRepository#subscribeToRealtime`to sync Brick data with Supabase changes (#472, #454) diff --git a/packages/brick_offline_first_with_supabase/README.md b/packages/brick_offline_first_with_supabase/README.md index 9b85514e..1bcf1967 100644 --- a/packages/brick_offline_first_with_supabase/README.md +++ b/packages/brick_offline_first_with_supabase/README.md @@ -164,12 +164,6 @@ Ideally, `@OfflineFirst(where:)` shouldn't be necessary to specify to make the a final Pizza pizza; ``` -### FAQ - -#### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. - ## Unsupported Field Types - Any unsupported field types from `SupabaseProvider`, or `SqliteProvider` diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 1064d3e3..2d390c0b 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -11,11 +11,6 @@ import 'package:supabase/supabase.dart'; /// Ensures the [remoteProvider] is a [SupabaseProvider]. /// -/// OfflineFirstWithSupabaseRepository should accept a type argument such as -/// <_RepositoryModel extends OfflineFirstWithSupabaseModel>, however, this causes a type bound -/// error on runtime. The argument should be reintroduced with a future version of the -/// compiler/analyzer. -/// /// Care should be given to attach an offline queue to the provider using the static convenience /// method [clientQueue]. /// @@ -41,8 +36,9 @@ import 'package:supabase/supabase.dart'; /// ); /// } /// ``` -abstract class OfflineFirstWithSupabaseRepository - extends OfflineFirstRepository { +abstract class OfflineFirstWithSupabaseRepository< + TRepositoryModel extends OfflineFirstWithSupabaseModel> + extends OfflineFirstRepository { /// The type declaration is important here for the rare circumstances that /// require interfacting with [SupabaseProvider]'s client directly. @override @@ -55,10 +51,7 @@ abstract class OfflineFirstWithSupabaseRepository @protected @visibleForTesting - final Map< - Type, - Map>>>> + final Map>>>> supabaseRealtimeSubscriptions = {}; OfflineFirstWithSupabaseRepository({ @@ -75,7 +68,7 @@ abstract class OfflineFirstWithSupabaseRepository ); @override - Future delete( + Future delete( TModel instance, { OfflineFirstDeletePolicy policy = OfflineFirstDeletePolicy.optimisticLocal, Query? query, @@ -94,9 +87,9 @@ abstract class OfflineFirstWithSupabaseRepository } @override - Future> get({ + Future> get({ OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, - query, + Query? query, bool seedOnly = false, }) async { try { @@ -118,7 +111,7 @@ abstract class OfflineFirstWithSupabaseRepository @protected @override - Future> hydrate({ + Future> hydrate({ bool deserializeSqlite = true, Query? query, }) async { @@ -148,13 +141,13 @@ abstract class OfflineFirstWithSupabaseRepository } @override - Future notifySubscriptionsWithLocalData({ + Future notifySubscriptionsWithLocalData({ bool notifyWhenEmpty = true, - Map>>? subscriptionsByQuery, + Map>>? subscriptionsByQuery, }) async { final supabaseControllers = supabaseRealtimeSubscriptions[TModel] ?.values - .fold(>>{}, (acc, eventMap) { + .fold(>>{}, (acc, eventMap) { acc.addEntries(eventMap.entries); return acc; }); @@ -199,7 +192,7 @@ abstract class OfflineFirstWithSupabaseRepository @protected @visibleForTesting @visibleForOverriding - PostgresChangeFilter? queryToPostgresChangeFilter( + PostgresChangeFilter? queryToPostgresChangeFilter( Query query, ) { final adapter = remoteProvider.modelDictionary.adapterFor[TModel]!; @@ -257,7 +250,7 @@ abstract class OfflineFirstWithSupabaseRepository /// `Query.where('user', Query.exact('name', 'Tom'))` is invalid but `Query.where('name', 'Tom')` /// is valid. The [Compare] operator is limited to a [PostgresChangeFilterType] equivalent. /// See [_compareToFilterParam] for a precise breakdown. - Stream> subscribeToRealtime({ + Stream> subscribeToRealtime({ PostgresChangeEvent eventType = PostgresChangeEvent.all, OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.alwaysHydrate, Query? query, @@ -363,7 +356,7 @@ abstract class OfflineFirstWithSupabaseRepository } @override - Future upsert( + Future upsert( TModel instance, { OfflineFirstUpsertPolicy policy = OfflineFirstUpsertPolicy.optimisticLocal, Query? query, diff --git a/packages/brick_sqlite/README.md b/packages/brick_sqlite/README.md index 073a9044..8a1b86ce 100644 --- a/packages/brick_sqlite/README.md +++ b/packages/brick_sqlite/README.md @@ -10,12 +10,12 @@ Local storage for Flutter apps using [Brick](https://github.com/GetDutchie/brick The following map exactly to their SQLite keywords. The values will be inserted into a SQLite statement **without being prepared**. -* `collate` -* `having` -* `groupBy` -* `limit` -* `offset` -* `orderBy` +- `collate` +- `having` +- `groupBy` +- `limit` +- `offset` +- `orderBy` As the values are directly inserted, use the field name: @@ -109,39 +109,41 @@ instanceFromSqlite.associations.length // => 0 The following are not serialized to SQLite. However, unsupported types can still be accessed in the model as non-final fields. -* Nested `List<>` e.g. `>>` -* Many-to-many associations +- Nested `List<>` e.g. `>>` +- Many-to-many associations ## Multiplatform Support Brick SQLite can be used when developing for Windows, MacOS, and Linux platforms. **The following is not required for iOS and Android development except in a test environment.**. 1. Add sqflite_common packages to your pubspec. If you're stubbing SQLite responses for testing, the packages only need to be added under `dev_dependencies:`. - ```yaml - sqflite_common: any - sqflite_common_ffi: any - ``` + + ```yaml + sqflite_common: any + sqflite_common_ffi: any + ``` 1. Use the [SQLite FFI](https://github.com/tekartik/sqflite/tree/master/sqflite_common_ffi) database factory when initializing your provider: - ```dart - import 'package:sqflite_common/sqlite_api.dart'; - import 'package:sqflite_common_ffi/sqflite_ffi.dart'; - - MyRepository( - sqliteProvider: SqliteProvider( - inMemoryDatabase, - databaseFactory: databaseFactoryFfi, - ), - ); - ``` + + ```dart + import 'package:sqflite_common/sqlite_api.dart'; + import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + + MyRepository( + sqliteProvider: SqliteProvider( + inMemoryDatabase, + databaseFactory: databaseFactoryFfi, + ), + ); + ``` 1. Make sure FFI is initialized when starting your app or running unit tests: - ```dart - void main() { - sqfliteFfiInit(); - runApp(MyApp()) - } - ``` + ```dart + void main() { + sqfliteFfiInit(); + runApp(MyApp()) + } + ``` ## Testing @@ -186,9 +188,3 @@ MemoryCacheProvider([Hat]) ``` It is not recommended to use this provider with parent models that have child associations, as those children may be updated in the future without notifying the parent. - -## FAQ - -### Why can't I declare a model argument? - -Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument. diff --git a/packages/brick_sqlite/lib/memory_cache_provider.dart b/packages/brick_sqlite/lib/memory_cache_provider.dart index f5da74e6..9ac8f1f1 100644 --- a/packages/brick_sqlite/lib/memory_cache_provider.dart +++ b/packages/brick_sqlite/lib/memory_cache_provider.dart @@ -9,7 +9,7 @@ import 'package:meta/meta.dart'; /// /// MemoryCacheProvider does not have a type argument due to a build_runner /// exception: https://github.com/dart-lang/sdk/issues/38309 -class MemoryCacheProvider extends Provider { +class MemoryCacheProvider extends Provider { @protected final Logger logger = Logger('MemoryCacheProvider'); @@ -23,7 +23,7 @@ class MemoryCacheProvider extends Provider { final modelDictionary = _MemoryCacheModelDictionary(); /// A complete hash table of the - Map> managedObjects = {}; + Map> managedObjects = {}; /// Is the [type] cached by this provider? bool manages(Type type) => managedModelTypes.contains(type); @@ -40,13 +40,13 @@ class MemoryCacheProvider extends Provider { /// basic lookups such as a single field (primary key). /// However, if the provider is extended to support complex [Where] statements in [get], /// this method should also be extended. - bool canFind([Query? query]) { + bool canFind([Query? query]) { final byPrimaryKey = Where.firstByField(InsertTable.PRIMARY_KEY_FIELD, query?.where); return manages(TModel) && byPrimaryKey?.value != null; } @override - bool delete(instance, {query, repository}) { + bool delete(instance, {query, repository}) { if (!manages(TModel)) return false; logger.finest('#delete: $TModel, $instance, $query'); @@ -56,7 +56,7 @@ class MemoryCacheProvider extends Provider { } @override - List? get({query, repository}) { + List? get({query, repository}) { if (!manages(TModel)) return null; managedObjects[TModel] ??= {}; @@ -79,7 +79,7 @@ class MemoryCacheProvider extends Provider { /// For convenience, the return value is the argument [models], /// **not** the complete set of managed [TModel]s. /// If the managed models are desired instead, use [get]. - List hydrate(List models) { + List hydrate(List models) { if (!manages(TModel)) return models; managedObjects[TModel] ??= {}; @@ -98,7 +98,7 @@ class MemoryCacheProvider extends Provider { } @override - TModel? upsert(instance, {query, repository}) { + TModel? upsert(instance, {query, repository}) { if (!manages(TModel)) return null; logger.finest('#upsert: $TModel, $instance, $query'); hydrate([instance]); diff --git a/packages/brick_sqlite/lib/src/sqlite_provider.dart b/packages/brick_sqlite/lib/src/sqlite_provider.dart index ceb96be2..299b03a4 100644 --- a/packages/brick_sqlite/lib/src/sqlite_provider.dart +++ b/packages/brick_sqlite/lib/src/sqlite_provider.dart @@ -15,7 +15,7 @@ import 'package:sqflite_common/utils/utils.dart' as sqlite_utils; import 'package:synchronized/synchronized.dart'; /// Retrieves from a Sqlite database -class SqliteProvider implements Provider { +class SqliteProvider implements Provider { /// Access the [SQLite](https://github.com/tekartik/sqflite/tree/master/sqflite_common_ffi), /// instance agnostically across platforms. @protected @@ -51,7 +51,7 @@ class SqliteProvider implements Provider { /// Remove record from SQLite. [query] is ignored. @override - Future delete(instance, {query, repository}) async { + Future delete(instance, {query, repository}) async { final adapter = modelDictionary.adapterFor[TModel]!; final db = await getDb(); final existingPrimaryKey = await adapter.primaryKeyByUniqueColumns(instance, db); @@ -73,9 +73,9 @@ class SqliteProvider implements Provider { /// /// If [query.where] is `null`, existence for **any** record is executed. @override - Future exists({ + Future exists({ Query? query, - ModelRepository? repository, + ModelRepository? repository, }) async { final sqlQuery = QuerySqlTransformer( modelDictionary: modelDictionary, @@ -119,7 +119,7 @@ class SqliteProvider implements Provider { /// equal `'providerArgs': { 'orderBy': 'created_at ASC, name ASC' }` with column names defined. /// As Brick manages column names, this is not recommended and should be written only when necessary. @override - Future> get({ + Future> get({ query, repository, }) async { @@ -205,10 +205,10 @@ class SqliteProvider implements Provider { /// Fetch results for model with a custom SQL statement. /// It is recommended to use [get] whenever possible. **Advanced use only**. - Future> rawGet( + Future> rawGet( String sql, List arguments, { - ModelRepository? repository, + ModelRepository? repository, }) async { final adapter = modelDictionary.adapterFor[TModel]!; @@ -272,7 +272,7 @@ class SqliteProvider implements Provider { /// Insert record into SQLite. Returns the primary key of the record inserted @override - Future upsert(instance, {query, repository}) async { + Future upsert(instance, {query, repository}) async { final adapter = modelDictionary.adapterFor[TModel]!; final db = await getDb();