Skip to content

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. InitializeDuckCMP.initialize(context)
// 3. Show dialogif (DuckCMP.shouldShowDialog()) {
DuckCMP.showConsentDialog(activity)
}
// 4. Check consentval hasAnalytics =DuckCMP.hasConsent(2) // category ID 2 = Analyticsval 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
lethasAnalytics=DuckCMP.hasConsent(categoryId:2)letstatus=DuckCMP.getConsentStatus()

Default Categories

IDCategoryDescriptionRequired
1NecessaryEssential for the app to functionYes (always on)
2AnalyticsHelp us understand how you use the appNo
3MarketingUsed to deliver personalized adsNo
4PreferencesRemember your settings and choicesNo

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

PropertyTypeDefaultDescription
categoriesList<ConsentCategory>4 defaultsConsent categories shown to the user
dialogTitleString"Privacy Settings"Heading text
dialogMessageString"We use cookies..."Body text
acceptAllLabelString"Accept All"Accept button text
rejectAllLabelString"Reject All"Reject button text
saveChoicesLabelString"Save Choices"Save toggles button text
manageLabelString"Manage Preferences"Expand toggles link text
showCategoryTogglesBooleantrueShow per-category toggles
requireConsentBooleanfalseBlock dismiss without choosing
buttonsList<ConsentButton>All 4Which buttons, in what order
layoutConsentLayoutPOPUPDialog presentation style
themeConsentThemeDefaultVisual customization

ConsentCategory

PropertyTypeDescription
idIntUnique identifier for hasConsent(id)
nameStringDisplay name in the dialog
descriptionStringExplanation shown to the user
isRequiredBooleanIf true, toggle is always on and disabled

ConsentTheme

PropertyDefaultDescription
primaryColor0xFF1976D2Buttons, toggles, active elements
backgroundColor0xFFFFFFFFDialog background
textColor0xFF212121Body text
titleColor0xFF212121Heading text
buttonTextColor0xFFFFFFFFPrimary button label
secondaryButtonTextColor0xFF1976D2Outlined button text
cornerRadius12fCorner radius (dp)
overlayColor0x80000000Background dim overlay

ConsentButton

ValueStyleDescription
ACCEPT_ALLPrimary filledAccept all categories
REJECT_ALLOutlinedReject all optional categories
SAVE_CHOICESPrimary filledSave individual toggle selections
MANAGEText linkExpand to show category toggles

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

ConsentLayout

ValueDescription
BANNERCompact bottom bar. Expands on "Manage".
POPUPCentered modal dialog (default).
FULLSCREENFull-screen overlay. Maximum attention.
BOTTOM_SHEETDraggable sheet (~85% height). Scrollable.

API Reference

All methods are static on DuckCMP.

MethodReturnsDescription
initialize(context, config?)voidInitialize SDK. Call once at app start.
showConsentDialog(activity)voidShow dialog with configured layout.
getConsentStatus()ConsentStatusUNKNOWN, ACCEPTED, REJECTED, or PARTIAL
getConsentResult()ConsentResult?Full consent snapshot with per-category details
hasConsent(categoryId)BooleanCheck if a category is consented
shouldShowDialog()Booleantrue if no consent stored yet
saveConsent(result)voidProgrammatically save consent
reset()voidClear all stored consent
getConfig()ConsentConfigGet current configuration
updateConfig(config)voidUpdate config at runtime
addCallback(callback)voidRegister for consent events
removeCallback(callback)voidUnregister a listener
attachToWebView(webView)voidInject __duckcmp JS bridge
detachFromWebView(webView)voidRemove JS bridge

ConsentCallback

interfaceConsentCallback {
funonConsentGiven(result:ConsentResult) // user accepted/rejected/saved
funonConsentRevoked() // reset() called
funonDialogDismissed() // dialog closed
}

ConsentStatus

ValueMeaning
UNKNOWNNo consent stored yet
ACCEPTEDAll categories consented
REJECTEDAll optional categories rejected
PARTIALSome accepted, some rejected

WebView Bridge

For hybrid apps, inject the __duckcmp JavaScript interface:

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

Then in your web content:

if(window.__duckcmp){// Readconststatus=window.__duckcmp.getStatus();// "ACCEPTED"consthasAds=window.__duckcmp.hasConsent(3);// true/falseconstfull=JSON.parse(window.__duckcmp.getConsent());// Writewindow.__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 JSwindow.__duckcmp.showConsentDialog();// Listen for native changeswindow.__duckcmp.onConsentChanged=function(state){console.log("Consent updated:",state);};}

How it works

PlatformMechanism
Android@JavascriptInterface via addJavascriptInterface(). Synchronous reads.
iOSWKScriptMessageHandler + 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:

PlatformBackend
AndroidSharedPreferences (named "duckcmp")
iOSUserDefaults.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

PlatformMinimum
AndroidAPI 24 (Android 7.0)
iOSiOS 15.0
Kotlin1.9+
Swift5.9+
JDK17

License

MIT License. See LICENSE.

About

Open-Source Consent Management for Apps

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages