- Features
- Example
- Introduce Architecture
- Installation
- Configure
- Implementation
- Integration with UI
- Advanced
- TODO
- 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
- iOS
- Deployment Target 10.0 or higher
- Android
- minSdkVersion 23
Add below gradle settings into your KMP (Kotlin Multiplatform Project)
plugins {
id("com.android.library")
kotlin("multiplatform")
kotlin("native.cocoapods")
}
val analyticsTools = "com.linecorp.abc:kmm-analytics-tools:1.1.0"
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
}
}
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
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"
}
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: "")
}
}
This function must be called in the scope where the user properties are changed
Android or Shared
ATEventCenter.setUserProperties()
iOS
ATEventCenter.Companion().setUserProperties()
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]) {
}
}
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<NSString> {}
final class ViewTime: AnyKeyValueContainer<KotlinInt> {}
}
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 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 []
}
}
}
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)
}
}
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")))
}
Button("Hello") {
ATEventCenter.Companion().send(
event: .click,
params: [BaseParam.ClickSource(value: "hello")])
}
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<NSString> { }
}
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")])
}
You can dynamically determine the screen name by implementing ATDynamicScreenNameMappable.
Android
class MainActivity : AppCompatActivity(), ATDynamicScreenNameMappable {
override fun mapScreenName(): String? {
return "ScreenNameAsYouWant"
}
}
iOS
extension MainViewController: ATDynamicScreenNameMappable {
func mapScreenName() -> String? {
"ScreenNameAsYouWant"
}
}
Android
ATEventCenter.send(Event.CLICK, listOf(
BaseParam.ScreenClass("screenClass"),
BaseParam.ScreenName("screenName"),
))
iOS
ATEventCenter.Companion().send(event: .click, params: [
BaseParam.ScreenClass(value: "screenClass"),
BaseParam.ScreenName(value: "screenName"),
])
- 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