Announcing the Supabase Integration
Brick has a first-party integration with Supabase, the open-source Firebase alternative with exponential momentum. After being heavily requested in #359 , the first stable release has finally landed on pub.dev.
Quick Start
Adding a Supabase provider to your repository is almost identical to adding a REST or GraphQL provider. You'll need just a little extra sauce to integrate an offline queue that will retry requests made while your app was offline:
final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(
// For Flutter, use import 'package:sqflite/sqflite.dart' show databaseFactory;
databaseFactory: databaseFactory,
);
final provider = SupabaseProvider(
SupabaseClient(supabaseUrl, supabaseAnonKey, httpClient: client),
modelDictionary: supabaseModelDictionary,
);
// For Flutter, initialize with this created client:
// final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(databaseFactory: databaseFactory);
// await Supabase.initialize(httpClient: client)
// final provider = SupabaseProvider(Supabase.instance.client, modelDictionary: supabaseModelDictionary)
_singleton = MyRepository._(
supabaseProvider: provider,
sqliteProvider: SqliteProvider(
'my_repository.sqlite',
databaseFactory: databaseFactory,
modelDictionary: sqliteModelDictionary,
),
migrations: migrations,
offlineRequestQueue: queue,
memoryCacheProvider: MemoryCacheProvider(),
);
Associations
Brick will generate all the necessary code for retrieving and upserting associations as well as other fields:
class Customer extends BrickOfflineFirstWithSupabaseModel {
@Supabase(unique: true)
final String id;
}
class Pizza extends BrickOfflineFirstWithSupabaseModel {
@Supabase(unique: true)
final String id;
final Customer customer;
}
Querying
Association querying with Supabase is automatically handled, even for nested queries:
await repository.get<Customer>(query: Where.exact('id', 'abcd-1234'));
await repository.get<Customer>(query: Query.where('pizza', Where.exact('frozen', true));
Note that not all of Supabase's extensive PostgREST operators are handled.
Creating
All associations of a model are upserted behind the scenes. You only need to upsert the parent model to ensure its dependencies also reach Supabase:
// Upserts each customer.pizzas first
await repository.upsert<Customer>(customer);
Testing
Quickly mock your Supabase endpoints to add uncluttered unit testing:
final mock = SupabaseMockServer(modelDictionary: supabaseModelDictionary);
group('MyClass', () {
setUp(mock.setUp);
tearDown(mock.tearDown);
test('#myMethod', () async {
final req = SupabaseRequest<MyModel>();
final resp = SupabaseResponse([
await mock.serialize(MyModel(name: 'Demo 1', id: '1')),
await mock.serialize(MyModel(name: 'Demo 2', id: '2')),
]);
mock.handle({req: resp});
final provider = SupabaseProvider(mock.client, modelDictionary: supabaseModelDictionary);
final retrieved = await provider.get<MyModel>();
expect(retrieved, hasLength(2));
});
});
Like the rest of Brick's domains, the Supabase integration brings straightforward offline capability to your app. Give it a spin and let us know how it works for you.
Finally, many thanks to @devj3ns for the extremely patient and thorough testing of the beta and alpha releases. We had a lot of back and forth on every issue and PR in #401 , so there will definitely never be a production bug.
Awesome 🎉 Thanks for all the work you put into this @tshedor! I enjoyed the collaboration. This is a big win for the Flutter developer community.
@tshedor hello! Thank you very much for your work. I am trying to use this technology stack in a Flutter app. As I understand, that could be used instead of Riverpod\Bloc for requests and persistence.
My question is, what is the best practice to still use Supabase auth / edge functions along side it? I only see direct db usage, like .get in official examples and no ability to get Supabase client directly (as I understand it is a good practice to use a single client to be in sync with all Brick operations). Looking for the best way to implement Supabase sign in, store user session and clear all user specific data on logout with Brick.
What would you recommend? Maybe you could show a quick code snippet for it? Thank you once again!
@ivanburlakov thanks for the comment.
As I understand, that could be used instead of Riverpod\Bloc for requests and persistence.
Real quick, I want to clarify that Brick is not inherently stateful - you should still use a state manager like Riverpod or Bloc. These state managers should call out to Brick to populate their data.
My question is, what is the best practice to still use Supabase auth / edge functions along side it?
Use Supabase.instance.client.auth or Supabase.instance.client.functions as normal.
By default, Brick will retry function requests, but it will not retry auth or storage requests (this default behavior can be configured). When you initialize Brick using the offline client, it attaches the offline client to the Supabase.instance.client, but Supabase.instance.client is still available for use throughout your Flutter app.