Skip to Content
Conceptos básicosSincronización de trazas

Tracelet Sync: la historia de la red

El seguimiento de la ubicación no significa nada si los datos nunca llegan a sus servidores. Los dispositivos móviles pierden la conexión constantemente: al entrar en ascensores, al conducir por valles o al cambiar de Wi-Fi a celular. Tracelet Sync es un motor de red sin conexión que tiene en cuenta la batería y está diseñado para garantizar la entrega de datos sin activar la interfaz de usuario.

Actualización de Tracelet 3.2.0: La lógica de sincronización HTTP se ha movido al módulo tracelet_sync. Debe incluir este módulo si requiere sincronización de red.


Cómo sincronizar con la red

Mientras Tracelet Core captura ubicaciones, tracelet_sync es el motor HTTP en segundo plano que las entrega a su servidor. Para habilitarlo, inicialice TraceletSync.ready() antes de comenzar a rastrear:

import 'package:tracelet_sync/tracelet_sync.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 1. Configure the Sync Engine await TraceletSync.ready(SyncConfig( url: 'https://your-api.com/locations', method: 'POST', autoSyncThreshold: 10, // Sync every 10 locations autoSyncDelay: 10000, // Wait 10s before pushing syncInterval: 0, // (Optional) Seconds between repeating queue flushes; 0 = off batchSync: true, // Send as a JSON array maxBatchSize: 250, // Up to 250 locations per request headers: { 'Authorization': 'Bearer YOUR_TOKEN' }, )); // 2. Configure and start Tracelet Core as usual await Tracelet.ready(Config.balanced()); await Tracelet.start(); }

Una vez configurado, el motor manejará todos los escenarios siguientes automáticamente.


Escenario 1: La caminata por la montaña

Conceptos explorados: Cola sin conexión, sincronización por lotes, eliminación de rebotes

El problema

Mark está usando tu aplicación de fitness para realizar un seguimiento de una caminata de 3 horas por las montañas. No tiene servicio celular. Si su aplicación intenta una POST HTTP cada vez que da un paso, fallará, gastará batería buscando una señal y perderá los puntos de ubicación para siempre. Cuando finalmente regrese a su coche, su ruta parecerá un mapa en blanco.

Cómo lo resuelve Tracelet Sync

  1. Persistencia de SQLite sin conexión (autoSyncThreshold) Como Mark no tiene señal, Tracelet inmediatamente deja de intentar enviar solicitudes HTTP. En cambio, cada ubicación se almacena de forma segura en la base de datos SQLite local. Configuramos autoSyncThreshold: 100, lo que significa que Tracelet ni siquiera intenta activar la radio de la red hasta que al menos 100 puntos estén en cola en la base de datos.

  2. Sincronización rebotada (autoSyncDelay) Cuando Mark finalmente baja la montaña y recupera 4G, de repente tiene 500 ubicaciones en cola. En lugar de enviar 500 solicitudes HTTP inmediatas (lo que congelaría su teléfono), autoSyncDelay: 10000 le dice a Tracelet que espere 10 segundos. Evita el rápido flujo de datos, permitiendo que la conexión se estabilice.

  3. Sincronización por lotes (batchSync y maxBatchSize) En lugar de 500 solicitudes POST individuales, batchSync: true y maxBatchSize: 250 agrupan las ubicaciones en dos matrices JSON masivas. Envía los primeros 250 puntos, espera a que su servidor devuelva un HTTP 200 OK, elimina esos puntos de SQLite y luego envía el siguiente lote.

  4. Sincronización basada en intervalos (syncInterval) autoSyncDelay reacciona a nuevas ubicaciones. Si también desea una descarga basada en el tiempo (cargando lo que esté en la cola con una cadencia fija, independientemente de cuántos puntos se hayan acumulado), configure syncInterval en la cantidad de segundos entre descargas (por ejemplo, syncInterval: 60 descarga la cola fuera de línea una vez por minuto). Se ejecuta junto con el rebote y está deshabilitado de forma predeterminada (0).


Escenario 2: Conexión al Wi-Fi de la cafetería

Conceptos explorados: Restricciones celulares, compresión delta

El problema

Mark termina su caminata y se dirige a un café. Viaja internacionalmente, por lo que su plan de datos móviles es extremadamente caro. Su aplicación ha puesto en cola megabytes de datos JSON de ubicación y enviarlos a través de su conexión 4G en roaming le costará dinero.

