Skip to Content
Other Tools & AdaptersDatabase AdaptersSupabase

Supabase Postgres Adapter

The tracelet_supabase package provides a drop-in adapter to sync your background locations directly into Supabase Postgres, via either a Postgres RPC function or a Supabase Edge Function.

Installation

Add the adapter to your pubspec.yaml:

flutter pub add tracelet_supabase

This adapter uses supabase_flutter. You must initialize the Supabase client before using it.

How it Works

The adapter plugs into Tracelet’s battery-efficient native HTTP sync engine: buildHttpConfig produces an HttpConfig pointing at your Supabase REST endpoint — either /rest/v1/rpc/<function> (RPC) or /functions/v1/<function> (Edge Function) — with the apikey and Authorization: Bearer <token> headers set for you.

Because syncing runs in the native engine, batches upload with exponential backoff and offline queuing even when the app is force-quit. On a 401, configureTokenRefresh refreshes the Supabase session — in the foreground and in a headless background isolate — and retries. You provide exactly one target: rpcFunction or edgeFunction.

Setup & Configuration

Create a table and an ingest function

Create a table to receive the locations, enable RLS, and add a Postgres function that inserts an incoming batch. Tracelet posts a batch of location objects, so the function accepts a jsonb array:

-- Table CREATE TABLE public.locations ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, user_id uuid REFERENCES auth.users NOT NULL DEFAULT auth.uid(), latitude double precision NOT NULL, longitude double precision NOT NULL, accuracy double precision, speed double precision, timestamp timestamptz NOT NULL, is_moving boolean, created_at timestamptz DEFAULT now() ); ALTER TABLE public.locations ENABLE ROW LEVEL SECURITY; CREATE POLICY "Users insert their own locations" ON public.locations FOR INSERT WITH CHECK (auth.uid() = user_id); -- RPC function: receives the batch as a jsonb array and inserts each row. -- (The exact field names come from Tracelet's sync payload — see the -- Tracelet Sync docs for the payload schema.) CREATE OR REPLACE FUNCTION public.insert_tracelet_locations(payload jsonb) RETURNS void LANGUAGE sql SECURITY INVOKER AS $$ INSERT INTO public.locations (latitude, longitude, accuracy, speed, timestamp, is_moving) SELECT (e->'coords'->>'latitude')::double precision, (e->'coords'->>'longitude')::double precision, (e->'coords'->>'accuracy')::double precision, (e->'coords'->>'speed')::double precision, (e->>'timestamp')::timestamptz, (e->>'is_moving')::boolean FROM jsonb_array_elements(payload) AS e; $$;

Prefer more custom processing (validation, fan-out, enrichment)? Deploy a Supabase Edge Function instead and pass its name as edgeFunction — the adapter will POST the batch to /functions/v1/<name>.

Initialize Supabase

Initialize the Supabase client in your main.dart:

import 'package:supabase_flutter/supabase_flutter.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Supabase.initialize( url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key', ); runApp(const MyApp()); }

Configure token refresh & build the HTTP config

Call configureTokenRefresh() once, build the HttpConfig targeting your RPC (or Edge Function), and pass it to Tracelet.ready:

import 'package:tracelet_supabase/tracelet_supabase.dart'; import 'package:tracelet/tracelet.dart'; const supabaseUrl = 'https://xyzcompany.supabase.co'; const supabaseAnonKey = 'public-anon-key'; // 1. Keep the Supabase session token fresh in the background (resolves 401s). await TraceletSupabase.configureTokenRefresh(anonKey: supabaseAnonKey); // 2. Build the native HTTP config. Provide EITHER rpcFunction OR edgeFunction. final httpConfig = TraceletSupabase.buildHttpConfig( supabaseUrl: supabaseUrl, anonKey: supabaseAnonKey, rpcFunction: 'insert_tracelet_locations', // ...or: edgeFunction: 'tracelet-ingest' // autoSync: true, batchSync: true, maxBatchSize: 250 are the defaults ); // 3. Start Tracelet with the adapter's HTTP config. await Tracelet.ready(Config( geo: const GeoConfig(distanceFilter: 50), http: httpConfig, )); await Tracelet.start();

Attaching custom metadata

To tag every location with business data (e.g. a driver_id or trip_id), use setRouteContext() — it travels with each location through the SQLite queue and is included in the payload your function receives:

await Tracelet.setRouteContext(RouteContext( taskId: 'order_123', custom: {'trip_id': 'trip_456'}, ));

Performance

Syncing runs through Tracelet’s batch engine, so a backlog of offline points is flushed in batches (default maxBatchSize: 250) rather than one request per point. Combined with Postgres’ bulk insert inside your RPC, this keeps high-frequency tracking (e.g. a point every 10 m) efficient on both device battery and database round-trips.