SDK de Flutter - Privacidad de datos

Documento

Cumplir con las leyes de privacidad de datos

Implemente la recopilación de datos que cumpla con la privacidad notificando a Singular las opciones de consentimiento del usuario para GDPR, CCPA, COPPA y otras regulaciones de privacidad del consumidor.

Cuando los usuarios consientan o rechacen compartir su información con terceros, utilice los métodos de privacidad de Singular para comunicar su elección. Esto garantiza el cumplimiento de normativas como la Ley de Privacidad del Consumidor de California (CCPA) y permite a los socios respetar las preferencias de privacidad del usuario.

Más información: Consulte Privacidad del usuario y Limitar el uso compartido de datos para obtener información detallada sobre cómo Singular procesa el consentimiento de privacidad.


Limitar el uso compartido de datos

Control del intercambio de datos con terceros

Notifique a Singular si los usuarios han dado su consentimiento para compartir sus datos personales con socios terceros utilizando el método limitDataSharing().

Firma del método:

static void limitDataSharing(bool shouldLimitDataSharing)

Parámetros:

  • false: El usuario ha optado y consentido compartir sus datos
  • true: El usuario ha optado por no participar y no consiente compartir sus datos

Importante: Aunque es opcional, este método afecta a la compartición de datos de atribución. Algunos socios sólo comparten la información de atribución completa cuando se notifica explícitamente que los usuarios han optado por ella.

Para obtener la documentación completa del método, consulte la referencia limitDataSharing.


Ejemplos de uso

Implemente controles de compartición de datos basados en las preferencias de privacidad del usuario.

Dart
import 'package:singular_flutter_sdk/singular.dart';

// User has opted in to share their data
void onUserOptedInToDataSharing() {
  Singular.limitDataSharing(false);
  print('Data sharing enabled');
}

// User has opted out and declined to share their data
void onUserOptedOutOfDataSharing() {
  Singular.limitDataSharing(true);
  print('Data sharing limited');
}

// Set based on user preference
void handlePrivacyConsent(bool userConsented) {
  // Pass inverse: false = opted in, true = opted out
  Singular.limitDataSharing(!userConsented);

  print('Data sharing: ${userConsented ? 'Enabled' : 'Limited'}');
}

Cómo funciona:

Singular utiliza esta configuración en las devoluciones de privacidad del usuarioy la pasa a los socios que la requieren para el cumplimiento normativo.


Métodos de cumplimiento de GDPR

Gestione el consentimiento de seguimiento del usuario y controle la funcionalidad del SDK para cumplir con el GDPR (Reglamento General de Protección de Datos) y otras normativas de privacidad.

Gestión del consentimiento de seguimiento

TrackingOptIn

Registre el consentimiento explícito del usuario para el seguimiento enviando un evento GDPR opt-in a los servidores de Singular.

Firma del método:

static void trackingOptIn()

Cuándo Usar:

  • Cumplimiento GDPR: Llamada cuando los usuarios consienten explícitamente el seguimiento en regiones reguladas por GDPR.
  • Registro de consentimiento: Marca a los usuarios como que han dado su consentimiento GDPR en los sistemas de Singular
  • Comportamiento por defecto: Sin esta llamada, el SDK continúa el seguimiento pero no registra específicamente el consentimiento.

Para obtener la documentación completa del método, consulte la referencia trackingOptIn.


Ejemplo de implementación

Llame a trackingOptIn() después de que los usuarios acepten el consentimiento de seguimiento a través del cuadro de diálogo de consentimiento de su aplicación.

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:shared_preferences/shared_preferences.dart';

class GDPRManager extends StatefulWidget {
  @override
  _GDPRManagerState createState() => _GDPRManagerState();
}

class _GDPRManagerState extends State<GDPRManager> {
  bool? hasConsent;

  @override
  void initState() {
    super.initState();
    checkStoredConsent();
  }

