Gradle plugin which collects all analytics screen names, events and user properties for Android projects.
This plugin has been written in Kotlin but works both inside Kotlin and Java projects.
IMPORTANT: Collar does NOT send out analytics data to remote services. This is left for the developer to solve in their own codebase, with Collar being simply a reflection of the current state of analytics data.
The project is organized in the following modules:
annotations
- contains all annotations necessary for the plugin to workcore
- plugin implementation core, depends on theannotations
moduleprocessor
- annotation processor generating and transforming classes for plugin, depends on theannotations
moduleplugin
- the Gradle plugin that adds all necessary dependencies to the projectui
- contains a single screen UI that provides visual tracking of sent eventsui-no-op
- contains a stub for easy release implementation of UI packagegenerator
- contains a generator code for provided tracking planlint
- contains custom Lint checkssample
- a sample app for testing the Gradle plugin
To include plugin to your project, you have to add buildscript dependencies in your project level build.gradle
or build.gradle.kts
:
Groovy
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.infinum.collar:collar-plugin:1.4.0"
}
}
KotlinDSL
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.infinum.collar:collar-plugin:1.4.0")
}
}
Then apply the plugin in your app build.gradle
or build.gradle.kts
:
Groovy
apply plugin: "com.infinum.collar.plugin"
KotlinDSL
plugins {
...
id("com.infinum.collar.plugin")
}
Now you can sync your project.
Create or inject an instance of Collar in your Application class and attach a Collector:
Collar.attach(object : Collector {
override fun onScreen(screen: Screen) =
analyticsProvider.sendScreenName(screenName = screen.name)
override fun onEvent(event: Event) =
analyticsProvider.sendEvent(eventName = event.name, eventParameters = event.params ?: mapOf<String, *>())
override fun onProperty(property: Property) =
analyticsProvider.sendProperty(property.name, property.value)
})
analyticsProvider is your own implementation of an analytics delegate class.
Screen names can be annotated on top of Activities or Fragments. No other views are eligible as screen name destination holders.
@ScreenName(AnalyticsKeys.ScreenName.BRAND_DETAILS)
class BrandFragment : Fragment(R.layout.fragment_brand) {
...
override fun onResume() = super.onResume().run { trackScreen() }
}
Actual screen name annotation values are maintained and provided per project implementing this plugin.
If a screen name annotation value is not provided then class name will be used as annotation value.
A convenience extension method will be generated by annotation processor trackScreen which must be called explicitly and preferably in onResume lifecycle callback.
However, you can always use Collar methods explicitly besides the extension one to track your screen names.
Collar.trackScreen(screen)
Create a model container sealed class with arbitrary name where you see fit in your project and annotate as illustrated below.
Nested classes represent the events you want to track.
Class name will be processed into lowercase snake_case appropriate for most analytics dependencies, as well as the containing variable names.
Variable values will be propagated as event parameter values.
You can override event name for specific nested class then use EventName annotation with a respective new event name.
Accordingly, you can override event parameter name using EventParameterName annotation too.
@AnalyticsEvents
sealed class AnalyticsEvent {
data class LanguageSelection(
val selectedLanguage: String,
val exhibitorCount: Int
) : AnalyticsEvent()
class Login : AnalyticsEvent()
@EventName("login_guest")
class Guest : AnalyticsEvent()
data class ShortcutLink(
@EventParameterName("URL")
val url: String
) : AnalyticsEvent()
}
You can have multiple annotated sealed classes declared like this through your project.
A convenience extension method will be generated by annotation processor trackEvent which must be called explicitly with the instance of your previously defined event class.
However, you can always use Collar methods explicitly besides the extension one to track your analytics events in the same way.
Collar.trackEvent(event)
@ScreenName(AnalyticsKeys.ScreenName.BRAND_DETAILS)
class BrandFragment : Fragment(R.layout.fragment_brand) {
...
override fun onResume() = super.onResume().run { trackScreen() }
...
private fun onFavor(exhibitor: Exhibitor) {
viewModel.favor(exhibitor)
...
trackEvent(AnalyticsEvent.ExhibitorFavored(exhibitorId = exhibitor.id, exhibitorName = exhibitor.name))
}
}
Create a model container sealed class with arbitrary name where you see fit in your project and annotate as illustrated below.
Nested classes represent the user properties you want to track.
Class name will be processed into lowercase snake_case appropriate for most analytics dependencies, as well as the containing variable names.
Variable value will be propagated as user property value.
You can override user property name for specific nested class then use PropertyName annotation with a respective new property name.
A property nested class can have only 1 declared variable as value.
@UserProperties
sealed class UserProperty {
@PropertyName(value = "user_type_corporate")
data class UserType1(
val value: String
) : UserProperty()
data class UserTypeRetail(
val someCoolValue: String
) : UserProperty()
}
You can have multiple annotated sealed classes declared like this through your project.
A convenience extension method will be generated by annotation processor trackProperty which must be called explicitly with the instance of your previously defined property class.
However, you can always use Collar methods explicitly besides the extension one to track your user properties in the same way.
Collar.trackProperty(property)
@ScreenName(AnalyticsKeys.ScreenName.BRAND_DETAILS)
class BrandFragment : Fragment(R.layout.fragment_brand) {
...
override fun onCreate() = super.onCreate().run {
trackProperty(UserProperty.UserTypeRetail("retail"))
}
...
override fun onResume() = super.onResume().run { trackScreen() }
...
private fun onFavor(exhibitor: Exhibitor) {
viewModel.favor(exhibitor)
...
trackEvent(AnalyticsEvent.ExhibitorFavored(exhibitorId = exhibitor.id, exhibitorName = exhibitor.name))
}
}
Collar plugin has been tuned per default for the most often used Firebase Analytics but additional options can be passed to annotation processor for fine tuning:
javaCompileOptions {
annotationProcessorOptions {
argument "events_count", "100"
argument "event_parameters_count", "10"
argument "event_name_length", "20"
argument "event_name_regex", "^[a-zA-Z0-9_]*$"
argument "properties_count", "17"
argument "property_name_regex", "^[a-zA-Z0-9_]*$"
argument "reserved_prefixes", "ga_,fb_"
argument "reserved_properties", "Age,Country"
}
}
Collar plugin provides it's own custom Lint checks. These can be disabled, suppressed or fixed like any other Lint registered issues.
Checks:
MissingScreenNameAnnotation
- All Activities and Fragments require a valid screen name annotation on the class. You must annotate an Activity or Fragment with @ScreenName with a valid value parameter or set enabled parameter to false.
A separate ui and ui-no-op packages are provided if you want to visually track what has been sent through Collar.
You can search, filter and clear all sent analytics.
In your app build.gradle
or build.gradle.kts
add:
Groovy
debugImplementation "com.infinum.collar:collar-ui:1.4.0"
releaseImplementation "com.infinum.collar:collar-ui-no-op:1.4.0"
KotlinDSL
debugImplementation("com.infinum.collar:collar-ui:1.4.0")
releaseImplementation("com.infinum.collar:collar-ui-no-op:1.4.0")
In order to start tracking with UI you must use LiveCollector as in this example:
val configuration = Configuration(
analyticsCollectionEnabled = true,
showSystemNotifications = true,
showInAppNotifications = true,
redactedKeywords = redactedWords
)
Collar.attach(object : LiveCollector(configuration) {
override fun onScreen(screen: Screen) =
super.onScreen(screen).run {
analyticsProvider.sendScreenName(screenName = screen.name)
}
override fun onEvent(event: Event) =
super.onEvent(event).run {
analyticsProvider.sendEvent(eventName = event.name, eventParameters = event.params ?: mapOf<String, *>())
}
override fun onProperty(property: Property) =
super.onProperty(property).run {
analyticsProvider.sendProperty(property.name, property.value)
}
})
LiveCollector constructor has a Configuration parameter that consists of the following members.
The first parameter (setAnalyticsCollectionEnabled) defines the default state of analytics collection. If set to false, a warning message will appear in the Collar UI.
If you set the second parameter (showSystemNotification) as true, a notification will show once analytics are gathered and clicking on it will open a dedicated screen.
The third parameter (showInAppNotification) with value true will show a Snackbar-ish popup once analytics are gathered inside the currently running Activity.
These parameters are default values per collector session but can be changed via CollarActivity menu and will remain valid until the next session.
Otherwise if set to false notification will not be shown but you can always run the UI with following command of getting the launch Intent instead of clicking the actual notification:
startActivity(
CollarUi.launchIntent().apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
)
Alternatively, you can use a dedicated method with default Intent setup:
CollarUi.show()
The final Configuration parameter is a set of keywords to redact if found in screen names, analytics events names and parameters and user properties names or values.
In order to prevent potential leaks of user sensitive data, developers have an option to implement a set of keywords to be replaced by a • in length of the matched keyword.
This set of keywords is provided to LiveCollector via Configuration.
Gradle plugin supports code generation from a JSON formatted file.
You will need to specify fileName
and packageName
in collar
plugin extension.
For example:
collar {
fileName = "example.json"
packageName = "com.infinum.collar.sample.analytics.generated"
}
JSON file has to be formatted in the same way as it is in sample
project module.
If you don't want to use this task simply don't specify data parameters in plugin extension.
Using this file is just a temporary solution and fetching the tracking plan will be implemented soon in future releases.
To run the task you can:
- Open
gradle
panel on right side, findcollar
task group and rungenerate
task - Type
./gradlew generate
in terminal
generate
Gradle task will create classes prepared for the Collar annotation processor.
Copyright 2020 Infinum
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Maintained and sponsored by Infinum.