Skip to content

analytics-debugger/duckcmp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DuckCMP

DuckCMP

Open-source Consent Management Platform SDK for Android & iOS

https://duckcmp.com

Quick Start · Configuration · API · WebView Bridge · Docs


  • Two native SDKs — Kotlin + Compose (Android), Swift + SwiftUI (iOS)
  • 4 layout options — Banner, Popup, Fullscreen, Bottom Sheet
  • Fully customizable — theme, categories, labels, button order
  • WebView bridge__duckcmp JS interface for hybrid apps
  • Zero dependencies — no third-party libraries beyond platform frameworks

Quick Start

Android

// 1. Add GitHub Packages repo (settings.gradle.kts)
dependencyResolutionManagement {
    repositories {
        maven {
            url = uri("https://maven.pkg.github.com/analytics-debugger/duckcmp")
            credentials {
                username = providers.gradleProperty("gpr.user").orNull
                    ?: System.getenv("GITHUB_ACTOR")
                password = providers.gradleProperty("gpr.key").orNull
                    ?: System.getenv("GITHUB_TOKEN")
            }
        }
    }
}

// app/build.gradle.kts
dependencies {
    implementation("com.analytics.debugger:duck-cmp:0.0.1-alpha")
}
// 2. Initialize
DuckCMP.initialize(context)

// 3. Show dialog
if (DuckCMP.shouldShowDialog()) {
    DuckCMP.showConsentDialog(activity)
}

// 4. Check consent
val hasAnalytics = DuckCMP.hasConsent(2)  // category ID 2 = Analytics
val status = DuckCMP.getConsentStatus()   // UNKNOWN, ACCEPTED, REJECTED, PARTIAL

iOS

// 1. Add via Swift Package Manager
// Xcode: File > Add Package Dependencies


// 2. Initialize
import DuckCMP

DuckCMP.initialize()

// 3. Show dialog (SwiftUI)
.fullScreenCover(isPresented: $showConsent) {
    ConsentDialogView {
        showConsent = false
    }
}

// 4. Check consent
let hasAnalytics = DuckCMP.hasConsent(categoryId: 2)
let status = DuckCMP.getConsentStatus()

Default Categories

ID Category Description Required
1 Necessary Essential for the app to function Yes (always on)
2 Analytics Help us understand how you use the app No
3 Marketing Used to deliver personalized ads No
4 Preferences Remember your settings and choices No

Configuration

All configuration is done through ConsentConfig. Every property has a sensible default.

Full Example

DuckCMP.initialize(context, ConsentConfig(
    // Labels
    dialogTitle = "Your Privacy Matters",
    dialogMessage = "Choose which data categories you allow.",
    acceptAllLabel = "Allow All",
    rejectAllLabel = "Deny All",
    saveChoicesLabel = "Confirm My Choices",
    manageLabel = "Manage Preferences",

    // Behavior
    requireConsent = true,              // must choose before dismissing
    showCategoryToggles = true,

    // Layout: BANNER, POPUP, FULLSCREEN, BOTTOM_SHEET
    layout = ConsentLayout.POPUP,

    // Button order (omit to hide)
    buttons = listOf(
        ConsentButton.SAVE_CHOICES,
        ConsentButton.ACCEPT_ALL,
        ConsentButton.REJECT_ALL
    ),

    // Theme
    theme = ConsentTheme(
        primaryColor = 0xFF6200EE,
        backgroundColor = 0xFFFFFFFF,
        textColor = 0xFF333333,
        titleColor = 0xFF111111,
        buttonTextColor = 0xFFFFFFFF,
        secondaryButtonTextColor = 0xFF6200EE,
        cornerRadius = 16f,
        overlayColor = 0x99000000
    ),

    // Custom categories
    categories = listOf(
        ConsentCategory(id = 1, name = "Essential", description = "...", isRequired = true),
        ConsentCategory(id = 2, name = "Performance", description = "..."),
        ConsentCategory(id = 3, name = "Targeting", description = "..."),
        ConsentCategory(id = 4, name = "Social", description = "...")
    )
))

ConsentConfig

Property Type Default Description
categories List<ConsentCategory> 4 defaults Consent categories shown to the user
dialogTitle String "Privacy Settings" Heading text
dialogMessage String "We use cookies..." Body text
acceptAllLabel String "Accept All" Accept button text
rejectAllLabel String "Reject All" Reject button text
saveChoicesLabel String "Save Choices" Save toggles button text
manageLabel String "Manage Preferences" Expand toggles link text
showCategoryToggles Boolean true Show per-category toggles
requireConsent Boolean false Block dismiss without choosing
buttons List<ConsentButton> All 4 Which buttons, in what order
layout ConsentLayout POPUP Dialog presentation style
theme ConsentTheme Default Visual customization

ConsentCategory

Property Type Description
id Int Unique identifier for hasConsent(id)
name String Display name in the dialog
description String Explanation shown to the user
isRequired Boolean If true, toggle is always on and disabled

ConsentTheme

Property Default Description
primaryColor 0xFF1976D2 Buttons, toggles, active elements
backgroundColor 0xFFFFFFFF Dialog background
textColor 0xFF212121 Body text
titleColor 0xFF212121 Heading text
buttonTextColor 0xFFFFFFFF Primary button label
secondaryButtonTextColor 0xFF1976D2 Outlined button text
cornerRadius 12f Corner radius (dp)
overlayColor 0x80000000 Background dim overlay

ConsentButton

Value Style Description
ACCEPT_ALL Primary filled Accept all categories
REJECT_ALL Outlined Reject all optional categories
SAVE_CHOICES Primary filled Save individual toggle selections
MANAGE Text link Expand to show category toggles