  Future<void> checkStoredConsent() async {
    final prefs = await SharedPreferences.getInstance();
    final consent = prefs.getString('gdpr_consent');

    if (consent == null) {
      // Show consent dialog
      showGDPRConsentDialog();
    } else {
      setState(() {
        hasConsent = consent == 'true';
      });
    }
  }

  void showGDPRConsentDialog() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Text('Privacy Consent'),
        content: Text(
          'We would like your permission to track app usage and improve your experience.'
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
              setState(() {
                hasConsent = false;
              });
            },
            child: Text('Decline'),
          ),
          ElevatedButton(
            onPressed: () async {
              Navigator.of(context).pop();
              await onUserAcceptedTracking();
            },
            child: Text('Accept'),
          ),
        ],
      ),
    );
  }

  Future<void> onUserAcceptedTracking() async {
    // Record user consent
    Singular.trackingOptIn();
    print('User opted in to tracking');

    // Save preference
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('gdpr_consent', 'true');

    setState(() {
      hasConsent = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: hasConsent != null
            ? Text('Privacy Status: ${hasConsent! ? 'Opted In' : 'Declined'}')
            : CircularProgressIndicator(),
      ),
    );
  }
}

Métodos de control de seguimiento

StopAllTracking

Desactiva completamente todas las actividades de seguimiento del SDK para el usuario actual en este dispositivo.

Firma del método:

static void stopAllTracking()

Crítico Advertencia: Este método desactiva permanentemente el SDK hasta que se llame a resumeAllTracking(). El estado deshabilitado persiste durante los reinicios de la aplicación y sólo puede revertirse mediante programación.

Comportamiento:

  • Efecto inmediato: Detiene todo el seguimiento, la notificación de eventos y la recopilación de datos al instante.
  • Estado persistente: Permanece desactivado incluso después de cerrar y volver a abrir la aplicación.
  • Sin restablecimiento automático: Debe llamar explícitamente a resumeAllTracking() para volver a habilitarlo

Para obtener la documentación completa del método, consulte la referencia stopAllTracking.


Ejemplo de implementación

Detener el seguimiento cuando los usuarios deniegan el consentimiento o se excluyen a través de la configuración de privacidad.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:shared_preferences/shared_preferences.dart';

// User declined all tracking
Future<void> onUserDeclinedTracking() async {
  Singular.stopAllTracking();
  print('All tracking stopped');

  // Store preference
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('tracking_enabled', false);
}

// Handle user opt-out from settings menu
void handlePrivacySettingsChange(bool trackingEnabled) {
  if (!trackingEnabled) {
    Singular.stopAllTracking();
    print('Privacy settings: Tracking disabled');
  }
}

ReanudarTodoElSeguimiento

Volver a activar el seguimiento después de haberlo detenido con stopAllTracking().

Firma del método:

static void resumeAllTracking()

Casos de uso:

  • Cambio de consentimiento: El usuario cambia sus preferencias de privacidad y vuelve a activar el seguimiento
  • Ajustes de privacidad: El usuario actualiza su consentimiento a través del menú de configuración de la aplicación
  • Cumplimiento regional: Volver a activar el seguimiento cuando el usuario se traslada a regiones no reguladas

Para ver la documentación completa del método, consulte la referencia resumeAllTracking.


Ejemplo de implementación

Reanudar el seguimiento cuando los usuarios vuelven a optar por él o actualizan sus preferencias de privacidad.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:shared_preferences/shared_preferences.dart';

// User opted back in to tracking
Future<void> onUserResumedTracking() async {
  Singular.resumeAllTracking();
  print('Tracking resumed');

  // Update stored preference
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('tracking_enabled', true);
}

// Handle consent update from settings
void handlePrivacySettingsChange(bool trackingEnabled) {
  if (trackingEnabled) {
    Singular.resumeAllTracking();
    print('Privacy settings: Tracking enabled');
  }
}

