惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tor Project blog
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
H
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
博客园 - 司徒正美
Jina AI
Jina AI
雷峰网
雷峰网
博客园_首页
博客园 - 三生石上(FineUI控件)
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Forbes - Security
Forbes - Security
T
Threatpost
V
Visual Studio Blog
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
Security Latest
Security Latest
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
Vercel News
Vercel News
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
NISL@THU
NISL@THU
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
PCI Perspectives
PCI Perspectives
博客园 - 【当耐特】
IT之家
IT之家
S
Secure Thoughts
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
Cisco Talos Blog
Cisco Talos Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
Latest news
Latest news
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
I
Intezer
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
大猫的无限游戏
大猫的无限游戏

Supabase Blog

AI Agents Know About Supabase. They Don't Always Use It Right. Custom OIDC Providers for Supabase Auth 100,000 GitHub stars Supabase docs over SSH Navigating Regional Network Blocks Supabase Joins the Stripe Projects Developer Preview Log Drains: Now available on Pro Supabase Storage: major performance, security, and reliability updates Supabase incident on February 12, 2026 Hydra joins Supabase X / Twitter OAuth 2.0 is now available for Supabase Auth BKND joins Supabase Supabase is now an official Claude connector Supabase PrivateLink is now available Introducing: Postgres Best Practices When to use Read Replicas vs. bigger compute Introducing TRAE SOLO integration with Supabase Supabase Security Retro: 2025 Sync Stripe Data to Your Supabase Database in One Click Building ChatGPT Apps with Supabase Edge Functions and mcp-use Own Your Observability: Supabase Metrics API Introducing iceberg-js: A JavaScript Client for Apache Iceberg Introducing Supabase for Platforms Adding Async Streaming to Postgres Foreign Data Wrappers Build "Sign in with Your App" using Supabase Auth Introducing Seven New Email Templates for Supabase Auth The new Supabase power for Kiro Introducing Supabase ETL Introducing Analytics Buckets Introducing Vector Buckets Snap, Inc. Launches Snap Cloud, Powered by Supabase Triplit joins Supabase Supabase Series E 1000 Y Combinator Founders Choose Supabase gm 👋 web3, welcome aboard to Sign in with Web3 (Solana, Ethereum) Announcing the Supabase Remote MCP Server Enterprise speed, enterprise standards with Bolt Cloud + Supabase PostgREST 13 Lovable Cloud + Supabase: The Default Platform for AI Builders Processing large jobs with Edge Functions, Cron, and Queues Defense in Depth for MCP Servers OrioleDB Patent: now freely available to the Postgres community Supabase Launch Week 15 Hackathon Winner Announcement The Vibe Coder's Guide to Supabase Environments Testing for Vibe Coders: From Zero to Production Confidence The Vibe Coding Master Checklist Vibe Coding: Best Practices for Prompting Supabase Auth: Build vs. Buy Top 10 Launches of Launch Week 15 Supabase Launch Week 15 Hackathon Storage: 10x Larger Uploads, 3x Cheaper Cached Egress, and 2x Egress Quota Persistent Storage and 97% Faster Cold Starts for Edge Functions Algolia Connector for Supabase New Observability Features in Supabase Improved Security Controls and A New Home for Security Introducing Branching 2.0 Stripe-To-Postgres Sync Engine as standalone Library Supabase Analytics Buckets with Iceberg Support Create a Supabase backend using Figma Make Introducing JWT Signing Keys Supabase UI: Platform Kit Build a Personalized AI Assistant with Postgres Announcing Multigres: Vitess for Postgres Building on open table formats Open Data Standards: Postgres, OTel, and Iceberg Simplifying back-end complexity with Supabase Data APIs PostgreSQL Event Triggers without superuser access Top 10 Launches of Launch Week 14 Supabase MCP Server Data API Routes to Nearest Read Replica Declarative Schemas for Simpler Database Management Realtime: Broadcast from Database Keeping Tabs on What's New in Supabase Studio Edge Functions: Deploy from the Dashboard + Deno 2.1 Automatic Embeddings in Postgres Introducing the Supabase UI Library Supabase Auth: Bring Your Own Clerk Postgres Language Server: Initial Release Migrating from Fauna to Supabase Migrating from the MongoDB Data API to Supabase Dedicated Poolers Postgres as a Graph Database: (Ab)using pgRouting AI Hackathon at Y Combinator Calendars in Postgres using Foreign Data Wrappers Supabase Launch Week 13 Hackathon Winners How to Hack the Base! Running Durable Workflows in Postgres using DBOS database.build v2: Bring-your-own-LLM Restore to a New Project Hack the Base! with Supabase Top 10 Launches of Launch Week 13 Supabase Queues High Performance Disk Supabase Cron Supabase CLI v2: Config as Code Supabase Edge Functions: Introducing Background Tasks, Ephemeral Storage, and WebSockets Supabase AI Assistant v2 OrioleDB Public Alpha Executing Dynamic JavaScript Code on Supabase with Edge Functions ClickHouse Partnership, improved Postgres Replication, and Disk Management
Building offline-first mobile apps with Supabase, Flutter and Brick
Tim Shedor, Tyler Shukert · 2024-10-08 · via Supabase Blog

