Android SDK
Build the consent jar for an Android app, create the client and look up every method, type and exception.
PiedapConsent records consent from a native Android app and keeps the answer on the device. It draws no banner: your app shows its own screen from the form the server sends.
Requirements#
- Plain Java 8 with no dependencies. Call it from Java or Kotlin.
- A JDK, version 9 or later, to build the jar.
- The
android.permission.INTERNETpermission in the app's manifest. - A publishable key,
pk_…. Its Allowed origins must listapp:followed by the application id, such asapp:in.example.kalpa. - A published form that a region rule sends your users to.
A test key answers any app. A live key answers only the apps on its list. See Mobile apps.
Install#
The SDK is not published to a public registry or served by the deployment. Your account contact provides the source. Build the jar with the script in the consent-android folder:
./build.shThe script compiles, runs the tests and writes build/piedap-consent-android-1.0.1.jar. Copy the jar into your app module's libs folder and add it to the build:
dependencies {
implementation files('libs/piedap-consent-android-1.0.1.jar')
}dependencies {
implementation(files("libs/piedap-consent-android-1.0.1.jar"))
}Create the client#
PiedapConsent.builder()#
Builds the client. Create one and keep it for the life of the app.
| Builder method | Type | Required or default | Description |
|---|---|---|---|
apiUrl() |
String |
Required | The deployment address. |
publishableKey() |
String |
Required | The publishable key, pk_…. |
applicationId() |
String |
Required | The application id. It must be the id listed on the key after app:. |
store() |
ConsentStore |
MemoryConsentStore |
Where state is kept. The default forgets everything when the app exits. |
timeoutMs() |
int |
10000 |
Connect and read timeout in milliseconds. |
Returns a PiedapConsent client from build().
Errors publishableKey() throws IllegalArgumentException for a key that does not start with pk_. build() throws IllegalStateException without an address, a key or an application id.
import com.piedap.consent.PiedapConsent;
PiedapConsent consent = PiedapConsent.builder()
.apiUrl("https://console.dpconsent.com/consent")
.publishableKey("pk_…")
.applicationId(context.getPackageName())
.store(new PreferencesStore(context))
.build();Each request carries the key in X-Piedap-Key and the application id in X-Piedap-App. Redirects are never followed. PreferencesStore is a class you add to the app. It is shown under ConsentStore.
Methods#
Network calls block, so run them off the main thread. There is no offline queue and no retry: a failed save throws, and the answer is not stored. Reading never needs the network.
loadForm()#
Fetches the form for this device.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
locale |
String |
Required | A language tag, such as hi-IN. |
countryCode |
String |
Required, may be null |
A two-letter country code. With null, the server works it out from the request. |
Returns a ConsentForm. The country you pass picks the form only. The record takes its country and law from where the request comes from.
Errors Throws ConsentApiException.
ConsentForm form = consent.loadForm(
Locale.getDefault().toLanguageTag(), null);
ConsentForm formForIndia = consent.loadForm("hi-IN", "IN");shouldAsk()#
Tells whether to show your consent screen for a form.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
form |
ConsentForm |
Required | The form from loadForm. |
Returns boolean. It is true:
- before the first decision
- when the published form has changed since the user answered, unless its
consentModelisNOTICE_ONLY - when the server asks for consent again
It is false when the form is not available.
Errors None.
if (consent.shouldAsk(form)) {
presentConsentScreen(form);
}acceptAll()#
Records that the user accepted every purpose. The record's interaction is ACCEPT_ALL.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
form |
ConsentForm |
Required | The form the user answered. |
Returns Map<String, Boolean>, the stored decisions by purpose key.
Errors Throws ConsentApiException. Throws IllegalStateException for a form that is not available.
consent.acceptAll(form);rejectAll()#
Records that the user refused every optional purpose. The record's interaction is REJECT_ALL.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
form |
ConsentForm |
Required | The form the user answered. |
Returns Map<String, Boolean>, the stored decisions. Always-on purposes stay allowed.
Errors Throws ConsentApiException. Throws IllegalStateException for a form that is not available.
consent.rejectAll(form);savePreferences()#
Records the user's own choices. The record's interaction is SAVE_PREFERENCES.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
form |
ConsentForm |
Required | The form the user answered. |
choices |
Map<String, Boolean> |
Required | Purpose key to allowed. A purpose you leave out counts as declined. |
Returns Map<String, Boolean>, the stored decisions. Always-on purposes stay allowed.
Errors Throws ConsentApiException. Throws IllegalStateException for a form that is not available.
Map<String, Boolean> choices = new HashMap<>();
choices.put("analytics", true);
choices.put("marketing", false);
consent.savePreferences(form, choices);The three recording methods write records in the same way:
- The first answer creates a record. A change of mind on the same form version updates it.
- An answer to a new version, or to a request for fresh consent, creates a new record. So does a change to a record that was erased on the server.
- The record is kept under a random id made for this install, with the type
DEVICE_ID, unless you calledidentify.
granted()#
Tells whether the stored decision allows a purpose.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
purposeKey |
String |
Required | The purpose key. |
Returns boolean. It is false for every purpose until the user decides. After loadForm finds no form for the user's location, it is true for every purpose, unless a decision is already stored.
Errors None.
if (consent.granted("analytics")) {
analytics.enable();
}decisions()#
Returns the stored decisions by purpose key.
Takes no parameters.
Returns Map<String, Boolean>, which cannot be changed. It is empty until the user decides.
Errors None. A stored value that cannot be read counts as no decision.
Map<String, Boolean> decisions = consent.decisions();consentId()#
Returns the id of the stored record.
Takes no parameters.
Returns String, cns_…, or null before the first answer.
Errors None.
String consentId = consent.consentId();addListener()#
Adds a listener that is called with the decisions after every save and withdrawal.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
listener |
PiedapConsent.Listener |
Required | Its onConsentChanged(Map<String, Boolean>) runs on the thread that made the call. |
Returns nothing. The client takes several listeners.
Errors None.
consent.addListener(latest -> applyConsent(latest));removeListener()#
Removes a listener added with addListener().
| Parameter | Type | Required or default | Description |
|---|---|---|---|
listener |
PiedapConsent.Listener |
Required | The listener to remove. |
Returns nothing.
Errors None.
consent.removeListener(listener);withdraw()#
Withdraws the record on the server and clears it from the device.
Takes no parameters.
Returns nothing. The next shouldAsk is true again.
Errors Throws ConsentApiException. The stored decision then stays as it was.
consent.withdraw();identify()#
Records consent created from now on under the signed-in person instead of the device.
| Parameter | Type | Required or default | Description |
|---|---|---|---|
subjectId |
String |
Required | The person's identifier, exactly the one the proof was made for. |
subjectType |
String |
Required | USER_ID, EMAIL or PHONE. |
proof |
String |
Required | The identity proof your server computed: 64 hex characters. |
Returns nothing. The identity is kept in memory only, so call identify after sign-in on every launch.
Errors Throws IllegalArgumentException for an empty id, another type, or a proof that is not 64 hex characters.
consent.identify(session.userId, "USER_ID", session.consentProof);identify does not call the server. A proof the server cannot verify is refused when the next record is created, with status 403 and the code IDENTITY_PROOF_REQUIRED. A decision already recorded under the device id keeps being updated in its own record. Compute the proof with identityProof() or identity_proof().
clearIdentity()#
Records consent created from now on under the device again. Call it on sign-out.
Takes no parameters.
Returns nothing.
Errors None.
consent.clearIdentity();Types#
ConsentForm#
The fields are public and final. A text field the form does not have is null.
| Field | Type | Description |
|---|---|---|
available |
boolean |
false when no form is targeted at the user's location. There is nothing to ask. |
title, body |
String |
The wording of the notice, in the language asked for. |
acceptAllLabel, rejectAllLabel, savePreferencesLabel |
String |
Button wording. |
privacyPolicyUrl |
String |
The privacy policy link, if the form has one. |
purposes |
List<Purpose> |
The purposes to show. |
consentModel |
String |
OPT_IN, OPT_OUT or NOTICE_ONLY. |
law |
String |
The law that applies, such as DPDP. |
key, contentHash, locale |
String |
Which form this is, and the language of its wording. |
version |
int |
The form's version. |
reconsentRequired |
boolean |
true when the server asks for a fresh answer. |
Purpose#
| Field | Type | Description |
|---|---|---|
key |
String |
The purpose key. |
name, description, category |
String |
What to show for the purpose. |
alwaysOn |
boolean |
The purpose cannot be switched off. |
defaultGranted |
boolean |
The form's default for the purpose. |
ConsentStore#
The interface the client keeps its state through.
| Member | Description |
|---|---|
get(String key) |
Returns the stored string, or null. |
put(String key, String value) |
Stores a string. |
remove(String key) |
Removes a key. |
MemoryConsentStore |
Built in. Keeps state in memory only. |
To keep state between launches, add a store backed by SharedPreferences:
import android.content.Context;
import android.content.SharedPreferences;
import com.piedap.consent.ConsentStore;
final class PreferencesStore implements ConsentStore {
private final SharedPreferences preferences;
PreferencesStore(Context context) {
preferences = context.getSharedPreferences(
"piedap_consent", Context.MODE_PRIVATE);
}
@Override public String get(String key) {
return preferences.getString(key, null);
}
@Override public void put(String key, String value) {
preferences.edit().putString(key, value).apply();
}
@Override public void remove(String key) {
preferences.edit().remove(key).apply();
}
}The store holds a random id for the install, the consent id, the latest decisions, the form and version they answer, and whether the last fetch found a form. Its keys start with piedap.consent..
Errors#
A failed request throws ConsentApiException, a checked exception.
| Method | Type | Description |
|---|---|---|
getStatus() |
int |
The HTTP status. 0 when the server was not reached. |
getMessage() |
String |
The server's message. |
getCode() |
String |
The server's code, or null. |
Match on the status and the code, not on the message. For some errors, such as a 401, the message arrives as a list in brackets, such as [Unauthorized]. A 401 means the key was not accepted. It is wrong, revoked or turned off, or it is a live key and the application id is not in its allowed origins. Every code is listed in Errors.
A mistake the SDK finds before it makes a request throws an unchecked exception. A catch (ConsentApiException …) does not catch it.
| Mistake | Exception |
|---|---|
A key that does not start with pk_ |
IllegalArgumentException |
| No address, key or application id | IllegalStateException from build() |
identify with an empty id, another type, or a proof that is not 64 hex characters |
IllegalArgumentException |
An answer to a form that is not available |
IllegalStateException |
try {
consent.acceptAll(form);
} catch (ConsentApiException error) {
Log.w("consent", error.getStatus() + " " + error.getCode());
}Not in this SDK#
- A ready-made consent screen. Draw your own from the form.
- Choices partner by partner. The SDK answers per purpose only.
- Linked identifiers. Link them from your server with
linkIdentifiers(). - An offline queue or retries.
- React Native and Flutter wrappers. Bridge the native SDK yourself, or call the API with the key in
X-Piedap-Keyand your app id inX-Piedap-App. - The complaint link, the rights request link, the privacy contact email and the list of languages. Read
form.copy.complaintUrl,runtime.rightsUrl,runtime.contactEmailandform.localesfromGET /sdk/v1/form. - Tracker blocking, Google Consent Mode, IAB TCF and GPP. Gate your own SDKs on
granted.