Flutter SDK - データプライバシー

ドキュメント

データプライバシー法の遵守

GDPR、CCPA、COPPA、その他の消費者プライバシー規制に対するユーザーの同意の選択肢をSingularに通知することで、プライバシーに準拠したデータ収集を実施します。

ユーザーが第三者との情報共有に同意または拒否する場合は、Singularのプライバシー保護方法を使用してユーザーの選択を伝えます。これにより、カリフォルニア州消費者プライバシー法(CCPA)などの規制への準拠が保証され、パートナーはユーザーのプライバシー設定を尊重することができます。

詳細はこちら:Singularがプライバシーの同意をどのように処理するかについての詳細は、ユーザーのプライバシーとデータ共有の制限をご覧ください。


データ共有の制限

サードパーティデータ共有の管理

limitDataSharing() メソッドを使用して、ユーザーが個人データをサードパーティパートナーと共有することに同意したかどうかをSingularに通知します。

メソッドの署名

static void limitDataSharing(bool shouldLimitDataSharing)

パラメータ

  • false:ユーザーがオプトインし、データ共有に同意している。
  • true:ユーザーはオプトアウトしており、データの共有に同意していない。

重要:オプションですが、このメソッドはアトリビューションデータの共有に影響します。一部のパートナーは、ユーザーがオプトインしたことを明示的に通知された場合にのみ、完全なアトリビューション情報を共有します。

メソッドの完全なドキュメントについては、limitDataSharing リファレンスを参照してください。


使用例

ユーザーのプライバシー設定に基づいて、データ共有の制御を実装します。

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

仕組み

Singularはこの設定をユーザープライバシーポストバックで使用し、規制遵守のためにそれを必要とするパートナーに渡します。


GDPRコンプライアンスの方法

GDPR(一般データ保護規則)やその他のプライバシー規制に準拠するために、ユーザーのトラッキング同意の管理とSDK機能の制御を行います。

トラッキング同意管理

トラッキングオプトイン

GDPRオプトインイベントをSingularサーバーに送信することで、トラッキングに対する明示的なユーザーの同意を記録します。

メソッドの署名

static void trackingOptIn()

いつ使用するか

  • GDPRコンプライアンス:GDPR規制地域でユーザーがトラッキングに明示的に同意した場合に呼び出します。
  • 同意の記録:ユーザーがSingularのシステムでGDPRに同意したことを記録します。
  • デフォルトの動作:この呼び出しがない場合、SDKはトラッキングを継続しますが、特に同意を記録しません。

メソッドの完全なドキュメントについては、trackingOptInリファレンスを参照してください。


実装例

ユーザーがアプリの同意ダイアログを通じてトラッキングの同意を受け入れた後に、trackingOptIn()

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(),
      ),
    );
  }
}

トラッキング・コントロール・メソッド

StopAllTracking

このデバイス上の現在のユーザーのすべてのSDKトラッキング活動を完全に無効にします。

メソッドの署名

static void stopAllTracking()

重大な警告:このメソッドは、resumeAllTracking() が呼び出されるまで、SDKを永久に無効にします。無効化された状態はアプリの再起動後も持続し、プログラムでのみ元に戻すことができます。

動作

  • 即時効果:すべてのトラッキング、イベントレポート、データ収集を即座に停止します。
  • 永続的な状態:アプリを終了し、再度起動しても無効のままです。
  • 自動リセットなし:再度有効にするには、resumeAllTracking() を明示的に呼び出す必要があります。

メソッドの詳細については、stopAllTrackingリファレンスを参照してください。


実装例

ユーザーが同意を拒否した場合、またはプライバシー設定でオプトアウトした場合にトラッキングを停止する。

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

再開

stopAllTracking() で停止したトラッキングを再度有効にする。

メソッドの署名

static void resumeAllTracking()

ユースケース

  • 同意の変更:ユーザーがプライバシー設定を変更し、トラッキングを再開する。
  • プライバシー設定:ユーザーがアプリの設定メニューから同意を更新
  • 地域コンプライアンス:ユーザーが非規制地域に移動した場合、トラッキングを再度有効にする。