IsAllTrackingStopped

Comprueba si se ha desactivado el seguimiento para el usuario actual.

Firma del método:

static Future<bool> isAllTrackingStopped()

Devuelve:

  • Future<true>: El seguimiento se ha detenido actualmente a través de stopAllTracking()
  • Future<false>: El seguimiento está activo (no se ha detenido nunca o se ha reanudado)

Para ver la documentación completa del método, consulte la referencia isAllTrackingStopped.


Ejemplo de implementación

Comprueba el estado de seguimiento para sincronizar el estado de la interfaz de usuario con el estado de seguimiento del SDK.

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';

class PrivacySettingsUI extends StatefulWidget {
  @override
  _PrivacySettingsUIState createState() => _PrivacySettingsUIState();
}

class _PrivacySettingsUIState extends State<PrivacySettingsUI> {
  bool trackingEnabled = false;
  String statusText = 'Checking...';

  @override
  void initState() {
    super.initState();
    updatePrivacyUI();
  }

  // Check current tracking status
  Future<bool> isTrackingEnabled() async {
    final isStopped = await Singular.isAllTrackingStopped();
    return !isStopped;
  }

  // Display privacy status in settings
  Future<String> getPrivacyStatusText() async {
    final isStopped = await Singular.isAllTrackingStopped();
    return isStopped ? 'Tracking: Disabled' : 'Tracking: Enabled';
  }

  // Sync UI with tracking state
  Future<void> updatePrivacyUI() async {
    final isStopped = await Singular.isAllTrackingStopped();
    final status = await getPrivacyStatusText();

    setState(() {
      trackingEnabled = !isStopped;
      statusText = status;
    });

    print('Current tracking state: ${isStopped ? 'Stopped' : 'Active'}');
  }

  // Handle toggle change from UI
  Future<void> onTrackingToggleChanged(bool enabled) async {
    if (enabled) {
      Singular.resumeAllTracking();
    } else {
      Singular.stopAllTracking();
    }

    await updatePrivacyUI();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(statusText),
        Switch(
          value: trackingEnabled,
          onChanged: onTrackingToggleChanged,
        ),
      ],
    );
  }
}

Protección de la privacidad de los niños

TrackingUnder13

Notifica a Singular que el usuario es menor de 13 años para cumplir con la ley COPPA (Children's Online Privacy Protection Act) y otras normativas de privacidad infantil.

Firma del método:

static void trackingUnder13()

Requisitos de cumplimiento:

  • Cumplimiento de COPPA: Obligatorio para apps que recopilen datos de menores de 13 años en Estados Unidos.
  • Contenido restringido por edad: Se utiliza cuando los usuarios se identifican como menores de 13 años durante el registro o la verificación de la edad
  • Seguimiento restringido: Limita la recopilación de datos para cumplir las leyes de protección de la privacidad de los niños

Para obtener la documentación completa del método, consulte la referencia trackingUnder13.


Ejemplo de implementación

Llame a trackingUnder13() inmediatamente después de determinar que el usuario es menor de 13 años mediante la verificación de la edad.

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:shared_preferences/shared_preferences.dart';

class COPPAManager extends StatefulWidget {
  @override
  _COPPAManagerState createState() => _COPPAManagerState();
}

class _COPPAManagerState extends State<COPPAManager> {
  final TextEditingController ageController = TextEditingController();

  // User identified as under 13
  Future<void> onUserUnder13() async {
    Singular.trackingUnder13();
    print('COPPA mode enabled for user under 13');

    // Store age category for app restart
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('user_age_category', 'under_13');
  }

  // Call after age verification
  Future<void> onAgeVerified(int userAge) async {
    final prefs = await SharedPreferences.getInstance();

    if (userAge < 13) {
      Singular.trackingUnder13();
      print('COPPA restrictions applied');

      // Also limit advertising identifiers
      Singular.limitDataSharing(true);

      await prefs.setString('user_age_category', 'under_13');

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Child Account'),
          content: Text('Special privacy protections have been applied.'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
          ],
        ),
      );
    } else {
      await prefs.setString('user_age_category', 'adult');
      print('Adult account - standard tracking');
    }
  }

  void handleAgeSubmit() {
    final userAge = int.tryParse(ageController.text);

    if (userAge == null || userAge < 1 || userAge > 120) {
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Invalid Age'),
          content: Text('Please enter a valid age.'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
          ],
        ),
      );
      return;
    }

    onAgeVerified(userAge);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: ageController,
          decoration: InputDecoration(
            hintText: 'Enter your age',
          ),
          keyboardType: TextInputType.number,
        ),
        ElevatedButton(
          onPressed: handleAgeSubmit,
          child: Text('Submit'),
        ),
      ],
    );
  }

  @override
  void dispose() {
    ageController.dispose();
    super.dispose();
  }
}

