Index
Features
- Unified handling of various event trackers is possible
- Automatically tracking and sending events of screen view and capture
- Provide screen name mapper
- Remove boilerplate by binding UI elements to event triggers
- Common interfaces available in KMM Shared
Example
Requirements
- iOS
- Deployment Target 10.0 or higher
- Android
- minSdkVersion 21
Installation
Gradle Settings
Add below gradle settings into your KMP (Kotlin Multiplatform Project)
build.gradle.kts in shared
plugins {
id("com.android.library")
kotlin("multiplatform")
kotlin("native.cocoapods")
}
val analyticsTools = "com.linecorp.abc:kmm-analytics-tools:1.0.14"
kotlin {
android()
ios {
binaries
.filterIsInstance<Framework>()
.forEach {
it.baseName = "shared"
it.transitiveExport = true
it.export(analyticsTools)
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(analyticsTools)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation(analyticsTools)
api(analyticsTools)
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2")
implementation("androidx.test:core:1.0.0")
implementation("androidx.test:runner:1.1.0")
implementation("org.robolectric:robolectric:4.2")
}
}
val iosMain by getting {
dependencies {
implementation(analyticsTools)
api(analyticsTools)
}
}
val iosTest by getting
}
}
Project Settings
Android
- You can use right now without other settings.
iOS
- Create
Podfile
with below setting in your project root.use_frameworks! platform :ios, '10.0' install! 'cocoapods', :deterministic_uuids => false target 'iosApp' do pod 'shared', :path => '../shared/' end
- Run command
pod install
on the terminal
Configure
Using Screen Mapper
Screen mapper is mapping system that to map between screen class and screen name. The file ATScreenNameMapper.json
that defines screen mapping table will automatically used if needed in system.
Android
-
Create a file named
ATScreenNameMapper.json
in your assets folder like below. -
Write table as json format for screen name mapping.
{
"MainActivity": "main"
}
iOS
-
Create a file named
ATScreenNameMapper.json
in your project like below. -
Write table as json format for screen name mapping.
{
"MainViewController": "main"
}
Initialization
Android
ATEventCenter.setConfiguration {
canTrackScreenView { true }
canTrackScreenCapture { true }
register(TSDelegate())
register(GoogleAnalyticsDelegate())
}
iOS
ATEventCenter.Companion().setConfiguration {
$0.canTrackScreenView { _ in
true
}
$0.canTrackScreenCapture {
true
}
$0.mapScreenClass {
$0.className()
}
$0.topViewController {
UIViewController.topMost
}
$0.register(delegate: TSDelegate())
$0.register(delegate: GoogleAnalyticsDelegate())
}
extension UIViewController {
static var topMost: UIViewController? {
UIViewControllerUtil.Companion().topMost()
}
var className: String {
let regex = try! NSRegularExpression(pattern: "<.(.*)>", options: .allowCommentsAndWhitespace)
let str = String(describing: type(of: self))
let range = NSMakeRange(0, str.count)
return regex.stringByReplacingMatches(in: str, options: .reportCompletion, range: range, withTemplate: "")
}
}
Setup User Properties
This function must be called in the scope where the user properties are changed
Android or Shared
ATEventCenter.setUserProperties()
iOS
ATEventCenter.Companion().setUserProperties()
Implementation
Delegate
Delegate class is proxy for various event trackers.
Android or Shared
class GoogleAnalyticsDelegate: ATEventDelegate {
// @optional to map key of event
override fun mapEventKey(event: Event): String {
event.value
}
// @optional to map key of parameter
override fun mapParamKey(container: KeyValueContainer): String {
container.key
}
// @required to set up event tracker
override fun setup() {
}
// @required to set user properties for event tracker
override fun setUserProperties() {
}
// @required to send event to the event tracker
override fun send(event: String, params: Map<String, String>) {
}
}
iOS
final class GoogleAnalyticsDelegate: ATEventDelegate {
// @required to map key of parameter
func mapEventKey(event: Event) -> String {
event.value
}
// @required to map key of parameter
func mapParamKey(container: KeyValueContainer) -> String {
container.key
}
// @required to set up event tracker
func setup() {
}
// @required to set user properties for event tracker
func setUserProperties() {
}
// @required to send event to the event tracker
func send(event: String, params: [String: String]) {
}
}
Parameters
Kotlin
sealed class Param: KeyValueContainer {
data class UserName(override val value: String): Param()
data class ViewTime(override val value: Int): Param()
}
Swift
enum Param {
final class UserName: AnyKeyValueContainer
{}
final class ViewTime: AnyKeyValueContainer
{}
}
If you want something more swift, use your own KeyValueContainer.
enum Param {
final class UserName: MyKeyValueContainer<String> {}
final class ViewTime: MyKeyValueContainer<Int> {}
}
class MyKeyValueContainer<T: Any>: KeyValueContainer {
init(_ value: T) {
self.value = value
}
let value: Any
var key: String {
"\(self)".components(separatedBy: ".").last?.snakenized ?? ""
}
}
ATEventParamProvider
ATEventParamProvider is used to get extra parameters when send to events and functions are called automatically.
Android
class MainActivity : AppCompatActivity(), ATEventParamProvider {
override fun params(event: Event, source: Any?): List<KeyValueContainer> {
return when(event) {
Event.VIEW -> listOf(
Param.UserName("steve"),
Param.ViewTime(10000))
else -> listOf()
}
}
}
iOS
extension MainViewController: ATEventParamProvider {
func params(event: Event, source: Any?) -> [KeyValueContainer] {
switch event {
case .view:
return [
Param.UserName("steve"),
Param.ViewTime(10000)
]
default:
return []
}
}
}
ATEventTriggerUICompatible (iOS Only)
This is an interface to make it easy to connect the click event of a ui element to an event trigger.
extension UIControl: ATEventTriggerUICompatible {
public func registerTrigger(invoke: @escaping () -> Void) {
rx.controlEvent(.touchUpInside)
.bind { invoke() }
.disposed(by: disposeBag)
}
}
Integration with UI
Android
Legacy
- Using with Event Trigger
sealed class Param: KeyValueContainer {
data class UserName(override val value: String): Param()
}
class MainActivity : AppCompatActivity(), ATEventParamProvider {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.eventTrigger
.click("button")
.register { listOf(Param.UserName("steve")) }
button.setOnClickListener { v ->
v.eventTrigger.send()
}
}
}
- Using Directly
<Button android:id="@+id/button" android:text="Hello!" android:onClick="onClickHello"/>
fun onClickHello(view: View) {
ATEventCenter.send(
Event.CLICK,
listOf(BaseParam.ClickSource("hello")))
}
iOS
SwiftUI
Button("Hello") {
ATEventCenter.Companion().send(
event: .click,
params: [BaseParam.ClickSource(value: "hello")])
}
Legacy
Using with Event Trigger
- Extension to get instance of ATEventTrigger
extension ATEventTriggerCompatible {
var eventTrigger: ATEventTrigger<Self> {
ATEventTriggerFactory.Companion().create(owner: self) as! ATEventTrigger<Self>
}
}
- Extension to make it easy to connect the click event of a ui element to an event trigger
extension UIControl: ATEventTriggerUICompatible {
public func registerTrigger(invoke: @escaping () -> Void) {
rx.controlEvent(.touchUpInside)
.bind { invoke() }
.disposed(by: disposeBag)
}
}
- Client side
enum Param {
final class UserName: AnyKeyValueContainer
{ }
}
override func viewDidLoad() {
super.viewDidLoad()
button.eventTrigger
.click(source: "hello")
.register { [Param.UserName("steve")] }
}
Using Directly
@IBAction func clicked(_ sender: UIButton) {
ATEventCenter.Companion().send(
event: .click,
params: [BaseParam.ClickSource(value: "hello")])
}
TODO
- Publish to maven repository
- Write install guide
- Integration UI with event trigger for iOS
- LegacyUI
- SwiftUI
- Integration UI with event trigger for Android
- Legacy
- View, Any
- Compose
- Legacy