メソッドの完全なドキュメントについては、resumeAllTrackingのリファレンスを参照してください。


実装例

ユーザーがオプトインに戻るか、プライバシー設定を更新したときにトラッキングを再開する。

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

現在のユーザーに対してトラッキングが無効化されているかどうかをチェックする。

メソッドのシグネチャ

static Future<bool> isAllTrackingStopped()

戻り値

  • Future<true>: stopAllTracking()
  • Future<false>:追跡がアクティブ(停止または再開されたことがない

メソッドの完全なドキュメントは、isAllTrackingStopped リファレンスを参照してください。


実装例

SDKのトラッキング状態とUIの状態を同期させるために、トラッキング状態をチェックします。

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,
        ),
      ],
    );
  }
}

子供のプライバシー保護

TrackingUnder13

COPPA(児童オンラインプライバシー保護法)およびその他の児童プライバシー規制に準拠するために、ユーザーが13歳未満であることをSingularに通知します。

メソッドの署名

static void trackingUnder13()

コンプライアンス要件

  • COPPA準拠:米国で13歳未満の子供からデータを収集するアプリに必要です。
  • 年齢制限コンテンツ:登録時または年齢確認時に、ユーザーが13歳未満であることを確認した場合に使用。
  • 制限付きトラッキング:子どものプライバシー保護法に準拠するため、データ収集を制限します。

メソッドの完全なドキュメントについては、trackingUnder13のリファレンスを参照してください。


実装例

年齢確認によってユーザが13歳未満であると判断した直後にtrackingUnder13() を呼び出します。

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

重要:ユーザーが13歳未満であると判断した後、できるだけ早い段階でこのメソッドを呼び出してください。理想的には、アプリの初期化中か年齢認証の直後です。こうすることで、その後のすべてのトラッキングが子どものプライバシー規定を尊重するようになります。


LimitAdvertisingIdentifiersコンフィギュレーション

子ども向けアプリのSDK初期化時に、広告識別子(AndroidではGAID、iOSではIDFA)の収集と使用を制限します。

設定プロパティです:

bool? limitAdvertisingIdentifiers

パラメータ

  • true:限定広告識別子モードを有効にする(収集を制限する
  • false:限定広告識別子モードを無効にする(通常の収集

使用例

  • 子供向けアプリ:主に13歳未満のユーザー向けに設計されたアプリ
  • 混合オーディエンスアプリ:SDKの初期化前に年齢が決定される、大人と子供の両方に対応するアプリ
  • プライバシー第一のアプリ:広告識別子の制限を最初から適用

設定の詳細については、limitAdvertisingIdentifiersのリファレンスを参照してください。


設定例

プライバシー要件を事前に把握しているアプリに対して、SDKの初期化時に広告識別子の制限を設定します。

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

複合的なアプローチ:子供向けアプリの場合は、trackingUnder13()limitAdvertisingIdentifiers = true を組み合わせて、包括的な COPPA コンプライアンスを確保します。


実装のベストプラクティス

完全なプライバシー管理の例

ユーザーの好みを尊重し、規制に準拠した包括的なプライバシー管理を実装する。

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

主な実施ガイドライン

ベストプラクティス

  • 永続的なストレージ:SharedPreferencesまたは安全なストレージ・ソリューションを使用してユーザー設定を保存する。
  • 早期の初期化:可能であればSDKの初期化前にプライバシー設定を適用
  • UIの同期: isAllTrackingStopped()を使用して、設定 UI を実際の SDK 状態と同期させます。
  • 明確なコミュニケーション:アプリの設定で明確でアクセスしやすいプライバシーコントロールを提供する。
  • 逆ロジック: limitDataSharing(false) はデータ共有が有効であることを意味し、true制限されていることを意味する。
  • COPPAの優先事項:子どものプライバシー保護(trackingUnder13() )を他のプライバシー設定よりも優先して適用する。
  • コンプライアンスの文書化:ユーザーがいつ、どのように同意を提供し、または取り消したかの記録を保持する。