Complying with Data Privacy Laws
Implement privacy-compliant data collection by notifying Singular of user consent choices for GDPR, CCPA, and other consumer privacy regulations.
When users consent or decline to share their information with third parties, use Singular's privacy methods to communicate their choice. This ensures compliance with regulations like California Consumer Privacy Act (CCPA) and enables partners to respect user privacy preferences.
Learn More: See User Privacy and Limit Data Sharing for detailed information on how Singular processes privacy consent.
Limit Data Sharing
Control Third-Party Data Sharing
Notify Singular whether users have consented to share their personal data with third-party partners using the
limitDataSharing
method.
Method Signature:
public static void limitDataSharing(boolean shouldLimitDataSharing);
Parameters:
- false: User has opted in and consented to share their data
- true: User has opted out and does not consent to share their data
Important: While optional, this method affects attribution data sharing. Some partners only share complete attribution information when explicitly notified that users have opted in.
Usage Examples
// User has opted in to share their data
Singular.limitDataSharing(false)
// User has opted out and declined to share their data
Singular.limitDataSharing(true)
// Example: Set based on user preference
fun handlePrivacyConsent(userConsented: Boolean) {
// Pass inverse: false = opted in, true = opted out
Singular.limitDataSharing(!userConsented)
Log.d("Privacy", "Data sharing: ${if (userConsented) "Enabled" else "Limited"}")
}
// User has opted in to share their data
Singular.limitDataSharing(false);
// User has opted out and declined to share their data
Singular.limitDataSharing(true);
// Example: Set based on user preference
public void handlePrivacyConsent(boolean userConsented) {
// Pass inverse: false = opted in, true = opted out
Singular.limitDataSharing(!userConsented);
Log.d("Privacy", "Data sharing: " + (userConsented ? "Enabled" : "Limited"));
}
How It Works:
Singular uses this setting in User Privacy Postbacks and passes it to partners who require it for regulatory compliance.
Per-Event Limit Data Sharing
Override Data Sharing on a Single Event
You can override the global
limitDataSharing
setting for a single event or custom revenue call by passing the
Attributes.sngAttrLimitDataSharing
attribute alongside your other event arguments. This is useful when a user updates their consent inline with an action and you want that specific event to reflect the new choice immediately, without changing the SDK-wide setting for subsequent events.
Accepted Values:
- false: User has opted in for this event only
- true: User has opted out for this event only
Important:
The attribute must be a
Boolean
. Non-boolean inputs are ignored and the request falls back to the global
limitDataSharing
setting. Either way, the attribute is removed from the event's arguments before the request is sent, so it never appears in your event payload.
Usage Examples
// Example 1: Standard event, opt out only for this event
val args = JSONObject()
.put(Attributes.sngAttrLimitDataSharing.toString(), true)
.put("order_id", "12345")
Singular.event("checkout_completed", args.toString())
// Example 2: Custom revenue, opt in only for this event
val attributes = mutableMapOf<String, Any>().apply {
put(Attributes.sngAttrLimitDataSharing.toString(), false)
put("sku", "premium_monthly")
}
Singular.customRevenue("premium_purchase", "USD", 9.99, attributes)
// Example 1: Standard event, opt out only for this event
JSONObject args = new JSONObject();
args.put(Attributes.sngAttrLimitDataSharing.toString(), true);
args.put("order_id", "12345");
Singular.event("checkout_completed", args.toString());
// Example 2: Custom revenue, opt in only for this event
Map<String, Object> attributes = new HashMap<>();
attributes.put(Attributes.sngAttrLimitDataSharing.toString(), false);
attributes.put("sku", "premium_monthly");
Singular.customRevenue("premium_purchase", "USD", 9.99, attributes);
How It Works:
When a valid boolean is supplied, the SDK removes the attribute from the event's arguments and writes it to the request's
data_sharing_options.limit_data_sharing
field — overriding the global
limitDataSharing
setting for that single request only. The global setting and the result of
getLimitDataSharing
are not modified, so any subsequent events that do not supply the attribute continue to use the global choice.
GDPR Compliance Methods
Manage user tracking consent and control SDK functionality to comply with GDPR (General Data Protection Regulation) and other privacy regulations.
Tracking Consent Management
trackingOptIn
Record explicit user consent for tracking by sending a GDPR opt-in event to Singular servers.
Method Signature:
Singular.trackingOptIn()
When to Use:
- GDPR Compliance: Call when users explicitly consent to tracking in GDPR-regulated regions
- Consent Recording: Marks users as having provided GDPR consent in Singular's systems
- Default Behavior: Without this call, SDK continues tracking but doesn't specifically record consent
// User accepted tracking consent
Singular.trackingOptIn()
// Example: Call after consent dialog
fun onUserAcceptedTracking() {
Singular.trackingOptIn()
Log.d("GDPR", "User opted in to tracking")
}
// User accepted tracking consent
Singular.trackingOptIn();
// Example: Call after consent dialog
public void onUserAcceptedTracking() {
Singular.trackingOptIn();
Log.d("GDPR", "User opted in to tracking");
}
Tracking Control Methods
stopAllTracking
Completely disable all SDK tracking activities for the current user on this device.
Method Signature:
Singular.stopAllTracking()
Critical Warning:
This method permanently disables the SDK until
resumeAllTracking()
is called. The disabled state persists across app restarts and can only be reversed programmatically.
Behavior:
- Immediate Effect: Stops all tracking, event reporting, and data collection instantly
- Persistent State: Remains disabled even after app closes and reopens
-
No Automatic Reset:
Must explicitly call
resumeAllTracking()to re-enable
// User declined all tracking
Singular.stopAllTracking()
// Example: Handle user opt-out
fun onUserDeclinedTracking() {
Singular.stopAllTracking()
Log.d("Privacy", "All tracking stopped")
// Optionally store preference
saveUserTrackingPreference(false)
}
// User declined all tracking
Singular.stopAllTracking();
// Example: Handle user opt-out
public void onUserDeclinedTracking() {
Singular.stopAllTracking();
Log.d("Privacy", "All tracking stopped");
// Optionally store preference
saveUserTrackingPreference(false);
}
resumeAllTracking
Re-enable tracking after it was stopped with
stopAllTracking()
.
Method Signature:
Singular.resumeAllTracking()
Use Cases:
- Consent Change: User changes privacy preferences and opts back into tracking
- Privacy Settings: User updates consent through app settings menu
- Regional Compliance: Re-enable tracking when user moves to non-regulated regions
// User opted back in to tracking
Singular.resumeAllTracking()
// Example: Handle consent update
fun onUserResumedTracking() {
Singular.resumeAllTracking()
Log.d("Privacy", "Tracking resumed")
// Optionally update stored preference
saveUserTrackingPreference(true)
}
// User opted back in to tracking
Singular.resumeAllTracking();
// Example: Handle consent update
public void onUserResumedTracking() {
Singular.resumeAllTracking();
Log.d("Privacy", "Tracking resumed");
// Optionally update stored preference
saveUserTrackingPreference(true);
}
isAllTrackingStopped
Check whether tracking has been disabled for the current user.
Method Signature:
public static boolean isAllTrackingStopped();
Returns:
-
true:
Tracking is currently stopped via
stopAllTracking() - false: Tracking is active (either never stopped or resumed)
Returns
false
if the SDK has not been initialized, regardless of any previous tracking state. Always confirm
Singular.init()
has completed before relying on this value, and treat
false
as "not stopped or not yet initialized" rather than "actively tracking."
// Check current tracking status
val isTrackingStopped = Singular.isAllTrackingStopped()
// Example: Display privacy status in settings
fun getPrivacyStatusText(): String {
return if (Singular.isAllTrackingStopped()) {
"Tracking: Disabled"
} else {
"Tracking: Enabled"
}
}
// Example: Sync UI with tracking state
fun updatePrivacyToggle() {
val isStopped = Singular.isAllTrackingStopped()
privacyToggle.isChecked = !isStopped
Log.d("Privacy", "Current tracking state: ${if (isStopped) "Stopped" else "Active"}")
}
// Check current tracking status
boolean isTrackingStopped = Singular.isAllTrackingStopped();
// Example: Display privacy status in settings
public String getPrivacyStatusText() {
if (Singular.isAllTrackingStopped()) {
return "Tracking: Disabled";
} else {
return "Tracking: Enabled";
}
}
// Example: Sync UI with tracking state
public void updatePrivacyToggle() {
boolean isStopped = Singular.isAllTrackingStopped();
privacyToggle.setChecked(!isStopped);
Log.d("Privacy", "Current tracking state: " + (isStopped ? "Stopped" : "Active"));
}
Children's Privacy Protection
trackingUnder13
Notify Singular that the user is under 13 years old to comply with COPPA (Children's Online Privacy Protection Act) and other child privacy regulations.
Method Signature:
Singular.trackingUnder13()
Compliance Requirements:
- COPPA Compliance: Required for apps that collect data from children under 13 in the United States
- Age-Gated Content: Use when users identify themselves as under 13 during registration or age verification
- Restricted Tracking: Limits data collection to comply with children's privacy protection laws
// User identified as under 13
Singular.trackingUnder13()
// Example: Call after age verification
fun onAgeVerified(userAge: Int) {
if (userAge < 13) {
Singular.trackingUnder13()
Log.d("Privacy", "COPPA mode enabled for user under 13")
}
}
// User identified as under 13
Singular.trackingUnder13();
// Example: Call after age verification
public void onAgeVerified(int userAge) {
if (userAge < 13) {
Singular.trackingUnder13();
Log.d("Privacy", "COPPA mode enabled for user under 13");
}
}
Important: Call this method as early as possible after determining the user is under 13, ideally during app initialization or immediately after age verification. This ensures all subsequent tracking respects children's privacy regulations.
setLimitAdvertisingIdentifiers
Restrict the collection and use of advertising identifiers (GAID on Android) after SDK initialization for mixed audience apps.
Method Signature:
public static void setLimitAdvertisingIdentifiers(boolean enabled);
Parameters:
-
enabled:
Pass
trueto limit advertising identifier collection;falseto restore normal collection.
Do not confuse this with the config-time variant.
The runtime method
Singular.setLimitAdvertisingIdentifiers(boolean)
requires a boolean argument, while the config-time method
SingularConfig.withLimitAdvertisingIdentifiers()
takes no arguments and enables the limit unconditionally. Calling the runtime method with no arguments will not compile.
Use Cases:
- Mixed Audience Apps: Apps that serve both adults and children where age is determined after app launch
- Dynamic Privacy Controls: Adjust tracking based on user actions or content being accessed
- Runtime Restrictions: Apply advertising identifier limitations after initial SDK setup
// Limit advertising identifiers after initialization
Singular.setLimitAdvertisingIdentifiers(true)
// Example: Mixed audience app with age gate
fun onKidsModeSwitched(isKidsMode: Boolean) {
if (isKidsMode) {
Singular.setLimitAdvertisingIdentifiers(true)
Log.d("Privacy", "Advertising identifiers limited for kids mode")
}
}
// Example: Content-based restrictions
fun onViewingChildrensContent() {
Singular.setLimitAdvertisingIdentifiers(true)
Log.d("Privacy", "Ad identifiers restricted for children's content")
}
// Limit advertising identifiers after initialization
Singular.setLimitAdvertisingIdentifiers(true);
// Example: Mixed audience app with age gate
public void onKidsModeSwitched(boolean isKidsMode) {
if (isKidsMode) {
Singular.setLimitAdvertisingIdentifiers(true);
Log.d("Privacy", "Advertising identifiers limited for kids mode");
}
}
// Example: Content-based restrictions
public void onViewingChildrensContent() {
Singular.setLimitAdvertisingIdentifiers(true);
Log.d("Privacy", "Ad identifiers restricted for children's content");
}
Configuration Alternative:
You can also limit advertising identifiers during SDK initialization using
SingularConfig.withLimitAdvertisingIdentifiers()
if you know privacy requirements before the SDK starts.
Configuration Method
Set advertising identifier limitations during SDK initialization for apps that know privacy requirements upfront.
// Limit advertising identifiers at initialization
val config = SingularConfig("SDK_KEY", "SDK_SECRET")
.withLimitAdvertisingIdentifiers()
Singular.init(applicationContext, config)
// Limit advertising identifiers at initialization
SingularConfig config = new SingularConfig("SDK_KEY", "SDK_SECRET")
.withLimitAdvertisingIdentifiers();
Singular.init(getApplicationContext(), config);
Implementation Best Practices
Complete Privacy Management Example
Implement comprehensive privacy controls that respect user preferences and comply with regulations.
class PrivacyManager(private val context: Context) {
private val prefs = context.getSharedPreferences("privacy_prefs", Context.MODE_PRIVATE)
// Initialize privacy settings on app start
fun initializePrivacy() {
val hasUserConsent = getUserConsent()
val allowDataSharing = getDataSharingPreference()
val userAge = getUserAge()
// Apply stored preferences
if (hasUserConsent) {
Singular.trackingOptIn()
Singular.resumeAllTracking()
} else {
Singular.stopAllTracking()
}
// Set data sharing preference
Singular.limitDataSharing(!allowDataSharing)
// Handle children's privacy
if (userAge > 0 && userAge < 13) {
Singular.trackingUnder13()
Singular.setLimitAdvertisingIdentifiers(true)
}
Log.d("Privacy", "Initialized: consent=$hasUserConsent, sharing=$allowDataSharing, age=$userAge")
}
// User accepts tracking via consent dialog
fun userAcceptedTracking() {
saveUserConsent(true)
Singular.trackingOptIn()
Singular.resumeAllTracking()
Log.d("Privacy", "User accepted tracking")
}
// User declines tracking
fun userDeclinedTracking() {
saveUserConsent(false)
Singular.stopAllTracking()
Log.d("Privacy", "User declined tracking")
}
// User updates data sharing preference
fun setDataSharingEnabled(enabled: Boolean) {
saveDataSharingPreference(enabled)
// Note: limitDataSharing uses inverse logic
Singular.limitDataSharing(!enabled)
Log.d("Privacy", "Data sharing: ${if (enabled) "Enabled" else "Limited"}")
}
// Handle age verification result
fun onAgeVerified(age: Int) {
saveUserAge(age)
if (age < 13) {
Singular.trackingUnder13()
Singular.setLimitAdvertisingIdentifiers(true)
Log.d("Privacy", "COPPA restrictions applied for user under 13")
}
}
// Get current tracking status
fun isTrackingEnabled(): Boolean {
return !Singular.isAllTrackingStopped()
}
// Private helper methods
private fun getUserConsent(): Boolean {
return prefs.getBoolean("user_consent", false)
}
private fun saveUserConsent(consent: Boolean) {
prefs.edit().putBoolean("user_consent", consent).apply()
}
private fun getDataSharingPreference(): Boolean {
return prefs.getBoolean("data_sharing", false)
}
private fun saveDataSharingPreference(enabled: Boolean) {
prefs.edit().putBoolean("data_sharing", enabled).apply()
}
private fun getUserAge(): Int {
return prefs.getInt("user_age", 0)
}
private fun saveUserAge(age: Int) {
prefs.edit().putInt("user_age", age).apply()
}
}
public class PrivacyManager {
private Context context;
private SharedPreferences prefs;
public PrivacyManager(Context context) {
this.context = context;
this.prefs = context.getSharedPreferences("privacy_prefs", Context.MODE_PRIVATE);
}
// Initialize privacy settings on app start
public void initializePrivacy() {
boolean hasUserConsent = getUserConsent();
boolean allowDataSharing = getDataSharingPreference();
int userAge = getUserAge();
// Apply stored preferences
if (hasUserConsent) {
Singular.trackingOptIn();
Singular.resumeAllTracking();
} else {
Singular.stopAllTracking();
}
// Set data sharing preference
Singular.limitDataSharing(!allowDataSharing);
// Handle children's privacy
if (userAge > 0 && userAge < 13) {
Singular.trackingUnder13();
Singular.setLimitAdvertisingIdentifiers(true);
}
Log.d("Privacy", "Initialized: consent=" + hasUserConsent + ", sharing=" + allowDataSharing + ", age=" + userAge);
}
// User accepts tracking via consent dialog
public void userAcceptedTracking() {
saveUserConsent(true);
Singular.trackingOptIn();
Singular.resumeAllTracking();
Log.d("Privacy", "User accepted tracking");
}
// User declines tracking
public void userDeclinedTracking() {
saveUserConsent(false);
Singular.stopAllTracking();
Log.d("Privacy", "User declined tracking");
}
// User updates data sharing preference
public void setDataSharingEnabled(boolean enabled) {
saveDataSharingPreference(enabled);
// Note: limitDataSharing uses inverse logic
Singular.limitDataSharing(!enabled);
Log.d("Privacy", "Data sharing: " + (enabled ? "Enabled" : "Limited"));
}
// Handle age verification result
public void onAgeVerified(int age) {
saveUserAge(age);
if (age < 13) {
Singular.trackingUnder13();
Singular.setLimitAdvertisingIdentifiers(true);
Log.d("Privacy", "COPPA restrictions applied for user under 13");
}
}
// Get current tracking status
public boolean isTrackingEnabled() {
return !Singular.isAllTrackingStopped();
}
// Private helper methods
private boolean getUserConsent() {
return prefs.getBoolean("user_consent", false);
}
private void saveUserConsent(boolean consent) {
prefs.edit().putBoolean("user_consent", consent).apply();
}
private boolean getDataSharingPreference() {
return prefs.getBoolean("data_sharing", false);
}
private void saveDataSharingPreference(boolean enabled) {
prefs.edit().putBoolean("data_sharing", enabled).apply();
}
private int getUserAge() {
return prefs.getInt("user_age", 0);
}
private void saveUserAge(int age) {
prefs.edit().putInt("user_age", age).apply();
}
}
Best Practices:
- Persistent Storage: Save user preferences in SharedPreferences or secure storage
- Early Initialization: Apply privacy settings before SDK initialization when possible
-
UI Sync:
Keep settings UI synchronized with actual SDK state using
isAllTrackingStopped() - Clear Communication: Provide clear, accessible privacy controls in app settings
- Age Verification: Implement robust age verification for apps targeting children
-
Combined Controls:
For users under 13, apply both
trackingUnder13()andsetLimitAdvertisingIdentifiers(true)