Skip to content

Commit 84a9e31

Browse files
authored
Merge pull request #14 from TelemetryDeck/send-navigation-signals
Add support for navigation signals
2 parents a8bc0a6 + 957842d commit 84a9e31

21 files changed

+416
-173
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.5.0
2+
3+
- https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.5.0
4+
- Add support for navigation signals
5+
- Upgrades to the latest version of the Swift and Kotlin SDKs
6+
17
## 0.4.0
28

39
- https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.4.0

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,25 @@ Telemetrydecksdk.stop()
9191

9292
In order to restart sending events, you will need to call the `start` method again.
9393

94+
## Navigation signals
95+
96+
A navigation signal is a regular TelemetryDeck signal of type `TelemetryDeck.Navigation.pathChanged`. Automatic navigation tracking is available using the `navigate` and `navigateToDestination` methods:
97+
98+
```dart
99+
Telemetrydecksdk.navigate("screen1", "screen2");
100+
101+
Telemetrydecksdk.navigateToDestination("screen3");
102+
```
103+
104+
Both methods allow for a custom `clientUser` to be passed as an optional parameter:
105+
106+
```dart
107+
Telemetrydecksdk.navigate("screen1", "screen2",
108+
clientUser: "custom_user");
109+
```
110+
111+
For more information, please check [this post](https://telemetrydeck.com/docs/articles/navigation-signals/).
112+
94113
## Test mode
95114

96115
If your app's build configuration is set to "Debug", all signals sent will be marked as testing signals. In the Telemetry Viewer app, activate **Test Mode** to see those.

RELEASE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Package Release Notes
2+
3+
This section refers to the process of maintaining, upgrading and publishing the current library.
4+
5+
## Releasing a new version
6+
7+
1. Create a PR to update the CHANGELOG in order to mention the changes made in the new version. This is optional, if this step is skipped, the `setupversion.sh` will create a generic entry.
8+
9+
2. Merge all changes into `main`.
10+
11+
3. Navigate to the [Set package version](https://github.com/TelemetryDeck/FlutterSDK/actions/workflows/set-version.yml) action and run it by setting the next `version`. Please note: this must be the same if you manually created a release entry in CHANGELOG.md.
12+
13+
🏁
14+
15+
## Adopting newer versions of the native SDKs
16+
17+
The Flutter SDK depends on the latest major version of the native SDKs. This is defined in the following locations:
18+
19+
On Android, the dependency is configured in `android/build.gradle`:
20+
21+
```
22+
implementation 'com.github.TelemetryDeck:KotlinSDK:2.+'
23+
```
24+
25+
On iOS, the dependency is configured in `ios/telemetrydecksdk.podspec` using the podspect Dependency format `s.dependency 'TelemetryClient', '~> 2.0'`.

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ group 'com.telemetrydeck.telemetrydecksdk'
22
version '1.0-SNAPSHOT'
33

44
buildscript {
5-
ext.kotlin_version = '1.7.10'
5+
ext.kotlin_version = '1.9.24'
66
repositories {
77
google()
88
mavenCentral()
@@ -52,7 +52,7 @@ android {
5252
}
5353

5454
dependencies {
55-
implementation 'com.github.TelemetryDeck:KotlinSDK:+'
55+
implementation 'com.github.TelemetryDeck:KotlinSDK:2.+'
5656
testImplementation 'org.jetbrains.kotlin:kotlin-test'
5757
testImplementation 'org.mockito:mockito-core:5.0.0'
5858
}

android/src/main/kotlin/com/telemetrydeck/telemetrydecksdk/TelemetrydecksdkPlugin.kt

Lines changed: 169 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -17,125 +17,188 @@ import kotlinx.coroutines.launch
1717
import kotlinx.coroutines.withContext
1818

1919
/** TelemetrydecksdkPlugin */
20-
class TelemetrydecksdkPlugin: FlutterPlugin, MethodCallHandler {
21-
/// The MethodChannel that will the communication between Flutter and native Android
22-
///
23-
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
24-
/// when the Flutter Engine is detached from the Activity
25-
private lateinit var channel : MethodChannel
26-
private var applicationContext: Context? = null
27-
private val coroutineScope = CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks
28-
29-
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
30-
applicationContext = flutterPluginBinding.applicationContext
31-
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk")
32-
channel.setMethodCallHandler(this)
33-
}
34-
35-
override fun onMethodCall(call: MethodCall, result: Result) {
36-
if (call.method == "start") {
37-
nativeInitialize(call, result)
38-
} else if (call.method == "stop") {
39-
nativeStop(call, result)
40-
} else if (call.method == "send") {
41-
// this maps to the queue method which aligns with the behaviour of the iOS SDK
42-
nativeQueue(call, result)
43-
} else if (call.method == "generateNewSession") {
44-
TelemetryManager.newSession()
45-
result.success(null)
46-
} else if (call.method == "updateDefaultUser") {
47-
nativeUpdateDefaultUser(call, result)
48-
} else {
49-
result.notImplemented()
20+
class TelemetrydecksdkPlugin : FlutterPlugin, MethodCallHandler {
21+
/// The MethodChannel that will the communication between Flutter and native Android
22+
///
23+
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
24+
/// when the Flutter Engine is detached from the Activity
25+
private lateinit var channel: MethodChannel
26+
private var applicationContext: Context? = null
27+
private val coroutineScope =
28+
CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks
29+
30+
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
31+
applicationContext = flutterPluginBinding.applicationContext
32+
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk")
33+
channel.setMethodCallHandler(this)
5034
}
51-
}
5235

53-
private fun nativeStop(call: MethodCall, result: Result) {
54-
coroutineScope.launch {
55-
TelemetryManager.stop()
56-
withContext(Dispatchers.Main) {
57-
result.success(null)
36+
override fun onMethodCall(call: MethodCall, result: Result) {
37+
when (call.method) {
38+
"start" -> {
39+
nativeInitialize(call, result)
40+
}
41+
"stop" -> {
42+
nativeStop(result)
43+
}
44+
"send" -> {
45+
// this maps to the queue method which aligns with the behaviour of the iOS SDK
46+
nativeQueue(call, result)
47+
}
48+
"generateNewSession" -> {
49+
TelemetryManager.newSession()
50+
result.success(null)
51+
}
52+
"updateDefaultUser" -> {
53+
nativeUpdateDefaultUser(call, result)
54+
}
55+
"navigate" -> {
56+
nativeNavigate(call, result)
57+
}
58+
"navigateToDestination" -> {
59+
nativeNavigateDestination(call, result)
60+
}
61+
else -> {
62+
result.notImplemented()
63+
}
5864
}
5965
}
60-
}
6166

62-
private fun nativeUpdateDefaultUser(call: MethodCall,
63-
result: Result) {
64-
val user = call.arguments<String>()
67+
/**
68+
* Send a signal that represents a navigation event with a source and a destination.
69+
*
70+
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
71+
* */
72+
private fun nativeNavigate(call: MethodCall, result: Result) {
73+
val sourcePath = call.argument<String>("sourcePath")
74+
val destinationPath = call.argument<String>("destinationPath")
75+
val clientUser = call.argument<String?>("clientUser")
76+
77+
if (sourcePath == null || destinationPath == null) {
78+
result.error("INVALID_ARGUMENT", "sourcePath and destinationPath are required", null)
79+
return
80+
}
6581

66-
coroutineScope.launch {
67-
TelemetryManager.newDefaultUser(user)
68-
withContext(Dispatchers.Main) {
69-
result.success(null)
70-
}
71-
}
72-
}
73-
74-
private fun nativeQueue(
75-
call: MethodCall,
76-
result: Result
77-
) {
78-
val signalType = call.argument<String>("signalType")
79-
if (signalType != null) {
80-
val clientUser = call.argument<String?>("clientUser")
81-
val additionalPayload = call.argument<Map<String, String>>("additionalPayload")
82-
83-
coroutineScope.launch {
84-
TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty())
85-
86-
withContext(Dispatchers.Main) {
87-
result.success(null)
82+
coroutineScope.launch {
83+
TelemetryManager.navigate(sourcePath, destinationPath, clientUser)
84+
withContext(Dispatchers.Main) {
85+
result.success(null)
86+
}
8887
}
89-
}
9088
}
91-
}
92-
93-
private fun nativeInitialize(call: MethodCall, result: Result) {
94-
val arguments = call.arguments as? Map<*, *> // Cast to a Map
95-
if (arguments != null) {
96-
// Extract values using the expected keys
97-
// we extract the required appID parameter
98-
val appID = arguments["appID"] as? String
99-
if (appID == null) {
100-
result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null)
101-
return
102-
}
10389

104-
// additional optional parameters
105-
val apiBaseURL = arguments["apiBaseURL"] as? String?
106-
val defaultUser = arguments["defaultUser"] as? String?
107-
val debug = arguments["debug"] as? Boolean
108-
val testMode = arguments["testMode"] as? Boolean
90+
/**
91+
* Send a signal that represents a navigation event with a destination and a default source.
92+
*
93+
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
94+
* */
95+
private fun nativeNavigateDestination(call: MethodCall, result: Result) {
96+
val destinationPath = call.argument<String>("destinationPath")
97+
val clientUser = call.argument<String?>("clientUser")
98+
99+
if (destinationPath == null) {
100+
result.error("INVALID_ARGUMENT", "destinationPath is required", null)
101+
return
102+
}
109103

104+
coroutineScope.launch {
105+
TelemetryManager.navigate(destinationPath, clientUser)
106+
withContext(Dispatchers.Main) {
107+
result.success(null)
108+
}
109+
}
110+
}
110111

111-
// Initialize the client
112-
// Do not activate the lifecycle provider
113-
val builder = TelemetryManager.Builder()
114-
.appID(appID)
115-
.providers(listOf(SessionProvider(), EnvironmentMetadataProvider()))
112+
private fun nativeStop(result: Result) {
113+
coroutineScope.launch {
114+
TelemetryManager.stop()
115+
withContext(Dispatchers.Main) {
116+
result.success(null)
117+
}
118+
}
119+
}
116120

117-
apiBaseURL?.let {
118-
builder.baseURL(it)
119-
}
120-
defaultUser?.let {
121-
builder.defaultUser(it)
122-
}
123-
debug?.let {
124-
builder.showDebugLogs(it)
125-
}
126-
testMode?.let {
127-
builder.testMode(it)
128-
}
121+
private fun nativeUpdateDefaultUser(
122+
call: MethodCall,
123+
result: Result
124+
) {
125+
val user = call.arguments<String>()
126+
127+
coroutineScope.launch {
128+
TelemetryManager.newDefaultUser(user)
129+
withContext(Dispatchers.Main) {
130+
result.success(null)
131+
}
132+
}
133+
}
129134

130-
val application = applicationContext as Application
131-
TelemetryManager.start(application, builder)
132-
result.success(null)
133-
} else {
134-
result.error("INVALID_ARGUMENT", "Arguments are not a map", null)
135+
private fun nativeQueue(
136+
call: MethodCall,
137+
result: Result
138+
) {
139+
val signalType = call.argument<String>("signalType")
140+
if (signalType != null) {
141+
val clientUser = call.argument<String?>("clientUser")
142+
val additionalPayload = call.argument<Map<String, String>>("additionalPayload")
143+
144+
coroutineScope.launch {
145+
TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty())
146+
147+
withContext(Dispatchers.Main) {
148+
result.success(null)
149+
}
150+
}
151+
} else {
152+
result.error("INVALID_ARGUMENT", "signalType must be provided", null)
153+
}
135154
}
136-
}
137155

138-
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
139-
channel.setMethodCallHandler(null)
140-
}
156+
private fun nativeInitialize(call: MethodCall, result: Result) {
157+
val arguments = call.arguments as? Map<*, *> // Cast to a Map
158+
if (arguments != null) {
159+
// Extract values using the expected keys
160+
// we extract the required appID parameter
161+
val appID = arguments["appID"] as? String
162+
if (appID == null) {
163+
result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null)
164+
return
165+
}
166+
167+
// additional optional parameters
168+
val apiBaseURL = arguments["apiBaseURL"] as? String?
169+
val defaultUser = arguments["defaultUser"] as? String?
170+
val debug = arguments["debug"] as? Boolean
171+
val testMode = arguments["testMode"] as? Boolean
172+
173+
174+
// Initialize the client
175+
// Do not activate the lifecycle provider
176+
val builder = TelemetryManager.Builder()
177+
.appID(appID)
178+
.providers(listOf(SessionProvider(), EnvironmentMetadataProvider()))
179+
180+
apiBaseURL?.let {
181+
builder.baseURL(it)
182+
}
183+
defaultUser?.let {
184+
builder.defaultUser(it)
185+
}
186+
debug?.let {
187+
builder.showDebugLogs(it)
188+
}
189+
testMode?.let {
190+
builder.testMode(it)
191+
}
192+
193+
val application = applicationContext as Application
194+
TelemetryManager.start(application, builder)
195+
result.success(null)
196+
} else {
197+
result.error("INVALID_ARGUMENT", "Arguments are not a map", null)
198+
}
199+
}
200+
201+
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
202+
channel.setMethodCallHandler(null)
203+
}
141204
}

example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.7.10'
2+
ext.kotlin_version = '1.9.24'
33
repositories {
44
google()
55
mavenCentral()

example/android/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
org.gradle.jvmargs=-Xmx4G
22
android.useAndroidX=true
33
android.enableJetifier=true
4+
kotlin.code.style=official

0 commit comments

Comments
 (0)