GetDutchie / brick

An intuitive way to work with persistent data in Dart
https://getdutchie.github.io/brick/#/
300 stars 27 forks source link

Announcing the Supabase Integration #445

Open tshedor opened 1 day ago

tshedor commented 1 day ago

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.

devj3ns commented 1 day ago

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.