Skip to content

Commit

Permalink
eng(supabase): add supabase example (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
devj3ns authored Jul 17, 2024
1 parent b56367e commit 278aa99
Show file tree
Hide file tree
Showing 12 changed files with 534 additions and 0 deletions.
38 changes: 38 additions & 0 deletions example_supabase/README.md
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 example_supabase/lib/brick/adapters/customer_adapter.g.dart
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);
}
36 changes: 36 additions & 0 deletions example_supabase/lib/brick/brick.g.dart
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 example_supabase/lib/brick/db/20240714103011.migration.dart
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,
);
}
20 changes: 20 additions & 0 deletions example_supabase/lib/brick/db/schema.g.dart
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>{})
});
23 changes: 23 additions & 0 deletions example_supabase/lib/brick/models/customer.model.dart
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 example_supabase/lib/brick/models/customer.model.request.dart
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);
}
39 changes: 39 additions & 0 deletions example_supabase/lib/brick/repository.dart
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._();
}
}
Loading

0 comments on commit 278aa99

Please sign in to comment.