Importante: Llame a este método lo antes posible después de determinar que el usuario es menor de 13 años, idealmente durante la inicialización de la aplicación o inmediatamente después de la verificación de la edad. De este modo se garantiza que todo el seguimiento posterior respete la normativa sobre privacidad infantil.


Configuración de LimitAdvertisingIdentifiers

Restrinja la recopilación y el uso de identificadores de publicidad (GAID en Android, IDFA en iOS) durante la inicialización del SDK para aplicaciones destinadas a niños.

Propiedad de configuración:

bool? limitAdvertisingIdentifiers

Parámetros:

  • true: Activar el modo de identificadores publicitarios limitados (restringir la recopilación)
  • false: Desactivar el modo de identificadores publicitarios limitados (recopilación normal)

Casos de uso:

  • Apps enfocadas a niños: Apps diseñadas principalmente para usuarios menores de 13 años
  • Aplicaciones para públicos mixtos: Aplicaciones para adultos y niños en las que la edad se determina antes de la inicialización del SDK.
  • Aplicaciones que dan prioridad a la privacidad: Aplicar limitaciones de identificadores publicitarios desde el principio

Para obtener documentación completa sobre la configuración, consulte la referencia limitAdvertisingIdentifiers.


Ejemplo de configuración

Establezca limitaciones de identificadores de publicidad durante la inicialización del SDK para aplicaciones que conocen los requisitos de privacidad de antemano.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';

// Limit advertising identifiers at initialization
void initializeSingular() {
  final config = SingularConfig(
    'YOUR_SDK_KEY',
    'YOUR_SDK_SECRET'
  );

  // Enable limited advertising identifiers mode
  config.limitAdvertisingIdentifiers = true;

  Singular.start(config);
  print('SDK initialized with advertising identifier restrictions');
}

Enfoque combinado: En el caso de aplicaciones para niños, combine trackingUnder13() con limitAdvertisingIdentifiers = true para garantizar un cumplimiento exhaustivo de la COPPA.


Mejores prácticas de implementación

Ejemplo de gestión completa de la privacidad

Implemente controles de privacidad completos que respeten las preferencias del usuario y cumplan la normativa.

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:shared_preferences/shared_preferences.dart';

const String PREF_USER_CONSENT = 'privacy_user_consent';
const String PREF_DATA_SHARING = 'privacy_data_sharing';
const String PREF_AGE_CATEGORY = 'user_age_category';

/// Initialize privacy settings on app startup based on stored preferences
Future<void> initializePrivacySettings() async {
  final hasUserConsent = await getUserConsent();
  final allowDataSharing = await getDataSharingPreference();
  final ageCategory = await getAgeCategory();

  // Apply age-based restrictions first
  if (ageCategory == 'under_13') {
    Singular.trackingUnder13();
    print('COPPA restrictions applied on startup');
  }

  // Apply stored tracking preference
  if (hasUserConsent) {
    Singular.trackingOptIn();
    Singular.resumeAllTracking();
    print('Privacy initialized: Tracking enabled with consent');
  } else {
    Singular.stopAllTracking();
    print('Privacy initialized: Tracking disabled');
  }

  // Set data sharing preference (inverse logic)
  Singular.limitDataSharing(!allowDataSharing);

  print('Privacy initialized: consent=$hasUserConsent, sharing=$allowDataSharing');
}

