effidevFlutter · Edge de Cloudflare · Optimización de costes en la nube
Español

Flutter Supabase Offline-First con Drift y PowerSync

Arquitectura Offline-First para Flutter y Supabase con Drift y PowerSync

Si una aplicación móvil se detiene mostrando únicamente un indicador de carga de “Error de red” en el metro, en un ascensor o en zonas sin cobertura, el usuario la desinstalará de inmediato. La estructura convencional de ‘solicitud de red ➔ lectura de caché al fallar’ no puede evitar los frecuentes indicadores de carga ni la exposición de datos obsoletos (Stale State).

En 2026, el estándar mundial en el desarrollo de aplicaciones móviles es la arquitectura Offline-First (o Local-First). En una aplicación offline-first, la base de datos SQLite local dentro del navegador o dispositivo se convierte en la única fuente de verdad (Single Source of Truth), y la UI responde de manera instantánea sin necesidad de conocer el estado de la red.

Este artículo explica la implementación completa de una arquitectura offline-first empresarial en un entorno de Flutter y Supabase (PostgreSQL), combinando Drift SQLite, el motor de sincronización PowerSync y Riverpod 3.0 para garantizar al 100% la capacidad de respuesta offline y la sincronización en tiempo real con el servidor.

Resumen clave

  • Única fuente de verdad (Single Source of Truth): La UI no llama directamente a la API de Supabase, sino que solo se suscribe a la base de datos local Drift SQLite. Garantiza una respuesta de 0 ms ante cualquier cambio de datos.
  • Rol del motor PowerSync: Automatiza por completo la sincronización parcial (Partial Sync), resolución de conflictos (Conflict Resolution) y buzón de salida asíncrono (Outbox) entre SQLite local y Supabase Postgres.
  • Integración con Riverpod 3.0: Al suscribirse al flujo enlazado de SQLite local a través de streamProvider, la UI se actualiza instantáneamente incluso en estado offline.
  • Transparencia de red: Aunque la conexión Wi-Fi se corte en el metro u otros lugares, el usuario puede continuar escribiendo o modificando datos, y al reconectarse se sincronizan automáticamente con el backend en segundo plano.
  • Superación de arquitecturas obsoletas (como Brick): En lugar de un simple almacenamiento en caché HTTP o librerías wrapper excesivas, la combinación estándar de 2026 de Drift + PowerSync evita al 100% el jank de renderizado y la pérdida de datos.

1. El paradigma Offline-First: Caching de red vs. Local-First

Esta es la diferencia fundamental entre el enfoque tradicional de caché y la arquitectura offline-first de 2026.

[Enfoque tradicional de caché de red] ❌
UI ──► Network Fetch (indicador 300 ms) ──(al fallar)──► Lectura de caché local (datos obsoletos)

[Arquitectura Offline-First de 2026] ⭕️
UI ──(Lectura/Escritura inmediata 0 ms)──► [Drift Local SQLite (Single Source of Truth)]

                                                     │ (Sincronización en segundo plano 0 ms ~ al conectar red)

                                             [PowerSync Engine]

                                                     │ (Real-time Sync)

                                            [Supabase PostgreSQL]

En una estructura offline-first, no existe restricción alguna en el funcionamiento de la aplicación incluso cuando se interrumpe la conexión de red. Todas las operaciones de lectura y escritura de datos se ejecutan inmediatamente en el SQLite de Drift dentro del dispositivo, permitiendo que la UI responda en 0 ms. El motor PowerSync registra los cambios en una cola de buzón de salida (Outbox Queue) en segundo plano y los sincroniza con Supabase tan pronto como se restablece la red.

2. Capas de la arquitectura Drift SQLite + PowerSync + Riverpod 3.0

Capa Rol y componentes Características principales
UI & State Layer Flutter UI + Riverpod 3.0 (NotifierProvider) Se suscribe únicamente al Stream de la BD Drift local, independientemente del estado de la red
Local Persistence Drift (SQLite ORM) Única fuente de verdad. Base de datos SQLite para Dart con alta seguridad de tipos
Sync Engine PowerSync Client Sincronización diferencial en tiempo real y resolución de conflictos entre SQLite local ↔ Supabase Postgres
Remote Database Supabase (PostgreSQL) Almacenamiento de datos multitenant basado en RLS (Row Level Security)

3. Código de implementación práctica Offline-First

Paso 1: Definición de tabla Drift SQLite (lib/database/app_database.dart)

Definición de base de datos local con seguridad de tipos conforme a la documentación oficial de Drift.

import 'package:drift/drift.dart';
import 'package:drift/native.dart';

part 'app_database.g.dart';

class Todos extends Table {
  TextColumn get id => text()();
  TextColumn get title => text()();
  BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
  DateTimeColumn get updatedAt => dateTime()();

  @override
  Set<Column> get primaryKey => {id};
}

@DriftDatabase(tables: [Todos])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(NativeDatabase.memory());

  @override
  int get schemaVersion => 1;

  // Expone los cambios de la BD local como un stream de 0 ms
  Stream<List<Todo>> watchAllTodos() {
    return (select(todos)..orderBy([(t) => OrderingTerm.desc(t.updatedAt)])).watch();
  }
}

Paso 2: Configuración del esquema offline y resolución de conflictos en PowerSync (lib/sync/powersync.dart)

Configuración del conector de sincronización en segundo plano offline con Supabase a través de la documentación oficial de PowerSync.

import 'package:powersync/powersync.dart';

final schema = Schema([
  Table('todos', [
    Column.text('title'),
    Column.integer('is_completed'),
    Column.text('updated_at'),
  ])
]);

late final PowerSyncDatabase db;

Future<void> initPowerSync(String supabaseUrl, String anonKey) async {
  db = PowerSyncDatabase(schema: schema, path: 'app_powersync.db');
  await db.initialize();

  // Conectar conector de Supabase (ejecuta sincronización en segundo plano al haber red)
  final connector = SupabaseConnector(url: supabaseUrl, anonKey: anonKey);
  await db.connect(connector: connector);
}

Paso 3: Suscripción UI reactiva offline con Riverpod 3.0 (lib/providers/todo_provider.dart)

Combinando el patrón reactivo de Stream con AsyncNotifier visto en la guía de Riverpod 3.0, se configura la UI para responder de inmediato sin importar si hay conexión de red.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../database/app_database.dart';

final databaseProvider = Provider<AppDatabase>((ref) => AppDatabase());

// StreamProvider de Riverpod 3.0 que se suscribe en tiempo real al Stream de la BD Drift local
final todoListProvider = StreamProvider.autoDispose<List<Todo>>((ref) {
  final db = ref.watch(databaseProvider);
  return db.watchAllTodos();
});

// Al agregar un Todo desde la UI: Insert inmediato en la BD local sin solicitud de red
class TodoNotifier extends AutoDisposeAsyncNotifier<void> {
  @override
  Future<void> build() async {}

  Future<void> addTodo(String title) async {
    final db = ref.read(databaseProvider);
    await db.into(db.todos).insert(
      TodosCompanion.insert(
        id: DateTime.now().millisecondsSinceEpoch.toString(),
        title: title,
        updatedAt: DateTime.now(),
      ),
    );
  }
}

4. Patrones de prueba prácticos para desconexión y reconexión de red

Para validar la solidez de una aplicación offline-first, es indispensable realizar una prueba de manipulación de red (Network Manipulation Test).

[Escenario de prueba]
1. Activar modo avión (offline) ➔ Crear 5 notas nuevas en la app ➔ Verificar reflejo inmediato en pantalla en 0 ms ⭕️
2. Cerrar y reiniciar la app (estado offline) ➔ Confirmar 100% de validez local de los datos creados ⭕️
3. Reconectar Wi-Fi ➔ PowerSync ejecuta Sync automático en segundo plano hacia Supabase Postgres ⭕️
4. Panel de Supabase ➔ Confirmar envío completo con políticas RLS aplicadas y sin conflictos de datos ⭕️

Al aplicar esta arquitectura, se evita por completo el jank de renderizado en el hilo de UI destacado en la Guía práctica de profiling de CPU y fugas de memoria en Flutter DevTools, manteniendo una tasa de cuadros fluida de 60 a 120 fps.

5. Guía de selección de librerías Offline-First para 2026

Librería Idoneidad Offline-First Evaluación y motivos de recomendación
PowerSync + Drift ★★★★★ (Recomendado) Mejor práctica de 2026. Armonía perfecta entre partial sync, resolución de conflictos y seguridad de tipos de Drift
Drift + Custom Outbox ★★★★☆ (Personalizada) Excelente cuando se crea una cola de Sync propia y conectores REST/GraphQL sin servicios de terceros
Caché Supabase Realtime ★★☆☆☆ (No recomendado) Escritura offline imposible. Especializado únicamente en suscripción en tiempo real con red activa
Brick ORM ★☆☆☆☆ (No recomendado) Desaconsejado para mantenimiento en 2026 debido a fallos de sincronización en eliminaciones del lado del servidor y boilerplate complejo

Preguntas frecuentes

¿Ocurren conflictos si varios dispositivos modifican los mismos datos estando offline?

El motor PowerSync resuelve automáticamente los conflictos en segundo plano mediante reglas de marca de tiempo LWW (Last-Write-Wins) o algoritmos CRDT (Conflict-free Replicated Data Type). En caso de requerir una lógica de negocio especial, es posible definir funciones trigger en Supabase Postgres o manejadores de conflicto personalizados en PowerSync para un control sencillo.

¿Qué hacer si la base de datos local SQLite crece demasiado en tamaño?

Al aprovechar las reglas de sincronización parcial (Partial Sync Rules) de PowerSync, los usuarios pueden descargar selectivamente al dispositivo local solo los datos que necesitan en el momento (por ejemplo, datos de los últimos 30 días o datos propios), minimizando el uso de memoria y espacio de almacenamiento del dispositivo.

¿Es segura la seguridad de datos offline en Supabase?

Al aplicar el motor de cifrado sqlcipher al archivo de base de datos local Drift SQLite, no es posible descifrar dicho archivo incluso en entornos de dispositivo con jailbreak o acceso root. Además, las políticas RLS (Row Level Security) del lado del servidor de Supabase realizan una segunda verificación al reconectarse, manteniendo una seguridad rigurosa.

¿El enfoque Offline-First funciona de la misma manera en la plataforma web (Flutter Web)?

Sí, funciona. En el entorno de Flutter Web, Drift se adapta automáticamente a un backend de Wasm + IndexedDB, creando una base de datos SQLite offline dentro del navegador. El rendimiento de renderizado Wasm en la web se analiza en detalle en la Guía de Flutter Web Wasm + Skwasm.