-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eng(supabase): add supabase example (#392)
- Loading branch information
Showing
12 changed files
with
534 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Brick with Supabase Example | ||
|
||
This minimal example demonstrates how to use Brick with [Supabase](https://supabase.com/). Follow the instructions below to get started. | ||
|
||
Every Supabase project comes with a [ready-to-use REST API](https://supabase.com/docs/guides/api) using [PostgREST](https://postgrest.org/) which Brick can use to interact with the database. | ||
|
||
## Setting up the Supabase project | ||
|
||
1. **Create the table**: Run the following SQL command in your Supabase SQL editor to create the customers table: | ||
|
||
```sql | ||
CREATE TABLE customers ( | ||
id UUID PRIMARY KEY, | ||
first_name text NOT NULL, | ||
last_name text NOT NULL, | ||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
); | ||
``` | ||
|
||
2. **Insert Dummy Data**: Insert some dummy data into the customers table by running the following SQL command | ||
|
||
```sql | ||
INSERT INTO customers (id, first_name, last_name, created_at) VALUES | ||
('a8098c1a-f86e-11da-bd1a-00112444be1e', 'Bruce', 'Fortner', NOW()), | ||
('b8098c1a-f86e-11da-bd1a-00112444be1e', 'Jane', 'Smith', NOW()), | ||
('c8098c1a-f86e-11da-bd1a-00112444be1e', 'Alice', 'Johnson', NOW()); | ||
``` | ||
|
||
|
||
3. **Enable Anonymous Sign-Ins**: Go to your Supabase dashboard, navigate to Settings > Authentication > User Signups, and enable anonymous sign-ins. | ||
|
||
## Setting up the Flutter example project | ||
|
||
4. **Update Environment Variables**: Open the `lib/env.dart` file and update it with your Supabase project URL and anonymous key. You can find these values in the Supabase dashboard under Settings > API. | ||
|
||
## Running the Flutter app | ||
|
||
5. **Run the Flutter Project**: This example supports iOS and Android. Make sure run `flutter create .` first. |
126 changes: 126 additions & 0 deletions
126
example_supabase/lib/brick/adapters/customer_adapter.g.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// GENERATED CODE DO NOT EDIT | ||
part of '../brick.g.dart'; | ||
|
||
Future<Customer> _$CustomerFromRest(Map<String, dynamic> data, | ||
{required RestProvider provider, | ||
OfflineFirstWithRestRepository? repository}) async { | ||
return Customer( | ||
id: data['id'] as String, | ||
firstName: data['first_name'] as String, | ||
lastName: data['last_name'] as String, | ||
createdAt: DateTime.parse(data['created_at'] as String)); | ||
} | ||
|
||
Future<Map<String, dynamic>> _$CustomerToRest(Customer instance, | ||
{required RestProvider provider, | ||
OfflineFirstWithRestRepository? repository}) async { | ||
return { | ||
'id': instance.id, | ||
'first_name': instance.firstName, | ||
'last_name': instance.lastName, | ||
'created_at': instance.createdAt.toIso8601String() | ||
}; | ||
} | ||
|
||
Future<Customer> _$CustomerFromSqlite(Map<String, dynamic> data, | ||
{required SqliteProvider provider, | ||
OfflineFirstWithRestRepository? repository}) async { | ||
return Customer( | ||
id: data['id'] as String, | ||
firstName: data['first_name'] as String, | ||
lastName: data['last_name'] as String, | ||
createdAt: DateTime.parse(data['created_at'] as String)) | ||
..primaryKey = data['_brick_id'] as int; | ||
} | ||
|
||
Future<Map<String, dynamic>> _$CustomerToSqlite(Customer instance, | ||
{required SqliteProvider provider, | ||
OfflineFirstWithRestRepository? repository}) async { | ||
return { | ||
'id': instance.id, | ||
'first_name': instance.firstName, | ||
'last_name': instance.lastName, | ||
'created_at': instance.createdAt.toIso8601String() | ||
}; | ||
} | ||
|
||
/// Construct a [Customer] | ||
class CustomerAdapter extends OfflineFirstWithRestAdapter<Customer> { | ||
CustomerAdapter(); | ||
|
||
@override | ||
final restRequest = CustomerRequestTransformer.new; | ||
@override | ||
final Map<String, RuntimeSqliteColumnDefinition> fieldsToSqliteColumns = { | ||
'primaryKey': const RuntimeSqliteColumnDefinition( | ||
association: false, | ||
columnName: '_brick_id', | ||
iterable: false, | ||
type: int, | ||
), | ||
'id': const RuntimeSqliteColumnDefinition( | ||
association: false, | ||
columnName: 'id', | ||
iterable: false, | ||
type: String, | ||
), | ||
'firstName': const RuntimeSqliteColumnDefinition( | ||
association: false, | ||
columnName: 'first_name', | ||
iterable: false, | ||
type: String, | ||
), | ||
'lastName': const RuntimeSqliteColumnDefinition( | ||
association: false, | ||
columnName: 'last_name', | ||
iterable: false, | ||
type: String, | ||
), | ||
'createdAt': const RuntimeSqliteColumnDefinition( | ||
association: false, | ||
columnName: 'created_at', | ||
iterable: false, | ||
type: DateTime, | ||
) | ||
}; | ||
@override | ||
Future<int?> primaryKeyByUniqueColumns( | ||
Customer instance, DatabaseExecutor executor) async { | ||
final results = await executor.rawQuery(''' | ||
SELECT * FROM `Customer` WHERE id = ? LIMIT 1''', [instance.id]); | ||
|
||
// SQFlite returns [{}] when no results are found | ||
if (results.isEmpty || (results.length == 1 && results.first.isEmpty)) { | ||
return null; | ||
} | ||
|
||
return results.first['_brick_id'] as int; | ||
} | ||
|
||
@override | ||
final String tableName = 'Customer'; | ||
|
||
@override | ||
Future<Customer> fromRest(Map<String, dynamic> input, | ||
{required provider, | ||
covariant OfflineFirstWithRestRepository? repository}) async => | ||
await _$CustomerFromRest(input, | ||
provider: provider, repository: repository); | ||
@override | ||
Future<Map<String, dynamic>> toRest(Customer input, | ||
{required provider, | ||
covariant OfflineFirstWithRestRepository? repository}) async => | ||
await _$CustomerToRest(input, provider: provider, repository: repository); | ||
@override | ||
Future<Customer> fromSqlite(Map<String, dynamic> input, | ||
{required provider, | ||
covariant OfflineFirstWithRestRepository? repository}) async => | ||
await _$CustomerFromSqlite(input, | ||
provider: provider, repository: repository); | ||
@override | ||
Future<Map<String, dynamic>> toSqlite(Customer input, | ||
{required provider, | ||
covariant OfflineFirstWithRestRepository? repository}) async => | ||
await _$CustomerToSqlite(input, | ||
provider: provider, repository: repository); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_core/query.dart'; | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_sqlite/db.dart'; | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_offline_first_with_rest/brick_offline_first_with_rest.dart'; | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_rest/brick_rest.dart'; | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_sqlite/brick_sqlite.dart'; | ||
// ignore: unused_import, unused_shown_name, unnecessary_import | ||
import 'package:brick_supabase/brick/models/customer.model.request.dart';// GENERATED CODE DO NOT EDIT | ||
// ignore: unused_import | ||
import 'dart:convert'; | ||
import 'package:brick_sqlite/brick_sqlite.dart' show SqliteModel, SqliteAdapter, SqliteModelDictionary, RuntimeSqliteColumnDefinition, SqliteProvider; | ||
import 'package:brick_rest/brick_rest.dart' show RestProvider, RestModel, RestAdapter, RestModelDictionary; | ||
// ignore: unused_import, unused_shown_name | ||
import 'package:brick_offline_first/brick_offline_first.dart' show RuntimeOfflineFirstDefinition; | ||
// ignore: unused_import, unused_shown_name | ||
import 'package:sqflite_common/sqlite_api.dart' show DatabaseExecutor; | ||
|
||
import '../brick/models/customer.model.dart'; | ||
|
||
part 'adapters/customer_adapter.g.dart'; | ||
|
||
/// Rest mappings should only be used when initializing a [RestProvider] | ||
final Map<Type, RestAdapter<RestModel>> restMappings = { | ||
Customer: CustomerAdapter() | ||
}; | ||
final restModelDictionary = RestModelDictionary(restMappings); | ||
|
||
/// Sqlite mappings should only be used when initializing a [SqliteProvider] | ||
final Map<Type, SqliteAdapter<SqliteModel>> sqliteMappings = { | ||
Customer: CustomerAdapter() | ||
}; | ||
final sqliteModelDictionary = SqliteModelDictionary(sqliteMappings); |
44 changes: 44 additions & 0 deletions
44
example_supabase/lib/brick/db/20240714103011.migration.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// GENERATED CODE EDIT WITH CAUTION | ||
// THIS FILE **WILL NOT** BE REGENERATED | ||
// This file should be version controlled and can be manually edited. | ||
part of 'schema.g.dart'; | ||
|
||
// While migrations are intelligently created, the difference between some commands, such as | ||
// DropTable vs. RenameTable, cannot be determined. For this reason, please review migrations after | ||
// they are created to ensure the correct inference was made. | ||
|
||
// The migration version must **always** mirror the file name | ||
|
||
const List<MigrationCommand> _migration_20240714103011_up = [ | ||
InsertTable('Customer'), | ||
InsertColumn('id', Column.varchar, onTable: 'Customer', unique: true), | ||
InsertColumn('first_name', Column.varchar, onTable: 'Customer'), | ||
InsertColumn('last_name', Column.varchar, onTable: 'Customer'), | ||
InsertColumn('created_at', Column.datetime, onTable: 'Customer'), | ||
]; | ||
|
||
const List<MigrationCommand> _migration_20240714103011_down = [ | ||
DropTable('Customer'), | ||
DropColumn('id', onTable: 'Customer'), | ||
DropColumn('first_name', onTable: 'Customer'), | ||
DropColumn('last_name', onTable: 'Customer'), | ||
DropColumn('created_at', onTable: 'Customer'), | ||
]; | ||
|
||
// | ||
// DO NOT EDIT BELOW THIS LINE | ||
// | ||
|
||
@Migratable( | ||
version: '20240714103011', | ||
up: _migration_20240714103011_up, | ||
down: _migration_20240714103011_down, | ||
) | ||
class Migration20240714103011 extends Migration { | ||
const Migration20240714103011() | ||
: super( | ||
version: 20240714103011, | ||
up: _migration_20240714103011_up, | ||
down: _migration_20240714103011_down, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// GENERATED CODE DO NOT EDIT | ||
// This file should be version controlled | ||
import 'package:brick_sqlite/db.dart'; | ||
part '20240714103011.migration.dart'; | ||
|
||
/// All intelligently-generated migrations from all `@Migratable` classes on disk | ||
final migrations = <Migration>{ | ||
const Migration20240714103011(),}; | ||
|
||
/// A consumable database structure including the latest generated migration. | ||
final schema = Schema(20240714103011, generatorVersion: 1, tables: <SchemaTable>{ | ||
SchemaTable('Customer', columns: <SchemaColumn>{ | ||
SchemaColumn('_brick_id', Column.integer, | ||
autoincrement: true, nullable: false, isPrimaryKey: true), | ||
SchemaColumn('id', Column.varchar, unique: true), | ||
SchemaColumn('first_name', Column.varchar), | ||
SchemaColumn('last_name', Column.varchar), | ||
SchemaColumn('created_at', Column.datetime) | ||
}, indices: <SchemaIndex>{}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import 'package:brick_offline_first_with_rest/brick_offline_first_with_rest.dart'; | ||
import 'package:brick_rest/brick_rest.dart'; | ||
import 'package:brick_sqlite/brick_sqlite.dart'; | ||
import 'package:brick_supabase/brick/models/customer.model.request.dart'; | ||
|
||
@ConnectOfflineFirstWithRest( | ||
restConfig: | ||
RestSerializable(requestTransformer: CustomerRequestTransformer.new), | ||
) | ||
class Customer extends OfflineFirstWithRestModel { | ||
@Sqlite(unique: true) | ||
final String id; | ||
final String firstName; | ||
final String lastName; | ||
final DateTime createdAt; | ||
|
||
Customer({ | ||
required this.id, | ||
required this.firstName, | ||
required this.lastName, | ||
required this.createdAt, | ||
}); | ||
} |
20 changes: 20 additions & 0 deletions
20
example_supabase/lib/brick/models/customer.model.request.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import 'package:brick_rest/brick_rest.dart'; | ||
|
||
// NOTE: This is just a minimal example, you may need to adjust the transformer | ||
// to support querying/filtering. See the linked PostgREST docs below. | ||
|
||
class CustomerRequestTransformer extends RestRequestTransformer { | ||
@override | ||
// see https://postgrest.org/en/v12/references/api/tables_views.html#read | ||
RestRequest get get => RestRequest(url: '/customers'); | ||
|
||
@override | ||
// see: https://postgrest.org/en/v12/references/api/tables_views.html#upsert | ||
RestRequest get upsert => RestRequest(url: '/customers'); | ||
|
||
@override | ||
// see https://postgrest.org/en/v12/references/api/tables_views.html#delete | ||
RestRequest get delete => throw UnimplementedError(); | ||
|
||
const CustomerRequestTransformer(super.query, super.instance); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import 'package:brick_offline_first_with_rest/brick_offline_first_with_rest.dart'; | ||
import 'package:brick_rest/brick_rest.dart'; | ||
import 'package:brick_sqlite/brick_sqlite.dart'; | ||
import 'package:brick_supabase/brick/brick.g.dart'; | ||
import 'package:brick_supabase/brick/db/schema.g.dart'; | ||
import 'package:brick_supabase/brick/supabase_brick_client.dart'; | ||
import 'package:brick_supabase/env.dart'; | ||
import 'package:sqflite/sqflite.dart' show databaseFactory; | ||
|
||
class Repository extends OfflineFirstWithRestRepository { | ||
Repository._() | ||
: super( | ||
restProvider: RestProvider( | ||
'${SUPABASE_PROJECT_URL}/rest/v1', | ||
modelDictionary: restModelDictionary, | ||
client: SupabaseBrickClient( | ||
anonKey: SUPABASE_ANON_KEY, | ||
), | ||
), | ||
sqliteProvider: SqliteProvider( | ||
'brick_db.sqlite', | ||
databaseFactory: databaseFactory, | ||
modelDictionary: sqliteModelDictionary, | ||
), | ||
offlineQueueManager: RestRequestSqliteCacheManager( | ||
'brick_offline_queue.sqlite', | ||
databaseFactory: databaseFactory, | ||
), | ||
migrations: migrations, | ||
); | ||
|
||
factory Repository() => _singleton!; | ||
|
||
static Repository? _singleton; | ||
|
||
static void configure() { | ||
_singleton = Repository._(); | ||
} | ||
} |
Oops, something went wrong.