SDK do Flutter - Privacidade de dados

Documento

Conformidade com as leis de privacidade de dados

Implementar a recolha de dados em conformidade com a privacidade, notificando a Singular das escolhas de consentimento do utilizador para GDPR, CCPA, COPPA e outros regulamentos de privacidade do consumidor.

Quando os utilizadores consentirem ou recusarem a partilha das suas informações com terceiros, utilize os métodos de privacidade da Singular para comunicar a sua escolha. Isto assegura a conformidade com regulamentos como o California Consumer Privacy Act (CCPA) e permite que os parceiros respeitem as preferências de privacidade dos utilizadores.

Saiba mais: Consulte Privacidade do usuário e Limitar compartilhamento de dados para obter informações detalhadas sobre como a Singular processa o consentimento de privacidade.


Limitar a partilha de dados

Controlar o compartilhamento de dados de terceiros

Notificar a Singular se os usuários consentiram em compartilhar seus dados pessoais com parceiros terceiros usando o método limitDataSharing().

Assinatura do método:

static void limitDataSharing(bool shouldLimitDataSharing)

Parâmetros:

  • false: O utilizador optou e consentiu a partilha dos seus dados
  • true: O utilizador optou por não participar e não consente a partilha dos seus dados

Importante: Embora opcional, este método afecta a partilha de dados de atribuição. Alguns parceiros só partilham informações de atribuição completas quando são explicitamente notificados de que os utilizadores optaram por participar.

Para obter a documentação completa do método, consulte a referência limitDataSharing.


Exemplos de utilização

Implementar controlos de partilha de dados com base nas preferências de privacidade do utilizador.

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'}');
}

Como funciona:

A Singular usa essa configuração em Postbacks de privacidade do usuárioe a passa para parceiros que a exigem para conformidade regulatória.


Métodos de conformidade com o GDPR

Gerencie o consentimento de rastreamento do usuário e controle a funcionalidade do SDK para estar em conformidade com o GDPR (Regulamento Geral de Proteção de Dados) e outros regulamentos de privacidade.

Gestão do consentimento de rastreio

TrackingOptIn

Regista o consentimento explícito do utilizador para o rastreio, enviando um evento GDPR opt-in para os servidores Singular.

Assinatura do método:

static void trackingOptIn()

Quando usar:

  • Conformidade com o GDPR: Chamar quando os usuários consentirem explicitamente com o rastreamento em regiões regulamentadas pelo GDPR
  • Registro de consentimento: Marca os usuários como tendo fornecido consentimento GDPR nos sistemas da Singular
  • Comportamento padrão: Sem esta chamada, o SDK continua a rastrear mas não regista especificamente o consentimento

Para obter a documentação completa do método, consulte a referência trackingOptIn.


Exemplo de implementação

Chame trackingOptIn() depois de os utilizadores aceitarem o consentimento de rastreio através da caixa de diálogo de consentimento da sua aplicação.

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 controlo de rastreio

StopAllTracking

Desactiva completamente todas as actividades de rastreio do SDK para o utilizador atual neste dispositivo.

Assinatura do método:

static void stopAllTracking()

Crítico Aviso: Este método desactiva permanentemente o SDK até que resumeAllTracking() seja chamado. O estado de desativação persiste durante as reinicializações da aplicação e só pode ser revertido programaticamente.

Comportamento:

  • Efeito imediato: Interrompe todo o rastreamento, relatório de eventos e coleta de dados instantaneamente
  • Estado persistente: Permanece desativado mesmo depois de a aplicação ser fechada e reaberta
  • Sem reinicialização automática: Tem de chamar explicitamente resumeAllTracking() para voltar a ativar

Para obter a documentação completa do método, consulte a referência stopAllTracking.


Exemplo de implementação

Parar o rastreio quando os utilizadores recusam o consentimento ou optam por sair através das definições de privacidade.

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');
  }
}

RetomarTudoRastreio

Reativar o rastreio depois de ter sido interrompido com stopAllTracking().

Assinatura do método:

static void resumeAllTracking()

Casos de utilização:

  • Alteração de consentimento: O utilizador altera as preferências de privacidade e volta a optar pelo rastreio
  • Definições de privacidade: O utilizador actualiza o consentimento através do menu de definições da aplicação
  • Conformidade regional: Reativar o rastreio quando o utilizador se desloca para regiões não regulamentadas

