Flutter : Building Offline-First Apps with Supabase

Because real users don’t always have five-bar 5G… and your app shouldn’t taste bland when the internet disappears.

Dev Adnani
July 9, 2025
4 min read
FlutterSupabaseOffline-FirstTutorial

1. Why Care About Offline-First? 🌶️

Think of a food-delivery app that loses your order the moment the elevator hits a dead zone—annoying, right? Offline-first design records every action locally first and syncs when the network wakes up, giving your UX the resilience of well-slow-cooked biryani.

2. The Ingredient List 🍲

SpicePurpose
Flutter 3.22+The sauté pan where everything simmers
supabase_flutterAuth, real-time DB, REST interface
Brick 📦Local persistence + queued sync layer
Connectivity PlusDetects when to trigger sync
WorkmanagerBackground tasks on Android/iOS
Riverpod / BlocState-management masala—pick your flavour

3. Setting Up Supabase (10 min prep)

  1. Create a project at https://supabase.com → copy anon & service keys.

  2. Schema – example notes table

    SQL
    create table public.notes (
      id uuid primary key default uuid_generate_v4(),
      title text not null,
      body text,
      updated_at timestamp with time zone default now()
    );
    
  3. Row-level security

    SQL
    alter table public.notes enable row level security;
    create policy "Users can access own notes"
      on notes for all
      using ( auth.uid() = user_id );
    

4. Bootstrapping Flutter & Brick 🛠️

SHELL
flutter create offline_supabase_demo
cd offline_supabase_demo
flutter pub add supabase_flutter brick_offline_first sqflite connectivity_plus workmanager riverpod

4.1 Brick Repository

DART
import 'package:brick_offline_first/brick_offline_first.dart';

class Note extends OfflineFirstWithRestModel {
  @primaryKey
  final Uuid id;
  final String title;
  final String? body;
  @OfflineFirstSerdes()
  final DateTime updatedAt;

  Note({required this.id, required this.title, this.body, required this.updatedAt});
}

Run code‑gen:

SHELL
flutter pub run build_runner build --delete-conflicting-outputs

Brick now gives you:

DART
final repository = OfflineFirstWithRestRepository(
  restProvider: RestProvider(
    'https://<YOUR-PROJECT>.supabase.co/rest/v1',
    headers: {'apikey': supabaseAnonKey},
  ),
  offlineProvider: SqliteProvider.databaseBuilder('notes.db').build(),
);

5. The Sync Flow 🔄

MERMAID
graph LR
USER_ACTION[User edits note] -->|Repository.save()| LOCAL_DB
LOCAL_DB -->|enqueue| QUEUE[Brick Sync Queue]
QUEUE -->|onConnectivity| SUPABASE
SUPABASE -->|webhooks| LOCAL_DB
  1. Create/update ⇒ saved instantly in SQLite.
  2. Sync queue persists mutation list.
  3. Connectivity().onConnectivityChanged triggers repository.synchronize() automatically.
  4. Supabase “UPDATE” triggers real-time listeners → UI refresh.

6. Conflict Resolution (Sweet vs Spicy) 🌗

StrategyWhen to use
Last‑write‑wins (default)Personal data, low risk
Timestamp mergeCollaborative docs
Manual diff UIHigh‑stakes records (financial, medical)

Brick exposes hooks:

DART
repository.addBeforeSaveTrigger((existing, incoming) {
  if (incoming.updatedAt.isAfter(existing.updatedAt)) return incoming;
  return existing; // keep older change
});

7. Background Sync with Workmanager ⏰

DART
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    await Supabase.initialize(...);
    await repository.synchronize();
    return Future.value(true);
  });
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
  Workmanager().registerPeriodicTask('sync-task', 'offlineSync',
      frequency: const Duration(minutes: 15));
  runApp(const MyApp());
}

8. Taste‑Test: Simulating Offline 🧪

  • Android EmulatorCellular → No Connectivity.
  • iOS SimulatorFeatures → Network Link Conditioner.
  • Keep DevTools open; Brick logs queued mutations so you can verify they replay when connectivity returns.

9. Security & Cost Tips 💰

  1. Enable PostgREST rate limits on Supabase to avoid abuse.
  2. Use Supabase Edge Functions for heavy joins—ship only what mobile truly needs.
  3. Purge the sync queue after 90 days with a cron job to keep SQLite lean.

10. Conclusion — Serve Hot, Even Offline 🔥

With Supabase as the back‑of‑house kitchen and Brick as your local tiffin box, a Flutter app can stay flavour‑packed whether users are on a bullet train or stuck in an elevator. Clone the demo, sprinkle your own masala (animations, theming), and tweet your build—let’s make flaky connections a non‑issue.


Further Reading

  • Brick Docs – offline‑first patterns
  • Supabase Flutter Guide – auth & RLS
  • My FlutterAhmedabad Talk Replay – 25‑min crash course

Feeling hungry for more? Drop a comment below or ping me on the Contact page—happy to taste‑test your prototypes!

Dev Adnani

Software Engineer & Web3 Enthusiast