Skip to content

Commit

Permalink
Merge branch 'supabase-integration' into update-package-specs-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tshedor committed Aug 26, 2024
2 parents f118141 + e9d378a commit dd77e75
Show file tree
Hide file tree
Showing 17 changed files with 575 additions and 322 deletions.
9 changes: 3 additions & 6 deletions example_supabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

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:
Expand All @@ -20,19 +18,18 @@ CREATE TABLE customers (
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
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.
4. **Update Variables**: Update `main.dart` 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.
5. **Run the Flutter Project**: This example supports iOS and Android. Make sure run `flutter create .` first.
Empty file.
166 changes: 109 additions & 57 deletions example_supabase/lib/brick/adapters/customer_adapter.g.dart
Original file line number Diff line number Diff line change
@@ -1,55 +1,90 @@
// GENERATED CODE DO NOT EDIT
part of '../brick.g.dart';

Future<Customer> _$CustomerFromRest(Map<String, dynamic> data,
{required RestProvider provider,
OfflineFirstWithRestRepository? repository}) async {
Future<Customer> _$CustomerFromSupabase(Map<String, dynamic> data,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? 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));
id: data['id'] as int?,
firstName: data['first_name'] as String?,
lastName: data['last_name'] as String?,
pizzas: await Future.wait<Pizza>(data['pizzas']
?.map(
(d) => PizzaAdapter().fromSupabase(d, provider: provider, repository: repository))
.toList()
.cast<Future<Pizza>>() ??
[]));
}

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<Map<String, dynamic>> _$CustomerToSupabase(Customer instance,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return {'id': instance.id, 'first_name': instance.firstName, 'last_name': instance.lastName};
}

Future<Customer> _$CustomerFromSqlite(Map<String, dynamic> data,
{required SqliteProvider provider,
OfflineFirstWithRestRepository? repository}) async {
{required SqliteProvider provider, OfflineFirstWithSupabaseRepository? 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))
id: data['id'] == null ? null : data['id'] as int?,
firstName: data['first_name'] == null ? null : data['first_name'] as String?,
lastName: data['last_name'] == null ? null : data['last_name'] as String?,
pizzas: (await provider.rawQuery(
'SELECT DISTINCT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE l_Customer_brick_id = ?',
[data['_brick_id'] as int]).then((results) {
final ids = results.map((r) => r['f_Pizza_brick_id']);
return Future.wait<Pizza>(ids.map((primaryKey) => repository!
.getAssociation<Pizza>(
Query.where('primaryKey', primaryKey, limit1: true),
)
.then((r) => r!.first)));
}))
.toList()
.cast<Pizza>())
..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()
};
{required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return {'id': instance.id, 'first_name': instance.firstName, 'last_name': instance.lastName};
}