Cómo lo resuelve Tracelet Sync

  1. Restricción celular (disableAutoSyncOnCellular) Al configurar disableAutoSyncOnCellular: true, Tracelet bloquea completamente el motor de sincronización mientras Mark está en 4G. Las ubicaciones permanecen seguras en SQLite. En el momento en que se conecta al Wi-Fi de la cafetería, el sistema operativo activa a Tracelet y el motor de sincronización vacía automáticamente la cola.

  2. Compresión de codificación Delta (enableDeltaCompression) Incluso con Wi-Fi, el envío de conjuntos JSON masivos es lento. Tracelet aplica Compresión Delta antes de enviar el lote. Si Mark caminaba en línea recta, su latitud no cambiaba mucho. En lugar de enviar las coordenadas completas para cada punto, Tracelet envía el primer punto completo y luego solo la diferencia (el delta) para los puntos siguientes. Con deltaCoordinatePrecision: 5 (con una precisión de ~1,1 metros), esto reduce el tamaño de la carga útil HTTP entre un 60 % y un 80 %.

Carga útil estándar (sin compresión):

[ {"lat": 37.774900, "lng": -122.419400}, {"lat": 37.774910, "lng": -122.419410}, {"lat": 37.774920, "lng": -122.419420} ]

Carga útil codificada en Delta (lo que recibe su servidor):

[ {"lat": 37.774900, "lng": -122.419400}, {"dLat": 10, "dLng": 10}, {"dLat": 10, "dLng": 10} ]

Escenario 3: La sesión caducada

Conceptos explorados: Reintentos 401, devoluciones de llamada sin cabeza, retroceso exponencial

El problema

El token de autenticación (JWT) de Mark expiró mientras estaba de excursión. Cuando Tracelet finalmente intenta sincronizar el lote con su servidor, su API devuelve HTTP 401 Unauthorized. Un motor de sincronización ingenuo eliminaría los datos pensando que fallaron o se quedaría atrapado en un bucle infinito de 401, agotando la batería. El teléfono de Mark está en su bolsillo con la pantalla apagada; no puede iniciar sesión en este momento.

Cómo lo resuelve Tracelet Sync

  1. Devoluciones de llamadas de encabezado dinámicas Cuando Tracelet recibe el 401, debe buscar un nuevo token antes de volver a intentarlo. Registra devoluciones de llamada para manejar esto tanto en estado de primer plano como de fondo (sin cabeza).

Devolución de llamada en primer plano:

tl.Tracelet.setHeadersCallback(() async { final newJwt = await AuthAPI.refreshToken(); return {'Authorization': 'Bearer $newJwt'}; });

Devolución de llamada en segundo plano (sin cabeza): Esto se ejecuta en un motor Dart aislado sin activar la interfaz de usuario, lo que garantiza que la sincronización funcione incluso si el usuario fuerza el cierre de la aplicación.

@pragma('vm:entry-point') void headlessHeadersCallback(tl.HeadlessEvent event) async { final newJwt = await AuthAPI.refreshToken(); tl.Tracelet.setDynamicHeaders({'Authorization': 'Bearer $newJwt'}); } // Register it before runApp() tl.Tracelet.registerHeadlessHeadersCallback(headlessHeadersCallback);
  1. Retroceso exponencial (maxRetries y retryBackoffCap) ¿Qué pasa si su servidor de autenticación no funciona y devuelve un 503? Tracelet maneja esto con gracia. Intenta volver a intentarlo. Falla. Espera 1 segundo (retryBackoffBase), luego 2 segundos, luego 4 segundos. El retroceso exponencial tiene un límite de 60 segundos (retryBackoffCap). Después de 3 intentos (maxRetries), se da por vencido por completo, dejando los datos de forma segura en SQLite para volver a intentarlo mañana.

Escenario 4: el esquema de servidor personalizado

Conceptos explorados: Constructores de carrocerías de sincronización personalizada, mapeo de esquemas

El problema

La empresa de Mark tiene un backend heredado que espera datos de ubicación en un formato muy específico y no estándar. La carga útil JSON predeterminada de Tracelet no coincide con el esquema requerido de su servidor y no pueden cambiar la API de backend solo para esta aplicación.