Order in the list = order on screen. Omit a button to hide it.

ConsentLayout

Value Description
BANNER Compact bottom bar. Expands on "Manage".
POPUP Centered modal dialog (default).
FULLSCREEN Full-screen overlay. Maximum attention.
BOTTOM_SHEET Draggable sheet (~85% height). Scrollable.

API Reference

All methods are static on DuckCMP.

Method Returns Description
initialize(context, config?) void Initialize SDK. Call once at app start.
showConsentDialog(activity) void Show dialog with configured layout.
getConsentStatus() ConsentStatus UNKNOWN, ACCEPTED, REJECTED, or PARTIAL
getConsentResult() ConsentResult? Full consent snapshot with per-category details
hasConsent(categoryId) Boolean Check if a category is consented
shouldShowDialog() Boolean true if no consent stored yet
saveConsent(result) void Programmatically save consent
reset() void Clear all stored consent
getConfig() ConsentConfig Get current configuration
updateConfig(config) void Update config at runtime
addCallback(callback) void Register for consent events
removeCallback(callback) void Unregister a listener
attachToWebView(webView) void Inject __duckcmp JS bridge
detachFromWebView(webView) void Remove JS bridge

ConsentCallback

interface ConsentCallback {
    fun onConsentGiven(result: ConsentResult)  // user accepted/rejected/saved
    fun onConsentRevoked()                      // reset() called
    fun onDialogDismissed()                     // dialog closed
}

ConsentStatus

Value Meaning
UNKNOWN No consent stored yet
ACCEPTED All categories consented
REJECTED All optional categories rejected
PARTIAL Some accepted, some rejected

WebView Bridge

For hybrid apps, inject the __duckcmp JavaScript interface:

// Android
DuckCMP.attachToWebView(webView)
// iOS
DuckCMP.attachToWebView(webView)

Then in your web content:

if (window.__duckcmp) {
    // Read
    const status = window.__duckcmp.getStatus();        // "ACCEPTED"
    const hasAds = window.__duckcmp.hasConsent(3);       // true/false
    const full = JSON.parse(window.__duckcmp.getConsent());

    // Write
    window.__duckcmp.setConsent(JSON.stringify({
        categories: [
            { id: 1, consent: true },
            { id: 2, consent: true },
            { id: 3, consent: false },
            { id: 4, consent: true }
        ]
    }));

    // Show native dialog from JS
    window.__duckcmp.showConsentDialog();

    // Listen for native changes
    window.__duckcmp.onConsentChanged = function(state) {
        console.log("Consent updated:", state);
    };
}

How it works

Platform Mechanism
Android @JavascriptInterface via addJavascriptInterface(). Synchronous reads.
iOS WKScriptMessageHandler + JS polyfill. Reads from pre-populated _state.

Native consent changes are automatically pushed to all attached WebViews. WebView changes persist to native storage and trigger callbacks.

Storage

Consent is persisted using platform-native storage:

Platform Backend
Android SharedPreferences (named "duckcmp")
iOS UserDefaults.standard

Keys

duckcmp_result          // Full JSON consent result
duckcmp_timestamp       // Unix timestamp of last consent
duckcmp_status          // "ACCEPTED" | "REJECTED" | "PARTIAL" | "UNKNOWN"
duckcmp_category_{id}   // Per-category boolean (e.g. duckcmp_category_2 = true)

Per-category keys let you check consent from anywhere without initializing the SDK — useful in background services, content providers, or broadcast receivers.

Project Structure

open_source_cmp_sdk/
├── android/
│   ├── duck-cmp/              # Library module (AAR)
│   │   └── src/main/kotlin/com/analyticsdebugger/cmp/
│   │       ├── DuckCMP.kt              # Public API singleton
│   │       ├── ConsentCallback.kt
│   │       ├── model/
│   │       │   ├── ConsentConfig.kt
│   │       │   ├── ConsentCategory.kt
│   │       │   ├── ConsentTheme.kt
│   │       │   ├── ConsentLayout.kt
│   │       │   ├── ConsentButton.kt
│   │       │   ├── ConsentResult.kt
│   │       │   └── ConsentStatus.kt
│   │       ├── storage/
│   │       │   ├── ConsentStorage.kt
│   │       │   └── StorageKeys.kt
│   │       ├── ui/
│   │       │   ├── ConsentDialog.kt
│   │       │   └── ConsentDialogActivity.kt
│   │       └── webview/
│   │           └── ConsentWebViewBridge.kt
│   └── sample-app/
├── ios/
│   ├── DuckCMP/               # Swift Package
│   │   └── Sources/DuckCMP/
│   │       ├── DuckCMP.swift
│   │       ├── ConsentConfig.swift
│   │       ├── ConsentCategory.swift
│   │       ├── ConsentTheme.swift
│   │       ├── ConsentLayout.swift
│   │       ├── ConsentResult.swift
│   │       ├── ConsentStatus.swift
│   │       ├── ConsentStorage.swift
│   │       ├── StorageKeys.swift
│   │       ├── UI/
│   │       │   └── ConsentDialogView.swift
│   │       └── WebView/
│   │           └── ConsentWebViewBridge.swift
│   └── SampleApp/
├── public_site/               # Documentation website
├── LICENSE
└── README.md

Requirements

Platform Minimum
Android API 24 (Android 7.0)
iOS iOS 15.0
Kotlin 1.9+
Swift 5.9+
JDK 17

License

MIT License. See LICENSE.

About

Open-Source Consent Management for Apps

Resources

License

Stars

Watchers

Forks

Packages