diff --git a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt index 8dbc1cc..63a59aa 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -22,6 +22,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result import java.lang.ref.WeakReference /** @@ -171,7 +172,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private fun track(params: Map) { val name = requireNotNull(params.getAsTypeOrNull(Keys.Tracking.NAME)) { - "Event name is required to track event" + "Event name is missing in params: $params" } val properties = params.getAsTypeOrNull>(Keys.Tracking.PROPERTIES) @@ -225,18 +226,16 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun screen(params: Map) { - // TODO: Fix screen implementation - /* - val name = params.getString(Keys.Tracking.EVENT_NAME) - val attributes = - params.getProperty>(Keys.Tracking.ATTRIBUTES) ?: emptyMap() + val title = requireNotNull(params.getAsTypeOrNull(Keys.Tracking.TITLE)) { + "Screen title is missing in params: $params" + } + val properties = params.getAsTypeOrNull>(Keys.Tracking.PROPERTIES) - if (attributes.isEmpty()) { - CustomerIO.instance().screen(name) + if (properties.isNullOrEmpty()) { + CustomerIO.instance().screen(title) } else { - CustomerIO.instance().screen(name, attributes) + CustomerIO.instance().screen(title, properties) } - */ } private fun initialize(args: Map): kotlin.Result = runCatching { diff --git a/android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt b/android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt index c2cab20..68d7773 100644 --- a/android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt +++ b/android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt @@ -28,5 +28,6 @@ internal object Keys { const val NAME = "name" const val PROPERTIES = "properties" + const val TITLE = "title" } } diff --git a/apps/amiapp_flutter/lib/src/app.dart b/apps/amiapp_flutter/lib/src/app.dart index 4744fce..5ac08e5 100644 --- a/apps/amiapp_flutter/lib/src/app.dart +++ b/apps/amiapp_flutter/lib/src/app.dart @@ -216,7 +216,7 @@ class _AmiAppState extends State { if (_customerIOSDK.sdkConfig?.screenTrackingEnabled == true) { final Screen? screen = _router.currentLocation().toAppScreen(); if (screen != null) { - CustomerIO.instance.screen(name: screen.name); + CustomerIO.instance.screen(title: screen.name); } } } diff --git a/apps/amiapp_flutter/lib/src/data/config.dart b/apps/amiapp_flutter/lib/src/data/config.dart index d08b74b..7b14cfb 100644 --- a/apps/amiapp_flutter/lib/src/data/config.dart +++ b/apps/amiapp_flutter/lib/src/data/config.dart @@ -8,8 +8,8 @@ class CustomerIOSDKConfig { final String cdpApiKey; final String? migrationSiteId; final Region? region; - final bool? debugModeEnabled; - final bool? screenTrackingEnabled; + final bool debugModeEnabled; + final bool screenTrackingEnabled; final bool? autoTrackDeviceAttributes; final String? apiHost; final String? cdnHost; @@ -22,8 +22,8 @@ class CustomerIOSDKConfig { required this.cdpApiKey, this.migrationSiteId, this.region, - this.debugModeEnabled, - this.screenTrackingEnabled, + this.debugModeEnabled = true, + this.screenTrackingEnabled = true, this.autoTrackDeviceAttributes, this.apiHost, this.cdnHost, @@ -53,9 +53,10 @@ class CustomerIOSDKConfig { cdpApiKey: cdpApiKey, migrationSiteId: prefs.getString(_PreferencesKey.migrationSiteId), region: region, - debugModeEnabled: prefs.getBool(_PreferencesKey.debugModeEnabled), + debugModeEnabled: + prefs.getBool(_PreferencesKey.debugModeEnabled) != false, screenTrackingEnabled: - prefs.getBool(_PreferencesKey.screenTrackingEnabled), + prefs.getBool(_PreferencesKey.screenTrackingEnabled) != false, autoTrackDeviceAttributes: prefs.getBool(_PreferencesKey.autoTrackDeviceAttributes), apiHost: prefs.getString(_PreferencesKey.apiHost), diff --git a/apps/amiapp_flutter/lib/src/screens/settings.dart b/apps/amiapp_flutter/lib/src/screens/settings.dart index 36887eb..92414da 100644 --- a/apps/amiapp_flutter/lib/src/screens/settings.dart +++ b/apps/amiapp_flutter/lib/src/screens/settings.dart @@ -61,8 +61,8 @@ class _SettingsScreenState extends State { _cdnHostValueController = TextEditingController(text: cioConfig?.cdnHost); _flushAtValueController = TextEditingController(text: cioConfig?.flushAt?.toString()); - _flushIntervalValueController = TextEditingController( - text: cioConfig?.flushInterval?.toTrimmedString()); + _flushIntervalValueController = + TextEditingController(text: cioConfig?.flushInterval?.toString()); _featureTrackScreens = cioConfig?.screenTrackingEnabled ?? true; _featureTrackDeviceAttributes = cioConfig?.autoTrackDeviceAttributes ?? true; @@ -115,11 +115,11 @@ class _SettingsScreenState extends State { _cdnHostValueController.text = defaultConfig.cdnHost ?? ''; _flushAtValueController.text = defaultConfig.flushAt?.toString() ?? ''; _flushIntervalValueController.text = - defaultConfig.flushInterval?.toTrimmedString() ?? ''; - _featureTrackScreens = defaultConfig.screenTrackingEnabled ?? true; + defaultConfig.flushInterval?.toString() ?? ''; + _featureTrackScreens = defaultConfig.screenTrackingEnabled; _featureTrackDeviceAttributes = defaultConfig.autoTrackDeviceAttributes ?? true; - _featureDebugMode = defaultConfig.debugModeEnabled ?? true; + _featureDebugMode = defaultConfig.debugModeEnabled; _saveSettings(context); }); } diff --git a/apps/amiapp_flutter/lib/src/utils/extensions.dart b/apps/amiapp_flutter/lib/src/utils/extensions.dart index c7dbcc9..7d573cf 100644 --- a/apps/amiapp_flutter/lib/src/utils/extensions.dart +++ b/apps/amiapp_flutter/lib/src/utils/extensions.dart @@ -108,15 +108,6 @@ extension AmiAppStringExtensions on String { } } -extension AmiAppIntExtensions on int { - String? toTrimmedString() { - if (this % 1.0 != 0.0) { - return toString(); - } - return toStringAsFixed(0); - } -} - extension LocationExtensions on GoRouter { // Get location of current route // This is a workaround to get the current location as location property diff --git a/ios/Classes/Keys.swift b/ios/Classes/Keys.swift index de7738b..087ac63 100644 --- a/ios/Classes/Keys.swift +++ b/ios/Classes/Keys.swift @@ -27,6 +27,7 @@ struct Keys { static let name = "name" static let properties = "properties" + static let title = "title" } struct Environment{ diff --git a/ios/Classes/SwiftCustomerIoPlugin.swift b/ios/Classes/SwiftCustomerIoPlugin.swift index c86bab5..8f85f0a 100644 --- a/ios/Classes/SwiftCustomerIoPlugin.swift +++ b/ios/Classes/SwiftCustomerIoPlugin.swift @@ -106,20 +106,17 @@ public class SwiftCustomerIoPlugin: NSObject, FlutterPlugin { } func screen(params : Dictionary) { - // TODO: Fix screen implementation - /* - guard let name = params[Keys.Tracking.eventName] as? String - else { - return - } - - guard let attributes = params[Keys.Tracking.attributes] as? Dictionary else{ - CustomerIO.shared.screen(name: name) - return - } - - CustomerIO.shared.screen(name: name, data: attributes) - */ + guard let title = params[Keys.Tracking.title] as? String else { + logger.error("Missing screen title in: \(params) for key: \(Keys.Tracking.title)") + return + } + + guard let properties = params[Keys.Tracking.properties] as? Dictionary else { + CustomerIO.shared.screen(title: title) + return + } + + CustomerIO.shared.screen(title: title, properties: properties) } diff --git a/lib/customer_io.dart b/lib/customer_io.dart index 76cb517..f5ea1ec 100644 --- a/lib/customer_io.dart +++ b/lib/customer_io.dart @@ -7,6 +7,7 @@ import 'customer_io_config.dart'; import 'customer_io_enums.dart'; import 'customer_io_inapp.dart'; import 'customer_io_platform_interface.dart'; +import 'extensions/map_extensions.dart'; import 'messaging_in_app/platform_interface.dart'; import 'messaging_push/platform_interface.dart'; @@ -88,9 +89,9 @@ class CustomerIO { /// @param userId unique identifier for a profile /// @param traits (Optional) params to set profile attributes void identify( - {required String userId, - Map traits = const {}}) { - return _platform.identify(userId: userId, traits: traits); + {required String userId, Map traits = const {}}) { + return _platform.identify( + userId: userId, traits: traits.excludeNullValues()); } /// Call this function to stop identifying a person. @@ -108,7 +109,8 @@ class CustomerIO { /// @param properties (Optional) params to be sent with event void track( {required String name, Map properties = const {}}) { - return _platform.track(name: name, properties: properties); + return _platform.track( + name: name, properties: properties.excludeNullValues()); } /// Track a push metric @@ -131,8 +133,9 @@ class CustomerIO { /// @param name name of the screen user visited /// @param attributes (Optional) params to be sent with event void screen( - {required String name, Map attributes = const {}}) { - return _platform.screen(name: name, attributes: attributes); + {required String title, Map properties = const {}}) { + return _platform.screen( + title: title, properties: properties.excludeNullValues()); } /// Use this function to send custom device attributes @@ -148,7 +151,8 @@ class CustomerIO { /// /// @param attributes additional attributes for a user profile void setProfileAttributes({required Map attributes}) { - return _platform.setProfileAttributes(traits: attributes); + return _platform.setProfileAttributes( + traits: attributes.excludeNullValues()); } /// Subscribes to an in-app event listener. diff --git a/lib/customer_io_const.dart b/lib/customer_io_const.dart index 8478dc5..e0d0d2f 100644 --- a/lib/customer_io_const.dart +++ b/lib/customer_io_const.dart @@ -28,4 +28,5 @@ class TrackingConsts { static const String name = "name"; static const String properties = "properties"; + static const String title = "title"; } diff --git a/lib/customer_io_method_channel.dart b/lib/customer_io_method_channel.dart index 4089c00..490dac2 100644 --- a/lib/customer_io_method_channel.dart +++ b/lib/customer_io_method_channel.dart @@ -128,12 +128,12 @@ class CustomerIOMethodChannel extends CustomerIOPlatform { /// Track screen events to record the screens a user visits @override void screen( - {required String name, - Map attributes = const {}}) async { + {required String title, + Map properties = const {}}) async { try { final payload = { - TrackingConsts.eventName: name, - TrackingConsts.attributes: attributes + TrackingConsts.title: title, + TrackingConsts.properties: properties }; methodChannel.invokeMethod(MethodConsts.screen, payload); } on PlatformException catch (exception) { diff --git a/lib/customer_io_platform_interface.dart b/lib/customer_io_platform_interface.dart index 1e44480..2109468 100644 --- a/lib/customer_io_platform_interface.dart +++ b/lib/customer_io_platform_interface.dart @@ -61,7 +61,7 @@ abstract class CustomerIOPlatform extends PlatformInterface { } void screen( - {required String name, Map attributes = const {}}) { + {required String title, Map properties = const {}}) { throw UnimplementedError('screen() has not been implemented.'); } diff --git a/lib/extensions/map_extensions.dart b/lib/extensions/map_extensions.dart new file mode 100644 index 0000000..a8baaef --- /dev/null +++ b/lib/extensions/map_extensions.dart @@ -0,0 +1,7 @@ +/// Extensions for [Map] class that provide additional functionality and convenience methods. +extension CustomerIOMapExtension on Map { + /// Returns a new map with entries that have non-null values, excluding null values. + Map excludeNullValues() { + return Map.fromEntries(entries.where((entry) => entry.value != null)); + } +} diff --git a/test/customer_io_method_channel_test.dart b/test/customer_io_method_channel_test.dart index cd92143..315318c 100644 --- a/test/customer_io_method_channel_test.dart +++ b/test/customer_io_method_channel_test.dart @@ -105,12 +105,12 @@ void main() { test('screen() should call platform method with correct arguments', () async { final Map args = { - 'eventName': 'screen_event', - 'attributes': {'screenName': '你好'} + 'title': 'screen_event', + 'properties': {'screenName': '你好'} }; final customerIO = CustomerIOMethodChannel(); - customerIO.screen(name: args['eventName'], attributes: args['attributes']); + customerIO.screen(title: args['title'], properties: args['properties']); expectMethodInvocationArguments('screen', args); }); diff --git a/test/customer_io_test.dart b/test/customer_io_test.dart index 1d7b2ab..c76d2e7 100644 --- a/test/customer_io_test.dart +++ b/test/customer_io_test.dart @@ -149,6 +149,19 @@ void main() { ); }); + test('screen() correct arguments are passed', () { + const title = 'checkout'; + final givenProperties = {'source': 'push'}; + CustomerIO.instance.screen(title: title, properties: givenProperties); + expect( + verify(mockPlatform.screen( + title: captureAnyNamed("title"), + properties: captureAnyNamed("properties"), + )).captured, + [title, givenProperties], + ); + }); + test('trackMetric() calls platform', () { const deliveryID = '123'; const deviceToken = 'abc'; diff --git a/test/customer_io_test.mocks.dart b/test/customer_io_test.mocks.dart index 768a9cd..c49c303 100644 --- a/test/customer_io_test.mocks.dart +++ b/test/customer_io_test.mocks.dart @@ -124,16 +124,16 @@ class MockTestCustomerIoPlatform extends _i1.Mock ); @override void screen({ - required String? name, - Map? attributes = const {}, + required String? title, + Map? properties = const {}, }) => super.noSuchMethod( Invocation.method( #screen, [], { - #name: name, - #attributes: attributes, + #title: title, + #properties: properties, }, ), returnValueForMissingStub: null,