Cómo lo resuelve Tracelet Sync

  1. Culturista de sincronización personalizada (setSyncBodyBuilder) En lugar de utilizar el contenedor JSON predeterminado, Tracelet le permite interceptar el lote de ubicaciones justo antes de que se envíen a la red, lo que le permite asignarlas a cualquier forma que desee su servidor.

A partir de Tracelet 3.2.8, las ubicaciones pasadas a este constructor utilizan un esquema anidado sólido (donde las coordenadas se agrupan de forma segura en coords y los datos de actividad en activity).

Tracelet.setSyncBodyBuilder((context) async { // Map Tracelet's nested schema to your legacy server's flat schema final mappedPoints = context.locations.map((loc) { final coords = loc['coords'] as Map; final activity = loc['activity'] as Map; return { 'lat': coords['latitude'], 'lng': coords['longitude'], 'time': loc['timestamp'], 'moving': loc['is_moving'], 'action': activity['type'], }; }).toList(); // Return the exact JSON structure your server expects return { 'device_id': myDeviceId, 'payload': mappedPoints, }; });
  1. Ejecución sin cabeza Al igual que las actualizaciones de tokens, esta construcción de carrocería personalizada también se puede ejecutar completamente sin cabeza en segundo plano a través de registerHeadlessSyncBodyBuilder(), lo que garantiza que su esquema personalizado se cree y envíe incluso cuando la aplicación esté completamente terminada.

Escenario 5: El conductor de reparto

Conceptos explorados: Contexto de ruta e inyección de lógica empresarial

El problema

Su backend recibe miles de coordenadas sin procesar. Pero una coordenada por sí sola no dice por qué el usuario estaba allí. ¿Estaba el conductor en una tarea de entrega? ¿Qué pedido estaban entregando? Necesita una forma de adjuntar la lógica empresarial directamente a la carga útil de la ubicación en segundo plano para poder consultarla fácilmente en su base de datos.

Cómo lo resuelve Tracelet Sync

  1. Configuración del contexto de la ruta Puede inyectar metadatos personalizados en Tracelet. Cada ubicación registrada después de llamar a setRouteContext() se etiquetará automáticamente de forma permanente con estos datos en la base de datos interna SQLite.

    await tl.Tracelet.setRouteContext( const tl.RouteContext( taskId: 'delivery-1234', driverId: 'john_doe', custom: {'shift_id': 'morning-shift-001'}, ), );
  2. La carga útil JSON resultante Cuando Tracelet se sincroniza con su backend, cada ubicación en la matriz incluirá el objeto context, lo que garantiza que su backend sepa exactamente a qué tarea pertenece esta ubicación.

    { "locations": [ { "coords": { "latitude": 37.7749, "longitude": -122.4194 }, "battery": { "level": 0.85, "isCharging": true }, "extras": { "your_custom_key": "your_value" }, "context": { "taskId": "delivery-1234", "driverId": "john_doe", "custom": { "shift_id": "morning-shift-001" } } } ] }

Seguimiento del estado de la batería

Tracelet está diseñado para entornos hostiles y fuera de línea donde los dispositivos pueden sincronizarse horas después de que se registró una ubicación. Para ayudarlo a comprender el estado del dispositivo en el campo, Tracelet captura automáticamente el estado exacto de la batería en el momento en que se registra cada ubicación.

Esto requiere configuración cero:

  • El motor captura el estado level (por ejemplo, 0,85 para 85%) y isCharging.
  • Este estado se guarda de forma segura en la base de datos SQLite fuera de línea junto con las coordenadas GPS.
  • Cuando el dispositivo vuelve a estar en línea, el motor de sincronización transmite el estado histórico exacto de la batería, no el estado actual de la batería.

Esto permite que su backend visualice con precisión el consumo de batería en una ruta o identifique si los conductores desconectan constantemente sus dispositivos durante los turnos.

  1. Borrar contexto Cuando el conductor termine su entrega, aclare el contexto. Las ubicaciones posteriores ya no se etiquetarán.

    await tl.Tracelet.clearRouteContext();

Incluso puedes consultar la base de datos SQLite interna según este contexto antes de sincronizar:

// Get all locations belonging to a specific delivery task final locations = await tl.Tracelet.getLocations( tl.SQLQuery(where: "context_task_id = 'delivery-1234'") );