/// Construct a [Customer]
class CustomerAdapter extends OfflineFirstWithRestAdapter<Customer> {
class CustomerAdapter extends OfflineFirstWithSupabaseAdapter<Customer> {
CustomerAdapter();

@override
final restRequest = CustomerRequestTransformer.new;
final tableName = 'customers';
@override
final defaultToNull = true;
@override
final Map<String, RuntimeSqliteColumnDefinition> fieldsToSqliteColumns = {
'id': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: int,
columnName: 'id',
),
'firstName': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: String,
columnName: 'first_name',
),
'lastName': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: String,
columnName: 'last_name',
),
'pizzas': const RuntimeSupabaseColumnDefinition(
association: true,
associationForeignKey: 'null',
associationType: Pizza,
columnName: 'pizzas',
)
};
@override
final ignoreDuplicates = false;
@override
final uniqueFields = {};
@override
final Map<String, RuntimeSqliteColumnDefinition> fieldsToSqliteColumns = {
'primaryKey': const RuntimeSqliteColumnDefinition(
Expand All @@ -62,7 +97,7 @@ class CustomerAdapter extends OfflineFirstWithRestAdapter<Customer> {
association: false,
columnName: 'id',
iterable: false,
type: String,
type: int,
),
'firstName': const RuntimeSqliteColumnDefinition(
association: false,
Expand All @@ -76,16 +111,15 @@ class CustomerAdapter extends OfflineFirstWithRestAdapter<Customer> {
iterable: false,
type: String,
),
'createdAt': const RuntimeSqliteColumnDefinition(
association: false,
columnName: 'created_at',
iterable: false,
type: DateTime,
'pizzas': const RuntimeSqliteColumnDefinition(
association: true,
columnName: 'pizzas',
iterable: true,
type: Pizza,
)
};
@override
Future<int?> primaryKeyByUniqueColumns(
Customer instance, DatabaseExecutor executor) async {
Future<int?> primaryKeyByUniqueColumns(Customer instance, DatabaseExecutor executor) async {
final results = await executor.rawQuery('''
SELECT * FROM `Customer` WHERE id = ? LIMIT 1''', [instance.id]);

Expand All @@ -99,28 +133,46 @@ class CustomerAdapter extends OfflineFirstWithRestAdapter<Customer> {

@override
final String tableName = 'Customer';
@override
Future<void> afterSave(instance, {required provider, repository}) async {
if (instance.primaryKey != null) {
final pizzasOldColumns = await provider.rawQuery(
'SELECT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ?',
[instance.primaryKey]);
final pizzasOldIds = pizzasOldColumns.map((a) => a['f_Pizza_brick_id']);
final pizzasNewIds = instance.pizzas?.map((s) => s.primaryKey).whereType<int>() ?? [];
final pizzasIdsToDelete = pizzasOldIds.where((id) => !pizzasNewIds.contains(id));

await Future.wait<void>(pizzasIdsToDelete.map((id) async {
return await provider.rawExecute(
'DELETE FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ? AND `f_Pizza_brick_id` = ?',
[instance.primaryKey, id]).catchError((e) => null);
}));

await Future.wait<int?>(instance.pizzas?.map((s) async {
final id = s.primaryKey ?? await provider.upsert<Pizza>(s, repository: repository);
return await provider.rawInsert(
'INSERT OR IGNORE INTO `_brick_Customer_pizzas` (`l_Customer_brick_id`, `f_Pizza_brick_id`) VALUES (?, ?)',
[instance.primaryKey, id]);
}) ??
[]);
}
}

@override
Future<Customer> fromRest(Map<String, dynamic> input,
{required provider,
covariant OfflineFirstWithRestRepository? repository}) async =>
await _$CustomerFromRest(input,
provider: provider, repository: repository);
Future<Customer> fromSupabase(Map<String, dynamic> input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$CustomerFromSupabase(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);
Future<Map<String, dynamic>> toSupabase(Customer input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$CustomerToSupabase(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);
{required provider, covariant OfflineFirstWithSupabaseRepository? 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);
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$CustomerToSqlite(input, provider: provider, repository: repository);
}
135 changes: 135 additions & 0 deletions example_supabase/lib/brick/adapters/pizza_adapter.g.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// GENERATED CODE DO NOT EDIT
part of '../brick.g.dart';

Future<Pizza> _$PizzaFromSupabase(Map<String, dynamic> data,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return Pizza(
id: data['id'] as int,
toppings: data['toppings'].map(Topping.values.byName).toList().cast<Topping>(),
frozen: data['frozen'] as bool);
}

Future<Map<String, dynamic>> _$PizzaToSupabase(Pizza instance,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return {
'id': instance.id,
'toppings': instance.toppings.map((e) => e.name).toList(),
'frozen': instance.frozen
};
}

Future<Pizza> _$PizzaFromSqlite(Map<String, dynamic> data,
{required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return Pizza(
id: data['id'] as int,
toppings: jsonDecode(data['toppings'])
.map((d) => d as int > -1 ? Topping.values[d] : null)
.whereType<Topping>()
.toList()
.cast<Topping>(),
frozen: data['frozen'] == 1)
..primaryKey = data['_brick_id'] as int;
}

Future<Map<String, dynamic>> _$PizzaToSqlite(Pizza instance,
{required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return {
'id': instance.id,
'toppings': jsonEncode(instance.toppings.map((s) => Topping.values.indexOf(s)).toList()),
'frozen': instance.frozen ? 1 : 0
};
}

/// Construct a [Pizza]
class PizzaAdapter extends OfflineFirstWithSupabaseAdapter<Pizza> {
PizzaAdapter();

@override
final tableName = 'pizzas';
@override
final defaultToNull = true;
@override
final Map<String, RuntimeSqliteColumnDefinition> fieldsToSqliteColumns = {
'id': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: int,
columnName: 'id',
),
'toppings': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: Topping,
columnName: 'toppings',
),
'frozen': const RuntimeSupabaseColumnDefinition(
association: false,
associationForeignKey: 'null',
associationType: bool,
columnName: 'frozen',
)
};
@override
final ignoreDuplicates = false;
@override
final uniqueFields = {};
@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: int,
),
'toppings': const RuntimeSqliteColumnDefinition(
association: false,
columnName: 'toppings',
iterable: true,
type: Topping,
),
'frozen': const RuntimeSqliteColumnDefinition(
association: false,
columnName: 'frozen',
iterable: false,
type: bool,
)
};
@override
Future<int?> primaryKeyByUniqueColumns(Pizza instance, DatabaseExecutor executor) async {
final results = await executor.rawQuery('''
SELECT * FROM `Pizza` 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 = 'Pizza';

@override
Future<Pizza> fromSupabase(Map<String, dynamic> input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$PizzaFromSupabase(input, provider: provider, repository: repository);
@override
Future<Map<String, dynamic>> toSupabase(Pizza input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$PizzaToSupabase(input, provider: provider, repository: repository);
@override
Future<Pizza> fromSqlite(Map<String, dynamic> input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$PizzaFromSqlite(input, provider: provider, repository: repository);
@override
Future<Map<String, dynamic>> toSqlite(Pizza input,
{required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async =>
await _$PizzaToSqlite(input, provider: provider, repository: repository);
}
Loading

0 comments on commit dd77e75

Please sign in to comment.