diff --git a/android/src/main/kotlin/io/customer/customer_io/CustomerIOExtensions.kt b/android/src/main/kotlin/io/customer/customer_io/CustomerIOExtensions.kt index 138a65a..b6256fc 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIOExtensions.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIOExtensions.kt @@ -3,17 +3,6 @@ package io.customer.customer_io import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -/** - * Returns the value corresponding to the given key after casting to the generic type provided, or - * null if such key is not present in the map or value cannot be casted to the given type. - */ -internal inline fun Map.getAsTypeOrNull(key: String): T? { - if (containsKey(key)) { - return get(key) as? T - } - return null -} - /** * Invokes lambda method that can be used to call matching native method conveniently. The lambda * expression receives function parameters as arguments and should return the desired result. Any diff --git a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt b/android/src/main/kotlin/io/customer/customer_io/CustomerIOPlugin.kt similarity index 82% rename from android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt rename to android/src/main/kotlin/io/customer/customer_io/CustomerIOPlugin.kt index 1a0f400..2707648 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIOPlugin.kt @@ -4,9 +4,11 @@ import android.app.Activity import android.app.Application import android.content.Context import androidx.annotation.NonNull +import io.customer.customer_io.bridge.NativeModuleBridge import io.customer.customer_io.constant.Keys import io.customer.customer_io.messaginginapp.CustomerIOInAppMessaging import io.customer.customer_io.messagingpush.CustomerIOPushMessaging +import io.customer.customer_io.utils.getAs import io.customer.messaginginapp.type.InAppEventListener import io.customer.messaginginapp.type.InAppMessage import io.customer.sdk.CustomerIO @@ -30,7 +32,7 @@ import java.lang.ref.WeakReference * Android implementation of plugin that will let Flutter developers to * interact with a Android platform * */ -class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { +class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { /// 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 @@ -39,7 +41,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var context: Context private var activity: WeakReference? = null - private lateinit var modules: List + private lateinit var modules: List private val logger: Logger = SDKComponent.logger @@ -154,8 +156,8 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun identify(params: Map) { - val userId = params.getAsTypeOrNull(Keys.Tracking.USER_ID) - val traits = params.getAsTypeOrNull>(Keys.Tracking.TRAITS) ?: emptyMap() + val userId = params.getAs(Keys.Tracking.USER_ID) + val traits = params.getAs>(Keys.Tracking.TRAITS) ?: emptyMap() if (userId == null && traits.isEmpty()) { logger.error("Please provide either an ID or traits to identify.") @@ -172,10 +174,10 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun track(params: Map) { - val name = requireNotNull(params.getAsTypeOrNull(Keys.Tracking.NAME)) { + val name = requireNotNull(params.getAs(Keys.Tracking.NAME)) { "Event name is missing in params: $params" } - val properties = params.getAsTypeOrNull>(Keys.Tracking.PROPERTIES) + val properties = params.getAs>(Keys.Tracking.PROPERTIES) if (properties.isNullOrEmpty()) { CustomerIO.instance().track(name) @@ -185,16 +187,16 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun registerDeviceToken(params: Map) { - val token = requireNotNull(params.getAsTypeOrNull(Keys.Tracking.TOKEN)) { + val token = requireNotNull(params.getAs(Keys.Tracking.TOKEN)) { "Device token is missing in params: $params" } CustomerIO.instance().registerDeviceToken(token) } private fun trackMetric(params: Map) { - val deliveryId = params.getAsTypeOrNull(Keys.Tracking.DELIVERY_ID) - val deliveryToken = params.getAsTypeOrNull(Keys.Tracking.DELIVERY_TOKEN) - val eventName = params.getAsTypeOrNull(Keys.Tracking.METRIC_EVENT) + val deliveryId = params.getAs(Keys.Tracking.DELIVERY_ID) + val deliveryToken = params.getAs(Keys.Tracking.DELIVERY_TOKEN) + val eventName = params.getAs(Keys.Tracking.METRIC_EVENT) if (deliveryId == null || deliveryToken == null || eventName == null) { throw IllegalArgumentException("Missing required parameters") @@ -212,7 +214,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun setDeviceAttributes(params: Map) { - val attributes = params.getAsTypeOrNull>(Keys.Tracking.ATTRIBUTES) + val attributes = params.getAs>(Keys.Tracking.ATTRIBUTES) if (attributes.isNullOrEmpty()) { logger.error("Device attributes are missing in params: $params") @@ -223,7 +225,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun setProfileAttributes(params: Map) { - val attributes = params.getAsTypeOrNull>(Keys.Tracking.ATTRIBUTES) + val attributes = params.getAs>(Keys.Tracking.ATTRIBUTES) if (attributes.isNullOrEmpty()) { logger.error("Profile attributes are missing in params: $params") @@ -234,10 +236,10 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun screen(params: Map) { - val title = requireNotNull(params.getAsTypeOrNull(Keys.Tracking.TITLE)) { + val title = requireNotNull(params.getAs(Keys.Tracking.TITLE)) { "Screen title is missing in params: $params" } - val properties = params.getAsTypeOrNull>(Keys.Tracking.PROPERTIES) + val properties = params.getAs>(Keys.Tracking.PROPERTIES) if (properties.isNullOrEmpty()) { CustomerIO.instance().screen(title) @@ -248,12 +250,12 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private fun initialize(args: Map): kotlin.Result = runCatching { val application: Application = context.applicationContext as Application - val cdpApiKey = requireNotNull(args.getAsTypeOrNull("cdpApiKey")) { + val cdpApiKey = requireNotNull(args.getAs("cdpApiKey")) { "CDP API Key is required to initialize Customer.io" } - val logLevelRawValue = args.getAsTypeOrNull("logLevel") - val regionRawValue = args.getAsTypeOrNull("region") + val logLevelRawValue = args.getAs("logLevel") + val regionRawValue = args.getAs("region") val givenRegion = regionRawValue.let { Region.getRegion(it) } CustomerIOBuilder( @@ -263,19 +265,18 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { logLevelRawValue?.let { logLevel(CioLogLevel.getLogLevel(it)) } regionRawValue?.let { region(givenRegion) } - args.getAsTypeOrNull("migrationSiteId")?.let(::migrationSiteId) - args.getAsTypeOrNull("autoTrackDeviceAttributes") - ?.let(::autoTrackDeviceAttributes) - args.getAsTypeOrNull("trackApplicationLifecycleEvents") + args.getAs("migrationSiteId")?.let(::migrationSiteId) + args.getAs("autoTrackDeviceAttributes")?.let(::autoTrackDeviceAttributes) + args.getAs("trackApplicationLifecycleEvents") ?.let(::trackApplicationLifecycleEvents) - args.getAsTypeOrNull("flushAt")?.let(::flushAt) - args.getAsTypeOrNull("flushInterval")?.let(::flushInterval) + args.getAs("flushAt")?.let(::flushAt) + args.getAs("flushInterval")?.let(::flushInterval) - args.getAsTypeOrNull("apiHost")?.let(::apiHost) - args.getAsTypeOrNull("cdnHost")?.let(::cdnHost) + args.getAs("apiHost")?.let(::apiHost) + args.getAs("cdnHost")?.let(::cdnHost) // Configure in-app messaging module based on config provided by customer app - args.getAsTypeOrNull>(key = "inApp")?.let { inAppConfig -> + args.getAs>(key = "inApp")?.let { inAppConfig -> CustomerIOInAppMessaging.addNativeModuleFromConfig( builder = this, config = inAppConfig, @@ -283,7 +284,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { ) } // Configure push messaging module based on config provided by customer app - args.getAsTypeOrNull>(key = "push").let { pushConfig -> + args.getAs>(key = "push").let { pushConfig -> CustomerIOPushMessaging.addNativeModuleFromConfig( builder = this, config = pushConfig ?: emptyMap() diff --git a/android/src/main/kotlin/io/customer/customer_io/CustomerIOPluginModule.kt b/android/src/main/kotlin/io/customer/customer_io/bridge/NativeModuleBridge.kt similarity index 90% rename from android/src/main/kotlin/io/customer/customer_io/CustomerIOPluginModule.kt rename to android/src/main/kotlin/io/customer/customer_io/bridge/NativeModuleBridge.kt index 403a339..2fb7a4e 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIOPluginModule.kt +++ b/android/src/main/kotlin/io/customer/customer_io/bridge/NativeModuleBridge.kt @@ -1,4 +1,4 @@ -package io.customer.customer_io +package io.customer.customer_io.bridge import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodChannel @@ -8,7 +8,7 @@ import io.flutter.plugin.common.MethodChannel * should be treated as module in Flutter SDK and should be used to hold all relevant methods at * single place. */ -internal interface CustomerIOPluginModule : MethodChannel.MethodCallHandler { +internal interface NativeModuleBridge : MethodChannel.MethodCallHandler { /** * Unique name of module to identify between other modules */ diff --git a/android/src/main/kotlin/io/customer/customer_io/messaginginapp/CustomerIOInAppMessaging.kt b/android/src/main/kotlin/io/customer/customer_io/messaginginapp/CustomerIOInAppMessaging.kt index afb8b3e..76cd4d6 100644 --- a/android/src/main/kotlin/io/customer/customer_io/messaginginapp/CustomerIOInAppMessaging.kt +++ b/android/src/main/kotlin/io/customer/customer_io/messaginginapp/CustomerIOInAppMessaging.kt @@ -1,9 +1,9 @@ package io.customer.customer_io.messaginginapp -import io.customer.customer_io.CustomerIOPluginModule +import io.customer.customer_io.bridge.NativeModuleBridge import io.customer.customer_io.constant.Keys -import io.customer.customer_io.getAsTypeOrNull import io.customer.customer_io.invokeNative +import io.customer.customer_io.utils.getAs import io.customer.messaginginapp.MessagingInAppModuleConfig import io.customer.messaginginapp.ModuleMessagingInApp import io.customer.messaginginapp.di.inAppMessaging @@ -21,7 +21,7 @@ import io.flutter.plugin.common.MethodChannel */ internal class CustomerIOInAppMessaging( pluginBinding: FlutterPlugin.FlutterPluginBinding, -) : CustomerIOPluginModule, MethodChannel.MethodCallHandler { +) : NativeModuleBridge, MethodChannel.MethodCallHandler { override val moduleName: String = "InAppMessaging" override val flutterCommunicationChannel: MethodChannel = MethodChannel(pluginBinding.binaryMessenger, "customer_io_messaging_in_app") @@ -54,7 +54,7 @@ internal class CustomerIOInAppMessaging( config: Map, region: Region ) { - val siteId = config.getAsTypeOrNull("siteId") + val siteId = config.getAs("siteId") if (siteId.isNullOrBlank()) { SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module") return diff --git a/android/src/main/kotlin/io/customer/customer_io/messagingpush/CustomerIOPushMessaging.kt b/android/src/main/kotlin/io/customer/customer_io/messagingpush/CustomerIOPushMessaging.kt index 0e6c560..0511a3f 100644 --- a/android/src/main/kotlin/io/customer/customer_io/messagingpush/CustomerIOPushMessaging.kt +++ b/android/src/main/kotlin/io/customer/customer_io/messagingpush/CustomerIOPushMessaging.kt @@ -1,10 +1,11 @@ package io.customer.customer_io.messagingpush import android.content.Context -import io.customer.customer_io.CustomerIOPluginModule +import io.customer.customer_io.bridge.NativeModuleBridge import io.customer.customer_io.constant.Keys -import io.customer.customer_io.getAsTypeOrNull import io.customer.customer_io.invokeNative +import io.customer.customer_io.utils.getAs +import io.customer.customer_io.utils.takeIfNotBlank import io.customer.messagingpush.CustomerIOFirebaseMessagingService import io.customer.messagingpush.MessagingPushModuleConfig import io.customer.messagingpush.ModuleMessagingPushFCM @@ -16,7 +17,7 @@ import io.customer.sdk.core.util.Logger import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import java.util.* +import java.util.UUID /** * Flutter module implementation for messaging push module in native SDKs. All functionality @@ -24,7 +25,7 @@ import java.util.* */ internal class CustomerIOPushMessaging( pluginBinding: FlutterPlugin.FlutterPluginBinding, -) : CustomerIOPluginModule, MethodChannel.MethodCallHandler { +) : NativeModuleBridge, MethodChannel.MethodCallHandler { override val moduleName: String = "PushMessaging" private val applicationContext: Context = pluginBinding.applicationContext override val flutterCommunicationChannel: MethodChannel = @@ -42,8 +43,8 @@ internal class CustomerIOPushMessaging( Keys.Methods.ON_MESSAGE_RECEIVED -> { call.invokeNative(result) { args -> return@invokeNative onMessageReceived( - message = args.getAsTypeOrNull>("message"), - handleNotificationTrigger = args.getAsTypeOrNull("handleNotificationTrigger") + message = args.getAs>("message"), + handleNotificationTrigger = args.getAs("handleNotificationTrigger") ) } } @@ -75,7 +76,7 @@ internal class CustomerIOPushMessaging( } // Generate destination string, see docs on receiver method for more details - val destination = (message["to"] as? String)?.takeIf { it.isNotBlank() } + val destination = (message["to"] as? String)?.takeIfNotBlank() ?: UUID.randomUUID().toString() return CustomerIOFirebaseMessagingService.onMessageReceived( context = applicationContext, @@ -100,14 +101,13 @@ internal class CustomerIOPushMessaging( builder: CustomerIOBuilder, config: Map ) { - val androidConfig = - config.getAsTypeOrNull>(key = "android") ?: emptyMap() + val androidConfig = config.getAs>(key = "android") ?: emptyMap() // Prefer `android` object for push configurations as it's more specific to Android // For common push configurations, use `config` object instead of `android` // Default push click behavior is to prevent restart of activity in Flutter apps - val pushClickBehavior = androidConfig.getAsTypeOrNull("pushClickBehavior") - ?.takeIf { it.isNotBlank() } + val pushClickBehavior = androidConfig.getAs("pushClickBehavior") + ?.takeIfNotBlank() ?.let { value -> runCatching { enumValueOf(value) }.getOrNull() } ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART diff --git a/android/src/main/kotlin/io/customer/customer_io/messagingpush/Extensions.kt b/android/src/main/kotlin/io/customer/customer_io/messagingpush/Extensions.kt index b51fa91..6b1f071 100644 --- a/android/src/main/kotlin/io/customer/customer_io/messagingpush/Extensions.kt +++ b/android/src/main/kotlin/io/customer/customer_io/messagingpush/Extensions.kt @@ -1,7 +1,7 @@ package io.customer.customer_io.messagingpush import com.google.firebase.messaging.RemoteMessage -import io.customer.customer_io.getAsTypeOrNull +import io.customer.customer_io.utils.getAs /** * Safely transforms any value to string @@ -23,8 +23,8 @@ private fun Any.toStringOrNull(): String? = try { * string for it. */ internal fun Map.toFCMRemoteMessage(destination: String): RemoteMessage { - val notification = getAsTypeOrNull>("notification") - val data = getAsTypeOrNull>("data") + val notification = getAs>("notification") + val data = getAs>("data") val messageParams = buildMap { notification?.let { result -> putAll(result) } // Adding `data` after `notification` so `data` params take more value as we mainly use @@ -42,10 +42,10 @@ internal fun Map.toFCMRemoteMessage(destination: String): RemoteMes value.toStringOrNull()?.let { v -> addData(key, v) } } } - getAsTypeOrNull("messageId")?.let { id -> setMessageId(id) } - getAsTypeOrNull("messageType")?.let { type -> setMessageType(type) } - getAsTypeOrNull("collapseKey")?.let { key -> setCollapseKey(key) } - getAsTypeOrNull("ttl")?.let { time -> ttl = time } + getAs("messageId")?.let { id -> setMessageId(id) } + getAs("messageType")?.let { type -> setMessageType(type) } + getAs("collapseKey")?.let { key -> setCollapseKey(key) } + getAs("ttl")?.let { time -> ttl = time } return@with build() } } diff --git a/android/src/main/kotlin/io/customer/customer_io/utils/MapExtensions.kt b/android/src/main/kotlin/io/customer/customer_io/utils/MapExtensions.kt new file mode 100644 index 0000000..d0a2785 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/utils/MapExtensions.kt @@ -0,0 +1,12 @@ +package io.customer.customer_io.utils + +/** + * Returns the value corresponding to the given key after casting to the generic type provided, or + * null if such key is not present in the map or value cannot be casted to the given type. + */ +internal inline fun Map.getAs(key: String): T? { + if (containsKey(key)) { + return get(key) as? T + } + return null +} diff --git a/android/src/main/kotlin/io/customer/customer_io/utils/StringExtensions.kt b/android/src/main/kotlin/io/customer/customer_io/utils/StringExtensions.kt new file mode 100644 index 0000000..42ac132 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/utils/StringExtensions.kt @@ -0,0 +1,6 @@ +package io.customer.customer_io.utils + +/** + * Extension function to return the string if it is not null or blank. + */ +internal fun String?.takeIfNotBlank(): String? = takeIf { !it.isNullOrBlank() } diff --git a/pubspec.yaml b/pubspec.yaml index d91304d..c77b0b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ flutter: platforms: android: package: io.customer.customer_io - pluginClass: CustomerIoPlugin + pluginClass: CustomerIOPlugin ios: pluginClass: CustomerIoPlugin native_sdk_version: 3.5.1