Para obter a documentação completa do método, consulte a referência resumeAllTracking.


Exemplo de implementação

Retomar o rastreio quando os utilizadores optarem por voltar a participar ou actualizarem as suas preferências de privacidade.

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

Verificar se o rastreio foi desativado para o utilizador atual.

Assinatura do método:

static Future<bool> isAllTrackingStopped()

Retorna:

  • Future<true>: O rastreamento está atualmente parado via stopAllTracking()
  • Future<false>: O rastreio está ativo (nunca parou ou foi retomado)

Para obter a documentação completa do método, consulte a referência isAllTrackingStopped.


Exemplo de implementação

Verificar o estado do rastreio para sincronizar o estado da IU com o estado do rastreio do 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,
        ),
      ],
    );
  }
}

Proteção da privacidade das crianças

TrackingUnder13

Notifica a Singular de que o utilizador tem menos de 13 anos de idade para cumprir a COPPA (Children's Online Privacy Protection Act) e outros regulamentos de privacidade infantil.

Assinatura do método:

static void trackingUnder13()

Requisitos de conformidade:

  • Conformidade com a COPPA: Necessário para aplicações que recolhem dados de crianças com menos de 13 anos nos Estados Unidos
  • Conteúdo com restrição de idade: Utilizar quando os utilizadores se identificam como tendo menos de 13 anos durante o registo ou a verificação da idade
  • Rastreio restrito: Limita a recolha de dados para cumprir as leis de proteção da privacidade das crianças

Para obter a documentação completa do método, consulte a referência trackingUnder13.


Exemplo de implementação

Chame trackingUnder13() imediatamente após determinar que o utilizador tem menos de 13 anos através da verificação da idade.

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: Chame este método o mais cedo possível depois de determinar que o utilizador tem menos de 13 anos, idealmente durante a inicialização da aplicação ou imediatamente após a verificação da idade. Isso garante que todo o rastreamento subsequente respeite os regulamentos de privacidade das crianças.


Configuração de LimitAdvertisingIdentifiers

Restringe a coleta e o uso de identificadores de publicidade (GAID no Android, IDFA no iOS) durante a inicialização do SDK para aplicativos que atendem a crianças.

Propriedade de configuração:

bool? limitAdvertisingIdentifiers

Parâmetros:

  • true: Ativar o modo de identificadores de publicidade limitados (restringir a recolha)
  • false: Desativar o modo de identificadores de publicidade limitados (recolha normal)

Casos de uso:

  • Aplicações direcionadas para crianças: Aplicações concebidas principalmente para utilizadores com menos de 13 anos
  • Aplicações de público misto: Aplicações que servem tanto adultos como crianças, em que a idade é determinada antes da inicialização do SDK
  • Aplicações com prioridade à privacidade: Aplicar limitações de identificadores de publicidade desde o início

Para obter a documentação de configuração completa, consulte a referência limitAdvertisingIdentifiers.


Exemplo de configuração

Defina limitações de identificadores de publicidade durante a inicialização do SDK para aplicativos que conhecem os requisitos de privacidade desde o início.

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');
}

Abordagem combinada: Para aplicações que servem crianças, combine trackingUnder13() com limitAdvertisingIdentifiers = true para garantir uma conformidade abrangente com a COPPA.


Melhores práticas de implementação

Exemplo de gestão de privacidade completa

Implemente controlos de privacidade abrangentes que respeitem as preferências do utilizador e cumpram os regulamentos.

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;
  }
}

Principais diretrizes de implementação

Melhores práticas:

  • Armazenamento persistente: Guarde as preferências do utilizador utilizando SharedPreferences ou uma solução de armazenamento seguro
  • Inicialização antecipada: Aplicar configurações de privacidade antes da inicialização do SDK, quando possível
  • Sincronização da IU: Manter as configurações da interface do usuário sincronizadas com o estado real do SDK usando isAllTrackingStopped()
  • Comunicação clara: Fornecer controlos de privacidade claros e acessíveis nas definições da aplicação
  • Lógica inversa: Lembre-se de que limitDataSharing(false) significa que a partilha de dados está activada, enquanto true significa que está limitada
  • Prioridade COPPA: Aplicar as protecções de privacidade das crianças (trackingUnder13()) antes de outras definições de privacidade
  • Documentação de conformidade: Manter registos de quando e como os utilizadores fornecem ou revogam o consentimento