/// User accepts tracking via consent dialog
Future<void> onUserAcceptedTracking() async {
  await saveUserConsent(true);

  Singular.trackingOptIn();
  Singular.resumeAllTracking();

  print('User accepted tracking');
}

/// User declines tracking
Future<void> onUserDeclinedTracking() async {
  await saveUserConsent(false);

  Singular.stopAllTracking();

  print('User declined tracking');
}

/// User updates data sharing preference
Future<void> setDataSharingEnabled(bool enabled) async {
  await saveDataSharingPreference(enabled);

  // Note: limitDataSharing uses inverse logic
  // false = data sharing enabled, true = data sharing limited
  Singular.limitDataSharing(!enabled);

  print('Data sharing: ${enabled ? 'Enabled' : 'Limited'}');
}

/// Check if tracking is currently enabled
Future<bool> isTrackingEnabled() async {
  final isStopped = await Singular.isAllTrackingStopped();
  return !isStopped;
}

/// Get current privacy status as readable text
Future<String> getPrivacyStatus() async {
  final isEnabled = await isTrackingEnabled();
  final dataSharingEnabled = await getDataSharingPreference();
  final ageCategory = await getAgeCategory();

  String status = 'Tracking: ${isEnabled ? 'Enabled' : 'Disabled'}
';
  status += 'Data Sharing: ${dataSharingEnabled ? 'Enabled' : 'Limited'}';

  if (ageCategory == 'under_13') {
    status += '
COPPA: Restrictions Applied';
  }

  return status;
}

// Private helper methods for SharedPreferences

Future<bool> getUserConsent() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getBool(PREF_USER_CONSENT) ?? false;
}

Future<void> saveUserConsent(bool consent) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool(PREF_USER_CONSENT, consent);
}

Future<bool> getDataSharingPreference() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getBool(PREF_DATA_SHARING) ?? false;
}

Future<void> saveDataSharingPreference(bool enabled) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool(PREF_DATA_SHARING, enabled);
}

Future<String?> getAgeCategory() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString(PREF_AGE_CATEGORY);
}

// Flutter widget to initialize privacy on app startup
class PrivacyInitializer extends StatefulWidget {
  final Widget child;

  const PrivacyInitializer({Key? key, required this.child}) : super(key: key);

  @override
  _PrivacyInitializerState createState() => _PrivacyInitializerState();
}

class _PrivacyInitializerState extends State<PrivacyInitializer> {
  @override
  void initState() {
    super.initState();
    initializePrivacySettings();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Directrices clave de implantación

Mejores prácticas:

  • Almacenamiento persistente: Guarde las preferencias del usuario utilizando SharedPreferences o una solución de almacenamiento seguro.
  • Inicialización temprana: Aplique la configuración de privacidad antes de la inicialización del SDK cuando sea posible.
  • Sincronización de la interfaz de usuario: Mantenga la interfaz de usuario sincronizada con el estado real del SDK utilizando isAllTrackingStopped()
  • Comunicación clara: Proporcione controles de privacidad claros y accesibles en la configuración de la aplicación
  • Lógica inversa: Recuerde que limitDataSharing(false) significa que el intercambio de datos está activado, mientras que true significa que está limitado.
  • Prioridad COPPA: Aplique las protecciones de la privacidad infantil (trackingUnder13()) antes que otros ajustes de privacidad
  • Documentación de cumplimiento: Mantener registros de cuándo y cómo los usuarios dan o revocan su consentimiento