Building offline-first mobile apps with Supabase, Flutter and Brick

Brick is an all-in-one data manager for Flutter that handles querying and uploading between Supabase and local caches like SQLite. Using Brick, developers can focus on implementing the application without worrying about translating or storing their data.

Most significantly, Brick focuses on offline-first data parity: an app should function the same with or without connectivity.

Why Offline?#

The worst version of your app is always the unusable one. People use their phones on subways, airplanes, and on sub-3G connections. Building for offline-first provides the best user experience when you can’t guarantee steady bandwidth.

Even if you’re online-only, Brick’s round trip time is drastically shorter because all data from Supabase is stored in a local cache. When you query the same data again, your app retrieves the local copy, reducing the time and expense of a round trip. And, if SQLite isn’t performant enough, Brick also offers a third cache in memory. When requests are made while the app is offline, they’ll be continually retried until the app comes online, ensuring that your local state syncs up to your remote state.

Of course, you can always opt-out of the cache on a request-by-request basis for sensitive or must-be-fresh data.

Getting Started#

Create a Flutter app:

Add the Brick dependencies to your pubspec.yaml:


_10

dependencies:

_10

brick_offline_first_with_supabase: ^1.0.0

_10

sqflite: ^2.3.0

_10

brick_sqlite: ^3.1.0

_10

uuid: ^3.0.4

_10

_10

dev_dependencies:

_10

brick_offline_first_with_supabase_build: ^1.0.0

_10

build_runner: ^2.4.0


Set up directories for Brick’s generated code:


_10

mkdir -p lib/brick/adapters lib/brick/db;


Brick synthesizes your remote data to your local data through code generation. From a Supabase table, create Dart fields that match the table’s columns:


_26

// Your model definition can live anywhere in lib/**/* as long as it has the .model.dart suffix

_26

// Assume this file is saved at my_app/lib/src/users/user.model.dart

_26

_26

import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';

_26

import 'package:brick_sqlite/brick_sqlite.dart';

_26

import 'package:brick_supabase/brick_supabase.dart';

_26

import 'package:uuid/uuid.dart';

_26

_26

@ConnectOfflineFirstWithSupabase(

_26

supabaseConfig: SupabaseSerializable(tableName: 'users'),

_26

)

_26

class User extends OfflineFirstWithSupabaseModel {

_26

final String name;

_26

_26

// Be sure to specify an index that **is not** auto incremented in your table.

_26

// An offline-first strategy requires distributed clients to create

_26

// indexes without fear of collision.

_26

@Supabase(unique: true)

_26

@Sqlite(index: true, unique: true)

_26

final String id;

_26

_26

User({

_26

String? id,

_26

required this.name,

_26

}) : this.id = id ?? const Uuid().v4();

_26

}


When some (or all) of your models have been defined, generate the code:


_10

dart run build_runner build


This will generate adapters to serialize/deserialize to and from Supabase. Migrations for SQLite are also generated for any new, dropped, or changed columns. Check these migrations after they are generated - Brick is smart, but not as smart as you.

The Repository#

Your application does not need to touch SQLite or Supabase directly. By interacting with this single entrypoint, Brick makes the hard choices under the hood about where to fetch and when to cache while the application code remains consistent in online or offline modes.

Finally, run your app:


_54

// Saved in my_app/lib/src/brick/repository.dart

_54

import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';

_54

import 'package:brick_sqlite/brick_sqlite.dart';

_54

// This hide is for Brick's @Supabase annotation; in most cases,

_54

// supabase_flutter **will not** be imported in application code.

_54

import 'package:brick_supabase/brick_supabase.dart' hide Supabase;

_54

import 'package:sqflite_common/sqlite_api.dart';

_54

import 'package:supabase_flutter/supabase_flutter.dart';

_54

_54

import 'brick.g.dart';

_54

_54

class Repository extends OfflineFirstWithSupabaseRepository {

_54

static late Repository? _instance;

_54

_54

Repository._({

_54

required super.supabaseProvider,

_54

required super.sqliteProvider,

_54

required super.migrations,

_54

required super.offlineRequestQueue,

_54

super.memoryCacheProvider,

_54

});

_54

_54

factory Repository() => _instance!;

_54

_54

static Future<void> configure(DatabaseFactory databaseFactory) async {

_54

final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(

_54

databaseFactory: databaseFactory,

_54

);

_54

_54

await Supabase.initialize(

_54

url: supabaseUrl,

_54

anonKey: supabaseAnonKey,

_54

httpClient: client,

_54

);

_54

_54

final provider = SupabaseProvider(

_54

Supabase.instance.client,

_54

modelDictionary: supabaseModelDictionary,

_54

);

_54

_54

_instance = Repository._(

_54

supabaseProvider: provider,

_54

sqliteProvider: SqliteProvider(

_54

'my_repository.sqlite',

_54

databaseFactory: databaseFactory,

_54

modelDictionary: sqliteModelDictionary,

_54

),

_54

migrations: migrations,

_54

offlineRequestQueue: queue,

_54

// Specify class types that should be cached in memory

_54

memoryCacheProvider: MemoryCacheProvider(),

_54

);

_54

}

_54

}



