tracelet_supabase 3.1.13 copy "tracelet_supabase: ^3.1.13" to clipboard
tracelet_supabase: ^3.1.13 copied to clipboard

Supabase adapter for Tracelet. Automatically configures Tracelet's native HTTP sync to push locations to Supabase and manages background auth token refresh.

Tracelet Supabase Adapter #

An official adapter for the Tracelet background geolocation package that provides a zero-configuration, battery-efficient integration with Supabase.

Why use this adapter? #

Tracelet is incredibly battery-efficient because its HTTP Sync Engine is written natively in Kotlin and Swift. Waking up the Flutter engine in the background to sync data using the supabase_flutter SDK drains the device's battery very quickly.

This adapter configures Tracelet's native HTTP engine to push background locations directly to Supabase REST endpoints, completely bypassing the Dart isolate. It also automatically handles Supabase's 1-hour JWT expiration by injecting fresh tokens into the native engine.

Installation #

Add both packages to your pubspec.yaml:

dependencies:
  tracelet: any
  supabase_flutter: any
  tracelet_supabase: any

Quick Start #

Initialize supabase_flutter as usual, then configure Tracelet to point to your Supabase table:

import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tracelet/tracelet.dart';
import 'package:tracelet_supabase/tracelet_supabase.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. Initialize Supabase
  await Supabase.initialize(
    url: 'https://xyz.supabase.co',
    anonKey: 'YOUR_ANON_KEY',
  );

  // 2. Configure Token Refresh (Crucial for background tracking!)
  await TraceletSupabase.configureTokenRefresh(anonKey: 'YOUR_ANON_KEY');

  // 3. Build the Native HTTP Config
  final httpConfig = TraceletSupabase.buildHttpConfig(
    supabaseUrl: 'https://xyz.supabase.co',
    anonKey: 'YOUR_ANON_KEY',
    tableName: 'locations', 
  );

  // 4. Initialize Tracelet
  await Tracelet.ready(Config(
    distanceFilter: 50,
    http: httpConfig,
  ));

  runApp(MyApp());
}

When you use rpcFunction: 'insert_tracelet_locations', Tracelet's native HTTP engine pushes data directly to Supabase's PostgREST API using a Postgres Function (RPC). This is required because Tracelet natively wraps the location array in a JSON object (e.g. {"location": [...]}), which a standard table endpoint cannot accept.

You must create the locations table and the RPC function in Supabase exactly matching Tracelet's payload. Run this in your Supabase SQL Editor:

-- Create the locations table
CREATE TABLE public.locations (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    user_id UUID REFERENCES auth.users(id) DEFAULT auth.uid(),
    
    -- Core Tracelet Fields
    uuid UUID NOT NULL UNIQUE,
    timestamp TIMESTAMPTZ NOT NULL,
    is_moving BOOLEAN NOT NULL,
    odometer DOUBLE PRECISION NOT NULL,
    event TEXT,
    
    -- Source and Integrity (Native sync engine omits these fields, so they must have defaults or be nullable)
    "locationSource" TEXT DEFAULT 'unknown',
    "reducedAccuracy" BOOLEAN DEFAULT false,
    is_mock BOOLEAN DEFAULT false,
    "mockHeuristics" JSONB,
    
    -- Nested Objects (stored as JSONB for flexibility)
    coords JSONB NOT NULL,
    activity JSONB,
    battery JSONB,
    extras JSONB,
    
    -- Enterprise Audit Fields
    audit_hash TEXT,
    audit_previous_hash TEXT,
    audit_chain_index INTEGER,
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Index for performance
CREATE INDEX locations_user_id_idx ON public.locations(user_id);
CREATE INDEX locations_timestamp_idx ON public.locations(timestamp DESC);

-- Enable RLS
ALTER TABLE public.locations ENABLE ROW LEVEL SECURITY;

-- Policy to allow users to read their own data
CREATE POLICY "Users can read their own locations" 
    ON public.locations FOR SELECT TO authenticated 
    USING ( (SELECT auth.uid()) = user_id );

-- Create the RPC function that PostgREST uses to unpack the JSON wrapper
CREATE OR REPLACE FUNCTION public.insert_tracelet_locations(location JSONB)
RETURNS void
LANGUAGE plpgsql
SECURITY INVOKER
AS $$
BEGIN
  INSERT INTO public.locations (
    uuid, timestamp, is_moving, odometer, event,
    coords, activity, battery, extras, 
    audit_hash, audit_previous_hash, audit_chain_index
  )
  SELECT
    (loc->>'uuid')::UUID,
    (loc->>'timestamp')::TIMESTAMPTZ,
    (loc->>'is_moving')::BOOLEAN,
    (loc->>'odometer')::DOUBLE PRECISION,
    loc->>'event',
    loc->'coords',
    loc->'activity',
    loc->'battery',
    loc->'extras',
    loc->>'audit_hash',
    loc->>'audit_previous_hash',
    (loc->>'audit_chain_index')::INTEGER
  FROM jsonb_array_elements(location) AS loc
  ON CONFLICT (uuid) DO NOTHING;
END;
$$;

Customizing the Payload #

If you want to inject custom data (like an order_id or trip_id) into every location point dynamically, you can use the Tracelet.setRouteContext() API in Dart. For example:

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

The route context data travels with the location row through the sync queue. Do not reshape the core JSON manually in Dart, as this drains the battery.

2. Edge Function Integration #

If your database uses custom column names (e.g., flat lat and lng instead of a coords JSON object), or if you need to perform server-side validation (like Geocoding or filtering out bad GPS points) before inserting into Postgres, point Tracelet to an Edge Function:

final httpConfig = TraceletSupabase.buildHttpConfig(
  supabaseUrl: 'https://xyz.supabase.co',
  anonKey: 'YOUR_ANON_KEY',
  edgeFunction: 'tracelet-ingest', // Use this instead of tableName
);

Your Deno Edge Function will receive the batch of locations and can transform or filter them before using the Supabase Server Client to insert them:

// supabase/functions/tracelet-ingest/index.ts
import { withSupabase } from 'npm:@supabase/server'

export default {
  // Use auth: 'user' to ensure the caller has a valid JWT.
  // ctx.supabase is automatically RLS-scoped to the authenticated user!
  fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
    try {
      // 1. Extract Tracelet payload (wrapped in a "location" object by the native engine)
      const body = await req.json()
      const locations = body.location || []
      
      // 2. You can filter or transform the points here if needed.
      // For this example, we just reject highly inaccurate points.
      const validPoints = locations.filter((loc: any) => loc.coords.accuracy <= 50)

      if (validPoints.length === 0) {
        return Response.json({ message: 'No valid points' }, { status: 200 })
      }

      // 3. Insert into the locations table using the RLS-scoped client
      const { error } = await ctx.supabase
        .from('locations')
        .insert(validPoints)

      if (error) throw error

      return Response.json({ success: true }, { status: 200 })
    } catch (error: any) {
      return Response.json({ error: error.message }, { status: 400 })
    }
  })
}

Important Note on Headless Execution #

When your app is fully terminated and the Supabase token expires, Tracelet will boot a headless Dart isolate to fetch a new token. You must ensure Supabase.initialize() is called within the headless scope, otherwise the token refresh will fail.

2
likes
0
points
2.9k
downloads

Publisher

verified publisherikolvi.com

Weekly Downloads

Supabase adapter for Tracelet. Automatically configures Tracelet's native HTTP sync to push locations to Supabase and manages background auth token refresh.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, supabase_flutter, tracelet

More

Packages that depend on tracelet_supabase