Skip to content

Commit

Permalink
Merge pull request #14 from TelemetryDeck/send-navigation-signals
Browse files Browse the repository at this point in the history
Add support for navigation signals
  • Loading branch information
kkostov authored Jul 22, 2024
2 parents a8bc0a6 + 957842d commit 84a9e31
Show file tree
Hide file tree
Showing 21 changed files with 416 additions and 173 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.5.0

- https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.5.0
- Add support for navigation signals
- Upgrades to the latest version of the Swift and Kotlin SDKs

## 0.4.0

- https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.4.0
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ Telemetrydecksdk.stop()

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

## Navigation signals

A navigation signal is a regular TelemetryDeck signal of type `TelemetryDeck.Navigation.pathChanged`. Automatic navigation tracking is available using the `navigate` and `navigateToDestination` methods:

```dart
Telemetrydecksdk.navigate("screen1", "screen2");
Telemetrydecksdk.navigateToDestination("screen3");
```

Both methods allow for a custom `clientUser` to be passed as an optional parameter:

```dart
Telemetrydecksdk.navigate("screen1", "screen2",
clientUser: "custom_user");
```

For more information, please check [this post](https://telemetrydeck.com/docs/articles/navigation-signals/).

## Test mode

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.
Expand Down
25 changes: 25 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Package Release Notes

This section refers to the process of maintaining, upgrading and publishing the current library.

## Releasing a new version

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.

2. Merge all changes into `main`.

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.

🏁

## Adopting newer versions of the native SDKs

The Flutter SDK depends on the latest major version of the native SDKs. This is defined in the following locations:

On Android, the dependency is configured in `android/build.gradle`:

```
implementation 'com.github.TelemetryDeck:KotlinSDK:2.+'
```

On iOS, the dependency is configured in `ios/telemetrydecksdk.podspec` using the podspect Dependency format `s.dependency 'TelemetryClient', '~> 2.0'`.
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ group 'com.telemetrydeck.telemetrydecksdk'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.24'
repositories {
google()
mavenCentral()
Expand Down Expand Up @@ -52,7 +52,7 @@ android {
}

dependencies {
implementation 'com.github.TelemetryDeck:KotlinSDK:+'
implementation 'com.github.TelemetryDeck:KotlinSDK:2.+'
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,125 +17,188 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/** TelemetrydecksdkPlugin */
class TelemetrydecksdkPlugin: FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
private var applicationContext: Context? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk")
channel.setMethodCallHandler(this)
}

override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "start") {
nativeInitialize(call, result)
} else if (call.method == "stop") {
nativeStop(call, result)
} else if (call.method == "send") {
// this maps to the queue method which aligns with the behaviour of the iOS SDK
nativeQueue(call, result)
} else if (call.method == "generateNewSession") {
TelemetryManager.newSession()
result.success(null)
} else if (call.method == "updateDefaultUser") {
nativeUpdateDefaultUser(call, result)
} else {
result.notImplemented()
class TelemetrydecksdkPlugin : FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private var applicationContext: Context? = null
private val coroutineScope =
CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk")
channel.setMethodCallHandler(this)
}
}

private fun nativeStop(call: MethodCall, result: Result) {
coroutineScope.launch {
TelemetryManager.stop()
withContext(Dispatchers.Main) {
result.success(null)
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"start" -> {
nativeInitialize(call, result)
}
"stop" -> {
nativeStop(result)
}
"send" -> {
// this maps to the queue method which aligns with the behaviour of the iOS SDK
nativeQueue(call, result)
}
"generateNewSession" -> {
TelemetryManager.newSession()
result.success(null)
}
"updateDefaultUser" -> {
nativeUpdateDefaultUser(call, result)
}
"navigate" -> {
nativeNavigate(call, result)
}
"navigateToDestination" -> {
nativeNavigateDestination(call, result)
}
else -> {
result.notImplemented()
}
}
}
}