_11

import 'package:my_app/brick/repository.dart';

_11

import 'package:sqflite/sqflite.dart' show databaseFactory;

_11

_11

Future<void> main() async {

_11

await Repository.configure(databaseFactory);

_11

// .initialize() does not need to be invoked within main()

_11

// It can be invoked from within a state manager or within

_11

// an initState()

_11

await Repository().initialize();

_11

runApp(MyApp());

_11

}


The fun part. Brick’s DSL queries are written once and transformed for local and remote integration. For example, to retrieve all users with the name “Thomas”:


_10

await Repository().get<User>(query: Query.where('name', 'Thomas'));


Or query by association:


_10

// Assuming we had a model `Order` with a `user` association

_10

await Repository().get<Order>(query: Query.where('user', Where.exact('name', 'Thomas'));


Queries can be much more advanced, leveraging contains, not, like operators as well as sub clauses. Please note that, as of writing, not all Supabase operators are supported.

Reactivity

Beyond async requests, you can subscribe to a stream of updated local data from anywhere in your app (for example, if you pull-to-refresh a list of users, all listeners will be notified of the new data):


_10

final Stream<List<User>> usersStream = Repository().subscribe<User>(query: Query.where('name', 'Thomas'));


This does not leverage Supabase’s channels by default; if Supabase updates, your app will not be notified. This opt-in feature is currently under active development.

Upserting

After a model has been created, it can uploaded to Supabase without serializing it to JSON first:


_10

await Repository().upsert<User>(User(name: 'Thomas'));


All attached associations will be upserted too.

Foreign Keys/Associations#

Easily connect related models/tables:


_25

import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';

_25

import 'package:brick_sqlite/brick_sqlite.dart';

_25

import 'package:brick_supabase/brick_supabase.dart';

_25

import 'package:my_app/lib/src/users/user.model.dart';

_25

import 'package:uuid/uuid.dart';

_25

_25

@ConnectOfflineFirstWithSupabase(

_25

supabaseConfig: SupabaseSerializable(tableName: 'orders'),

_25

)

_25

class Order extends OfflineFirstWithSupabaseModel {

_25

// Like Supabase's client, specifying a foreign_key

_25

// is possible but only necessary if there are joins

_25

// with multiple foreign keys

_25

// @Supabase(foreignKey: 'user_id')

_25

final User user;

_25

_25

@Supabase(unique: true)

_25

@Sqlite(index: true, unique: true)

_25

final String id;

_25

_25

Order({

_25

String? id,

_25

required this.user,

_25

}) : this.id = id ?? const Uuid().v4();

_25

}


Brick allows very granular model configuration - you can specify specific tables, individual columns, and more.

Testing#

Quickly mock your Supabase endpoints to add uncluttered unit testing:


_33

import 'package:brick_supabase/testing.dart';

_33

import 'package:test/test.dart'

_33

_33

void main() {

_33

// Pass an instance of your model dictionary to the mock server.

_33

// This permits quick generation of fields and generated responses

_33

final mock = SupabaseMockServer(modelDictionary: supabaseModelDictionary);

_33

_33

group('MyClass', () {

_33

setUp(mock.setUp);

_33

_33

tearDown(mock.tearDown);

_33

_33

test('#myMethod', () async {

_33

// If your request won't exactly match the columns of MyModel, provide

_33

// the query list to the `fields:` parameter

_33

final req = SupabaseRequest<MyModel>();

_33

final resp = SupabaseResponse([

_33

// mock.serialize converts models to expected Supabase payloads

_33

// but you don't need to use it - any jsonEncode-able object

_33

// can be passed to SupabaseRepsonse

_33

await mock.serialize(MyModel(name: 'Demo 1', id: '1')),

_33

await mock.serialize(MyModel(name: 'Demo 2', id: '2')),

_33

]);

_33

// This method stubs the server based on the described requests

_33

// and their matched responses

_33

mock.handle({req: resp});

_33

final provider = SupabaseProvider(mock.client, modelDictionary: supabaseModelDictionary);

_33

final retrieved = await provider.get<MyModel>();

_33

expect(retrieved, hasLength(2));

_33

});

_33

});

_33

}


Brick manages a lot. It can be overwhelming at times. But it’s been used in production across thousands of devices for more than five years, so it’s got a sturdy CV. There’s likely an existing solution to a seemingly novel problem. Please reach out to the community or package maintainers with any questions.