The most recent production version is 9.2.7
.
Please do not use this SDK in production without:
- being subscribed to #pulse-support (Slack)
- being subscribed to [email protected] (email)
- being able to access Pulse Console
- having a provider ID registered in Pulse Console
You can find Javadoc at https://pages.github.schibsted.io/spt-dataanalytics/event-sdk-android/javadoc/index.html
Table of Contents
- Getting Started
- API Overview
- Internal Packages
- Workflow
- Fetching Remote Configuration
- Changing Organization Name
- Allow Tracking
- Update User Details
- Android Advertising ID
- Location Tracking
- Consents
- Posting Events
- Sessions
- Xandr Ad Identifiers PPID
- Advertising Identifiers including PPID
- Pulse Hybrid Mode
- Optional "Common_Transforms" Dependency
- Observing Environment ID, JWE Token, etc.
- Debugging
- Advanced Testing Features
- Testing Tools
- Notes for SDKs that Depend on PulseTracker
- Notes about Multi-processing
- Common Mistakes
- Migration from 7.5.1 to 7.6.0
- Contact
- Other
If your are using Pulse for the first time, we recommend reading the Introduction to Pulse.
If you are looking to integrate with Pulse and start capturing data, see our Integration Guide
All events must comply with event definitions that can be found in event-formats
repository.
These definitions were developed because there are many web and mobile applications in Schibsted
that send usage reports to data stakeholders (analysts, data scientists, etc). Having a common
event format lets them gather and process this data easier.
For example, imagine we have two apps: "A" and "B". Both apps want to send an event when user signs in. The app "A" sends:
"event":"sign-in"
"account":"[email protected]"
The app "B" sends:
"action":"log-in"
"actor":"[email protected]"
As you can see, though the meaning of these messages is the same, their form is different. This can be a huge problem if you want to share events with other entities within Schibsted, especially if they have many more fields and not 2 but 20 apps that send them.
event-formats
repository is
a joint effort to standardise event formats and facilitate easy exchange of data between
stakeholders. Event and object definitions there are described using json schema.
It is very important that you understand what those definitions are and how to build events according to the schemas.
There is also event-formats-jvm
repository.
It is supposed to be JVM implementation of those definitions. Unfortunately, as of today it has
some problems that prevent developers from using it fully. We have plans to refactor and improve
it. Until then, however, using event-formats-jvm
is discouraged.
Since version 7, you can use this SDK without a dependency on event-formats-jvm
.
This SDK is designed to work in multithreaded environment. Internally it uses 2 threads.
The SDK is released on Schibsted artifactory and Sonatype.
To use Schibsted artifactory, you need to include this repository with its credentials (you might already have it):
val artifactoryContext = properties["artifactory_context"]?.toString() ?: System.getenv("ARTIFACTORY_CONTEXT")
maven {
url = uri("$artifactoryContext/libs-release")
credentials {
username = properties["artifactory_user"]?.toString() ?: System.getenv("ARTIFACTORY_USER")
password = properties["artifactory_pwd"]?.toString() ?: System.getenv("ARTIFACTORY_PWD")
}
}
You can either set the credentials as environment variables:
export [email protected]
export ARTIFACTORY_PWD=<artifactory-password>
export ARTIFACTORY_CONTEXT=https://artifacts.schibsted.io/artifactory
Or as gradle properties, suggested at ~/.gradle/gradle.properties
:
[email protected]
artifactory_pwd=<artifactory-password>
artifactory_context=https://artifacts.schibsted.io/artifactory
Sonatype artifact is available from mavenCentral()
repository.
Once Schibsted artifactory is set up, add the following line to your build.gradle
:
implementation("com.schibsted.spt.tracking:event-sdk-android:<version>")
Since version 7 this SDK doesn't require apps to use event-formats-jvm
. All objects and events
(defined in event-formats
repository) can be built without it. If you still want to use it,
you can add the following line to your build.gradle
:
implementation("com.schibsted:event-formats-jvm:<version>")
The SDK uses Google Play services to fetch advertising ID and location. Currently it is build
with version 18.0.1 AdIdentifier and 21.0.1 Location. The SDK doesn't contain any version-specific code. If you use a newer
version of Google Play services, Gradle should be able to automatically upgrade this dependency.
If you want use an older version of Google Play Services, you might want to exclude
it from the SDK.
In this case make sure that both play-services-ads
and play-services-location
are explicitly
included in your build.gradle
:
implementation("com.schibsted.spt.tracking:event-sdk-android:<version>") {
exclude(group = "com.google.android.gms")
}
implementation("com.google.android.gms:play-services-ads:<your-gp-services-version>")
implementation("com.google.android.gms:play-services-location:<your-gp-services-version>")
Since version 7 you can provide custom AdvertisingIdProvider
and LocationProvider
.
This way you can have finer control over when and how to fetch these values, and even use
this SDK on devices without Google Play services at all:
AdvertisingIdProvider customAdvertisingIdProvider = ...;
LocationProvider customLocationProvider = ...;
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
.advertisingIdProvider(customAdvertisingIdProvider)
.locationProvider(customLocationProvider)
... // other stuff
.build();
// Now you may exclude "com.google.android.gms" from dependencies
This SDK uses Gson
to build and modify events.
Room
architecture component
is used for database operations. Note, Room v1.1.0
has a huge bug that renders database
unusable after migrations.
DO NOT USE Room v1.1.0
! At least 1.1.1-rc1
or 1.1.1
is absolutely required
(these versions are identical).
LiveData
architecture
component is used to expose environment ID
, JWE token
and other details your app might need.
This SDK uses the popular Retrofit 2
,
OkHttp3
and Okio
for networking.
This SDK will NEVER request any permissions directly. Client applications must declare them in their manifest and ask user to grant them in runtime.
Some permissions are vital for this SDK. They are:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
They are obviously required for networking.
Some actions, like fetching device's location, require additional permissions. If they are denied, the respective features will be disabled (for example, location details will not be included in events). Very likely, you will want to add them to the Manifest:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
The library (in the aar format) includes consumerProguardFiles
which means
that you do not need to set Proguard settings for this library manually.
Our public API consists of these classes/interfaces:
- PulseEnvironment (javadoc) provides details about device, OS, tracker itself, client application, location, advertising ID, etc. Basically, it is a description of environment this SDK runs in.
- GlobalPulseTracker (javadoc) contains methods that affect all tracker instances immediately: reset environment ID, enable or disable tracking, set user ID. etc.
- PulseTracker (javadoc) contains the core API that builds and posts events.
- PulseTrackerFactory (javadoc)
creates instances of
PulseTracker
using providedPulseEnvironment
. - Transform (javadoc)
is the core abstraction for manipulating an event: you can add, change or remove properties
of an event. By calling
PulseTracker.update(Transform)
you create new instance ofPulseTracker
that will apply this transform to every event it tracks. You don't have to use transforms; you can use useGson
to create your events instead.
We consider example app
a part of
documentation. It contains multiple classes that demonstrate how to create PulseEnvironment
,
initialize PulseTracker
, manipulate and post events. It has many comments that explain
almost every line.
We tried to separate internal packages (com.schibsted.pulse.tracker.internal
) from public API.
Unfortunately, at the moment it is impossible to exclude external dependencies from compile
classpath while leaving them in the runtime classpath. Until version 4.6 Gradle
doesn't respect compile-runtime separation for JARs,
and for AARs it won't be respected at least till version 5.0.
Please, don't ever use them.
The whole process of setting up and using this SDK is following:
-
Create an instance of
PulseEnvironment
usingPulseEnvironment.Builder
. Very likely, all you need for production builds is:PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, appClientId) .build();
For development builds, you will probably want to enable debug features, such as HTTP logging, faster dispatches,
dev
deploy stage, etc. Please, study PulseTrackerProvider class in the example app to see how to configurePulseEnvironment
.The SDK also enables you to use different environments to send events and CIS requests. While developing you dont want to bloat production environments with not-real user data. To do so simply pass environment name as in example ChangeEnvironmentExample
builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "development"); builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "production"); builder.setConfigurationOption(ConfigurationOptions.CONFIGURATION_NAME, "preproduction");
We strongly suggest to create it in
Application.onCreate()
. OncePulseEnvironment
is created, DON'T EVER ATTEMPT to create a differentPulseEnvironment
! You MUST NEVER use differentPulseEnvironment
instances in the same app. -
Create an instance of
PulseTracker
usingPulseTrackerFactory
:PulseTracker rootTracker = PulseTrackerFactory.create(pulseEnvironment);
Since version 7 you don't have to keep it as singleton. You can have as many instances as you want.
-
Now, configure the global state in
GlobalPulseTracker
:GlobalPulseTracker global = rootTracker.global(); global.enableTracking(true); global.setUserAnonymous(); // etc.
-
Now that you have your root tracker instance, you can create child trackers with their associated
Transforms
.Transforms
add, change or remove properties from event'sJsonObject
. You can add aTransform
to aPulseTracker
once, and then use thisPulseTracker
to send multiple events. TheTransform
will be applied on each event posted to thisPulseTracker
instance.It is very likely that you will want to have a number of properties added to every event you send. Please, take a look at TrackerEventProperties in our example app. It does exactly that: adds
deployStage
,deployTag
,provider
,@id
,actor
,schema
,creationDate
,device
,tracker
,location
andsession
to each posted event.The output of
Transforms
is a regularJsonObject
with key-value pairs in it. -
Finally, you post events. You can build an event using
Transforms
only, or usingJsonObject
alone, or you can combine them both. To post an event, callPulseTracker.track(JsonObject)
method. If you passJsonObject
into it, its properties will be copied into theJsonObject
built byTransforms
. Please, note the sequence: firstTransforms
add their properties, then properties from yourJsonObject
are copied on top of them. Prior to version 7 the sequence was opposite.Please, take a look at LaunchEventSender in the example app to see how we send a simple "Launch" event. There are also multiple other examples there that teach different ways of constructing events with
Transforms
andGson
.
When the PulseTracker
instance is created, the first thing it does is fetching a configuration file
from a remote server. This configuration file contains up-to-date URLs for CIS and DataCollector.
The location of the file is https://sdkconfig.pulse.schibsted.com/schibsted_default/config/
.
NB! At the time of writing, any values other than schibsted
are not supported by the schema.
This feature exists to make this SDK "future-proof".
Organization name is used to create SDRN-formatted identifiers (you can see them in every event).
For example, it's schibsted
in sdrn:schibsted:client:<some-client-id>
. Your app can
specify different organization name:
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, appClientId)
.organization("adevinta") // default value is "schibsted"
.build();
Now, your SDRN will look like sdrn:adevinta:client:<some-client-id>
.
Applications must explicitly allow or prohibit tracking. If your app posts events before tracking is enabled, these events will be deleted without dispatching. The same goes for events that are posted when tracking is prohibited:
tracker.global().enableTracking(true);
Even if tracking is prohibited, we still communicate with CIS servers in order to fetch environment ID and JWE token. They are important not only for this SDK but for other components at Schibsted. CIS servers are stateless and don't store any data.
A complete user ID consists of two parts: login system and unique ID within this system.
To combine them into SDRN-formatted string, you can use Helpers.
When application starts or when user signs in/out, apps are expected to notify
GlobalPulseTracker
about it:
tracker.global().setUserIdentified("<sdrn-formatted-user-id>");
// or
tracker.global().setUserAnonymous();
These calls will eventually lead to network calls to CIS. CIS will generate JWE token that will be used when dispatching events to the DataCollector.
The Android Advertising ID provided by Google Play services acts as unique identifier of a device. It is reported to CIS and is used to generate environment ID. This SDK comes with a built-in advertising ID provider that fetches the value from Advertising ID Client from Google Play services.
It is possible to set a custom AdvertisingIdProvider
.
You might want to do that if your app can be installed on devices without Google Play services.
Set your custom implementation using PulseEnvironment.Builder
(see
Google Play services section of this README).
Important! Google Play store has special requirements for apps that use advertising ID. Your app must specify a Privacy Policy, in particular. Please, carefully review the current rules. You can start in Play Console Help.
Your events may include Location
. This SDK comes with a built-in location provider that uses
Fused Location Provider API
from Google Play Services.
Google Play Store will detect this background location code and require you to declare it. When removing the Google Play Services dependency, the location code will not be invoked. Reach out to the Pulse team in #pulse-support (Slack) for any questions and feature requests relating to Location.
It is possible to set a custom LocationProvider
.
You might want to do that if your app can be installed on devices without Google Play services.
Set your custom implementation using PulseEnvironment.Builder
(see
Google Play services section of this README) and attach it to
the posted events, for example, using Transforms
(see TrackerEventProperties).
Only three digits detail after the decimal point is kept of the mentioned coordinates, for privacy reasons.
The Pulse SDK supports adding consent information to all Pulse events.
tracker.global().setConsents(consents)
adds the user's consents (opt-ins and opt-outs) to data processing for specific purposes, e.g. personalization.
These consent purposes are used to enrich Pulse events with the consent choices made by the current user.
This is legally required in some markets.
You must at all times provide valid consents. If the user modifies their consents you must pass the updated values to this SDK, using this method.
tracker.global().setConsents(consents);
Should the values be unknown or (temporarily) unavailable to your app, pass ConsentStatus.UNKNOWN
, if your app is required to always provide consent information on events.
In case consents can be modified externally to the app, e.g. on a web page: do make sure that when the app comes into foreground, you request up-to-date information on consents from the Consent Management Platform, e.g SourcePoint, if not done automatically, and pass on the consents to this SDK (always).
A consent purpose consists of a category and an id, where category is a textual representation of a consent purpose.
The purpose categories are pre-defined.
The id
is optional and can be used to identify the consent purpose from the Consent Management Platform (CMP).
Use the optional ConsentSource
parameter to specify if the values are defaults or cached. Possible values are CMP
,CACHED
,DEFAULT
see ConsentSource
The code example below shows a valid Consent object
passed through the tracker.global().setConsents(consents)
method.
Consents consents = (new Consents(
new ConsentPurposes(
new ConsentPurpose(ConsentStatus.ACCEPTED, "1"), // CMP_ANALYTICS
new ConsentPurpose(ConsentStatus.ACCEPTED), // CMP_MARKETING
new ConsentPurpose(ConsentStatus.ACCEPTED), // CMP_ADVERTISING
new ConsentPurpose(ConsentStatus.REJECTED) // CMP_PERSONALIZATION
),
ConsentSource.CACHE
));
tracker.global().setConsents(consents);
See how to set and use the Consents methods in the example code.
WARNING: Be aware that the SDK is not checking the validity of the data you provide through the tracker.global().setConsents()
method.
You are responsible for verifying that the data you provide through this interface is legally obtained and otherwise valid, this SDK will only transport the consent values, not validate them.
If the legislation for your area requires you to have an opt-in to data processing for advertising purposes from the end-user before performing targeted advertising,
enable the AdvertisingOptIn
mode by calling
PulseEnvironment.Builder.requireAdvertisingOptIn(true);
when configuring the Pulse SDK in your app.
This SDK will then not provide ad identifiers through its API, and not store ad identifiers on the device.
When opening web pages coming from Schibsted organisation (using Hybrid Mode) it is expected that the application uses the latest of user consents. It is important in order to avoid privacy issues and having always the content accurate to users decision. That is why, while implementing WebView you should always make sure that:
- You are applying official SourcePoint documentation for synchronising consents.
- You are always attaching consent changes into the all the opened WebViews by:
- Using available SourcePoint methods whenever possible.
- Recreating the Webview object when consents change, if you are unable to update the Webview objects in any other way.
When you track an event using PulseTracker.track(JsonObject)
, the JsonObject
is first modified
by adding the Consent
key+values if a ConsentProvider
is set and it returns data.
After that it will be constructed by Transforms
. Then it's merged with the JsonObject
that you passed into PulseTracker.track(JsonObject)
method. It is then serialized
into a json string and saved into a local database.
Then another component periodically fetches new events from the database, updates their "special"
values (like environment ID, JWE token, user ID, event ID, etc.) and dispatches them
to the DataCollector
. We try to batch events together to save network calls, when possible.
Since 7.3.0 we also compress these batches with gzip if size of a batch exceeds 800 bytes.
In production mode events are dispatched every 2 minutes. If network is not available, events will be dispatched when connection is re-established, or saved till the next session. When an event is successfully dispatched, it is deleted from database.
This SDK keeps maximum 1_000 events in database.
The DataCollector
API will not accept POST requests larger then 100_000 bytes. For this reason
this SDK will silently discard events that are longer then 100_000 bytes when serialized into
json string.
Schema version 228 added new property to tracker events - "session". The purpose is to track a continuous period of user activity. Each tracker event within the session window should hold a reference to that session. To add sessions to your events, just set the "session" property to placeholder. Pulse Tracker will populate it with a proper session object before saving the event to database:
JsonObject event = ...;
event.addProperty("session", com.schibsted.pulse.tracker.JsonObjectFactories.PLACEHOLDER);
We strongly recommend to upgrade schema for all your tracker-events
to at least:
schema 228
"http://schema.schibsted.com/events/tracker-event.json/228.json" and your engagement-events
to
schema 254
"http://schema.schibsted.com/events/engagement-event.json/254.json".
Lower versions don't support sessions.
DEPRECATED - will be removed soon!
For observing Xandr Ad Identifiers (PPID) values (PPID1, PPID2, AdId) you should use getXandrAdIdentifiersLiveData()
in PulseEnvironment via observing
their LiveData
representation.
tracker.getPulseEnvironment().getXandrAdIdentifiersLiveData();
To start observing Advertising Identifiers values (PPID1,PPID2, AdId) you should specify advertising vendors you want to request.
builder.advertisingVendors(List.of(AdvertisingVendors.XANDR, AdvertisingVendors.ADFORM, AdvertisingVendors.DELTA));
For observing Advertising Identifiers you should use getAdvertisingIdentifiersLiveData()
in PulseEnvironment via observing
their LiveData<AdvertisingIdentifiers>
representation.
tracker.getPulseEnvironment().getAdvertisingIdentifiersLiveData();
If your application opens any site tracked by the Pulse Web SDK inside WebView, you should integrate pulse tracker hybrid mode with your WebView so you can natively track events and utilise native JWE Token.
PulseHybridWebController webController = new PulseHybridWebController();
tracker.setupHybridMode(webView,
webController,
true,
json -> trackEventFromWebView(json);
);
Since version 7.6.0 we publish a new optional artifact:
implementation("com.schibsted.spt.tracking:common_transforms:<version>")
So far, it contains only one class:
TrackerEventProperties.
Essentially, it is a drop-in replacement for CommonProperties
class from our example app
that you no longer need to write yourselves. If added to your tracker instance, it will add
a number of most common properties to all tracked events: "@id", "schema", "deployStage",
"deployTag", "provider", "actor", "creationDate", "device", "tracker", "location" and "session".
Of course, you can overwrite these values, as usual.
To use TrackerEventProperties
, just create its instance and update your tracker:
Transform trackerEventProperties = new TrackerEventProperties();
PulseTracker trackerWithTransform = tracker.update(trackerEventProperties);
trackerWithTransform
will now be able to automatically add the common properties to
all events it tracks.
You can observe environment ID, JWE token and a few other changing properties using
LiveData
objects exposed from PulseEnvironment
. You must remember that both environment ID and JWE token
are assigned by CIS server and require a successful network call. Network is not always
available on mobile devices. When environment ID is reset it might take significant time
until we actually obtain the new value from the server and post it to LiveData
. The same goes
for JWE token. Its value changes every time environment ID is reset or when user signs in/out.
It might take long time until LiveData
is updated with the correct values.
You can see how this work in the example app. Start the application, note environment ID, JWE token, etc, turn off internet and press "Reset Environment ID". See that the displayed values don't change. Turn internet back on, and see when they are updated.
This SDK can correctly work in these constraints and use accurate values when dispatching events. But there is no guarantee that third-party SDKs can handle it properly.
This SDK provides a number features for development and debugging purposes. The previous versions
used PulseEnvironment.Builder.debug(boolean)
flag to enable them all at once. Version 7.1.0
introduced more modular controls that will allow developers to tailor SDK's behavior
to their needs: so called configuration options
.
We plan to increase the number of available configuration options in future releases.
You can see how to use them in our example app.
The following code will emulate the behavior of now deprecated PulseEnvironment.Builder.debug(boolean)
flag:
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
.setConfigurationOption(ConfigurationOptions.INTERVAL_BETWEEN_DISPATCHES,
TimeUnit.SECONDS.toMillis(10))
... // other stuff
.build();
Since version 7.5.1 we have changed the recommended way to log HTTP communication. Instead of
setting a configuration option (the old way), initialize and configure your own instance of
HttpLoggingInterceptor, and add it to PulseEnvironment
, for example:
Interceptor httpLogger = new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
.addInterceptor(httpLogger)
... // other stuff
.build();
Please, note that HTTP logging requires additional dependency added to your build.gradle
:
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
In development or testing builds you might also want to add deployStage
and deployTag
properties to your events. deployStage
lets you separate development events from the production
ones, and deployTag
adds additional possibilities for filtering and examining events in
Pulse Console.
To set deploy stage and tag, first add them to PulseEnvironment
:
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
.deployStage("dev") // or "pro", or "pre"
.deployTag("<some-custom-tag>")
... // other stuff
.build();
Note, setting deployStage
and deployTag
in PulseEnvironment
alone won't add deployStage
and deployTag
properties to your events. You also need to fetch these values from
PulseEnvironment
and add them to your events directly. If you use common_transforms
artifact
(added in 7.6.0), it will add them for you.
You might ask why we have these methods in PulseEnvironment
anyway? Because other SDKs
at Schibsted also do their own logging. If you use those SDKs, they need to know the proper
deploy stage and tag as well, and PulseEnvironment
is the place to find them.
You can now change SDK's behavior so that can be easily tested on emulators or actual devices:
PulseEnvironment pulseEnvironment = new PulseEnvironment.Builder(appContext, yourAppClientId)
.setConfigurationOption(ConfigurationOptions.ENABLE_ADVANCED_TESTING, true)
... // other stuff
.build();
The effects are:
PulseTracker
doesn't use background threads and runs synchronously;- in-memory database is used instead of a usual on-disk database;
- all instances returned by
PulseTrackerFactory
are fully isolated from each other; - tracker does not observe connectivity changes and does not try to re-schedule failed network operations to a later time.
A few other useful ConfigurationOptions
:
CONFIGURATION_URL
,CIS_URL
andDATA_COLLECTOR_URL
override URLs specified inPulseEnvironment.Builder
or in configuration file.- By default tracker uses
default
configuration in configuration file. You can now make it choose a different one by settingCONFIGURATION_NAME
.
We have created a set of tools that simplify PulseTracker
testing and checking if your events
are valid and comply to a schema:
androidTestImplementation("com.schibsted.spt.tracking:testing:<version>")
Please, take a look at README in Testing
package to learn more. Testing tools are
available since v7.3.0.
SDKs MUST NOT create a new instance of PulseEnvironment
. Instead, request your client apps
to provide it. Alternatively, request an instance of PulseTracker
and fetch a PulseEnvironment
from it:
PulseEnvironment env = pulseTrackerProvidedByClientApp.getPulseEnvironment();
SDKs MUST NOT call methods in GlobalPulseTracker
unless you know what you do.
The reason is every call to GlobalPulseTracker
affects all PulseTracker
instances in the app.
Calling them may lead to unexpected consequences.
SDKs should suggest to client apps to create an instance of PulseTracker
and call its
global()
methods even if they do not use Pulse SDKs themselves:
pulseTracker.global().enableTracking(true);
pulseTracker.global().setUserIdentified(userId);
pulseTracker.global().setUserAnonymous();
// etc.
Otherwise events may not be dispatched to DataCollector or may contain incorrect information.
We recommend that SDKs use app's PulseEnvironment
instance to create a new PulseTracker
for their own needs. Re-using app's PulseTracker
is not advisable because it may contain
transforms that will mess up your events.
Pulse SDKs are designed to work in multi-threaded environments but multi-processing is completely
different story. Every process will initialize its own Application
and its own PulseTracker
.
But all of them will attempt to operate with the same database file. This will certainly
prevent Pulse SDK from working properly and make database inconsistent.
What are the symptoms of the problem? If you see exceptions saying that database is locked, or returned null instead of non-null, or migration failed (for example, "column X already exists"), and so on - these errors mean you spawned multiple processes that modify the same database simultaneously.
If you actually need to use multiple processes, you will have to implement a ContentProvider
that wraps Pulse SDK (or implement another kind of IPC).
Some libraries can also spawn processes "behind the scenes". One popular example is LeakCanary.
If you use LeakCanary, you have to check whether your Application
runs in analyzer process
(by calling LeakCanary.isInAnalyzerProcess(Context)
) and prevent it from initializing your app.
See LeakCanary docs.
- Attaching user ID to your event (outside of
actor
property) a HUGE mistake. It makes it hard to anonymize such data, and may lead to violation of GDPR and other laws. - Some event require lists of objects to be attached. There is no limitation on how many items such lists may hold. You must be careful though: they might quickly grow out of control. We have seen apps that attempted to send megabytes-long events because they forgot to manage list size, and added hundreds or thousands items there.
At this point sessions are optional. You can add them your events manually or by using
optional common_transforms
dependency.
You can add sessions to your events on your own by adding "session" property to your events:
event.addProperty("session", JsonObjectFactories.PLACEHOLDER);
Before saving the event to database, tracker will replace the placeholder with the actual session object.
If you decided to add sessions, you also need to your tracker-events to schema 228 and your engagement-events to schema 254.
If you decided to use optional common_transforms
dependency,
take a look at its source code.
It is very likely that it duplicates some parts of your own code.
In case of a Pulse SDK related issue we expect to keep an open communication on our Slack channels:
- 👉  #pulse-android-sdk for Android related issues
or - 👉  #pulse-support for all other general purpose requests
When, for technical reasons Slack cannot work for you, please make sure to use GitHub issues link instead. We will provide all the necessary answers within the relevant Slack threads (or in Github issue when your case is inserted there). Thank you for your cooperation!
See the tagging guides in Pulse Console/Tagging Plans