flush method
Flush pending telemetry events
Sends all queued events to the backend immediately. Call this on app background/exit to ensure events are sent.
Implementation
Future<void> flush() async {
if (!_enabled || _eventQueue.isEmpty || _isFlushInProgress) {
return;
}
_isFlushInProgress = true;
try {
// Take current batch
final batch = List<TelemetryEvent>.from(_eventQueue);
_eventQueue.clear();
// Get telemetry endpoint based on environment
final endpoint = _getTelemetryEndpoint();
if (_environment == SDKEnvironment.development) {
// Supabase: Send events ONE AT A TIME to avoid "All object keys must match" error
// Each event can have different keys based on its properties
int successCount = 0;
for (final event in batch) {
try {
final payload = event.toSupabaseJson(
deviceId: _deviceId ?? 'unknown',
sdkVersion: SDKConstants.version,
platform: SDKConstants.platform,
);
// Debug: Log the payload being sent
_logger.debug('Sending telemetry event: ${event.type}');
_logger.debug('Payload: $payload');
final response = await HTTPService.shared.post<dynamic>(
endpoint,
payload,
requiresAuth: false,
);
// Debug: Log the response
_logger.debug('Response for ${event.type}: $response');
successCount++;
} catch (e) {
_logger.error('Failed to send event ${event.type}: $e');
}
}
_logger.debug('Flushed $successCount/${batch.length} events');
} else {
// Production/Staging: Group events by modality and send separate batches
// This matches the C++ telemetry manager which groups by modality
// V2 modalities: llm, stt, tts, model (get modality field at batch level)
// V1 modality: system/null (SDK lifecycle, storage, device, network events)
final v2Modalities = {'llm', 'stt', 'tts', 'model'};
final Map<String?, List<TelemetryEvent>> byModality = {};
// Group events by modality
for (final event in batch) {
final modality = event.category.value;
// V2 modalities get their modality name, V1 events get null
final key = v2Modalities.contains(modality) ? modality : null;
byModality.putIfAbsent(key, () => []).add(event);
}
// Send batches by modality (matching C++ telemetry_manager.cpp)
int successCount = 0;
for (final entry in byModality.entries) {
final modality = entry.key;
final modalityEvents = entry.value;
final payload = <String, dynamic>{
'events': modalityEvents.map((e) => e.toProductionJson()).toList(),
'device_id': _deviceId,
'timestamp': DateTime.now().toUtc().toIso8601String(),
};
// Include modality at batch level for V2 events
if (modality != null) {
payload['modality'] = modality;
}
try {
await HTTPService.shared.post<dynamic>(
endpoint,
payload,
requiresAuth: true,
);
successCount += modalityEvents.length;
_logger.debug(
'Flushed ${modalityEvents.length} ${modality ?? "system"} events');
} catch (e) {
_logger.error(
'Failed to flush ${modality ?? "system"} events: $e');
}
}
_logger.debug('Flushed $successCount/${batch.length} events total');
}
} catch (e) {
_logger.error('Failed to flush telemetry: $e');
// Events are already removed from queue, so they're lost on failure
// This is acceptable for telemetry to avoid memory buildup
} finally {
_isFlushInProgress = false;
}
}