private fun nativeUpdateDefaultUser(call: MethodCall,
result: Result) {
val user = call.arguments<String>()
/**
* Send a signal that represents a navigation event with a source and a destination.
*
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
* */
private fun nativeNavigate(call: MethodCall, result: Result) {
val sourcePath = call.argument<String>("sourcePath")
val destinationPath = call.argument<String>("destinationPath")
val clientUser = call.argument<String?>("clientUser")

if (sourcePath == null || destinationPath == null) {
result.error("INVALID_ARGUMENT", "sourcePath and destinationPath are required", null)
return
}

coroutineScope.launch {
TelemetryManager.newDefaultUser(user)
withContext(Dispatchers.Main) {
result.success(null)
}
}
}

private fun nativeQueue(
call: MethodCall,
result: Result
) {
val signalType = call.argument<String>("signalType")
if (signalType != null) {
val clientUser = call.argument<String?>("clientUser")
val additionalPayload = call.argument<Map<String, String>>("additionalPayload")

coroutineScope.launch {
TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty())

withContext(Dispatchers.Main) {
result.success(null)
coroutineScope.launch {
TelemetryManager.navigate(sourcePath, destinationPath, clientUser)
withContext(Dispatchers.Main) {
result.success(null)
}
}
}
}
}

private fun nativeInitialize(call: MethodCall, result: Result) {
val arguments = call.arguments as? Map<*, *> // Cast to a Map
if (arguments != null) {
// Extract values using the expected keys
// we extract the required appID parameter
val appID = arguments["appID"] as? String
if (appID == null) {
result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null)
return
}

// additional optional parameters
val apiBaseURL = arguments["apiBaseURL"] as? String?
val defaultUser = arguments["defaultUser"] as? String?
val debug = arguments["debug"] as? Boolean
val testMode = arguments["testMode"] as? Boolean
/**
* Send a signal that represents a navigation event with a destination and a default source.
*
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
* */
private fun nativeNavigateDestination(call: MethodCall, result: Result) {
val destinationPath = call.argument<String>("destinationPath")
val clientUser = call.argument<String?>("clientUser")

if (destinationPath == null) {
result.error("INVALID_ARGUMENT", "destinationPath is required", null)
return
}

coroutineScope.launch {
TelemetryManager.navigate(destinationPath, clientUser)
withContext(Dispatchers.Main) {
result.success(null)
}
}
}

// Initialize the client
// Do not activate the lifecycle provider
val builder = TelemetryManager.Builder()
.appID(appID)
.providers(listOf(SessionProvider(), EnvironmentMetadataProvider()))
private fun nativeStop(result: Result) {
coroutineScope.launch {
TelemetryManager.stop()
withContext(Dispatchers.Main) {
result.success(null)
}
}
}

apiBaseURL?.let {
builder.baseURL(it)
}
defaultUser?.let {
builder.defaultUser(it)
}
debug?.let {
builder.showDebugLogs(it)
}
testMode?.let {
builder.testMode(it)
}
private fun nativeUpdateDefaultUser(
call: MethodCall,
result: Result
) {
val user = call.arguments<String>()

coroutineScope.launch {
TelemetryManager.newDefaultUser(user)
withContext(Dispatchers.Main) {
result.success(null)
}
}
}

val application = applicationContext as Application
TelemetryManager.start(application, builder)
result.success(null)
} else {
result.error("INVALID_ARGUMENT", "Arguments are not a map", null)
private fun nativeQueue(
call: MethodCall,
result: Result
) {
val signalType = call.argument<String>("signalType")
if (signalType != null) {
val clientUser = call.argument<String?>("clientUser")
val additionalPayload = call.argument<Map<String, String>>("additionalPayload")

coroutineScope.launch {
TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty())

withContext(Dispatchers.Main) {
result.success(null)
}
}
} else {
result.error("INVALID_ARGUMENT", "signalType must be provided", null)
}
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
private fun nativeInitialize(call: MethodCall, result: Result) {
val arguments = call.arguments as? Map<*, *> // Cast to a Map
if (arguments != null) {
// Extract values using the expected keys
// we extract the required appID parameter
val appID = arguments["appID"] as? String
if (appID == null) {
result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null)
return
}

// additional optional parameters
val apiBaseURL = arguments["apiBaseURL"] as? String?
val defaultUser = arguments["defaultUser"] as? String?
val debug = arguments["debug"] as? Boolean
val testMode = arguments["testMode"] as? Boolean


// Initialize the client
// Do not activate the lifecycle provider
val builder = TelemetryManager.Builder()
.appID(appID)
.providers(listOf(SessionProvider(), EnvironmentMetadataProvider()))

apiBaseURL?.let {
builder.baseURL(it)
}
defaultUser?.let {
builder.defaultUser(it)
}
debug?.let {
builder.showDebugLogs(it)
}
testMode?.let {
builder.testMode(it)
}

val application = applicationContext as Application
TelemetryManager.start(application, builder)
result.success(null)
} else {
result.error("INVALID_ARGUMENT", "Arguments are not a map", null)
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.24'
repositories {
google()
mavenCentral()
Expand Down
1 change: 1 addition & 0 deletions example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
Loading

0 comments on commit 84a9e31

Please sign in to comment.