Skip to content

Commit

Permalink
chore: added in-app event listeners (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shahroz16 authored Nov 25, 2024
1 parent d66b25c commit bd03be8
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 182 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package io.customer.customer_io

import io.customer.sdk.CustomerIOBuilder
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodChannel

/**
* Module class corresponds to modules concept in native SDKs. Any module added to native SDKs
* 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 CustomerIOPluginModule : MethodChannel.MethodCallHandler, ActivityAware {
/**
* Unique name of module to identify between other modules
*/
Expand Down Expand Up @@ -36,4 +39,14 @@ internal interface CustomerIOPluginModule : MethodChannel.MethodCallHandler {
fun onDetachedFromEngine() {
flutterCommunicationChannel.setMethodCallHandler(null)
}

fun configureModule(builder: CustomerIOBuilder, config: Map<String, Any>)

override fun onDetachedFromActivity() {}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

override fun onDetachedFromActivityForConfigChanges() {}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {}
}
90 changes: 28 additions & 62 deletions android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package io.customer.customer_io

import android.app.Activity
import android.app.Application
import android.content.Context
import androidx.annotation.NonNull
import io.customer.customer_io.constant.Keys
import io.customer.customer_io.messaginginapp.CustomerIOInAppMessaging
import io.customer.customer_io.messagingpush.CustomerIOPushMessaging
import io.customer.messaginginapp.type.InAppEventListener
import io.customer.messaginginapp.type.InAppMessage
import io.customer.sdk.CustomerIO
import io.customer.sdk.CustomerIOBuilder
import io.customer.sdk.core.di.SDKComponent
Expand All @@ -24,7 +21,6 @@ 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

/**
* Android implementation of plugin that will let Flutter developers to
Expand All @@ -37,28 +33,11 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
/// when the Flutter Engine is detached from the Activity
private lateinit var flutterCommunicationChannel: MethodChannel
private lateinit var context: Context
private var activity: WeakReference<Activity>? = null

private lateinit var modules: List<CustomerIOPluginModule>

private val logger: Logger = SDKComponent.logger

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
this.activity = WeakReference(binding.activity)
}

override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}

override fun onDetachedFromActivity() {
this.activity = null
}

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
flutterCommunicationChannel =
Expand Down Expand Up @@ -276,18 +255,21 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
args.getAsTypeOrNull<String>("cdnHost")?.let(::cdnHost)
// Configure in-app messaging module based on config provided by customer app
args.getAsTypeOrNull<Map<String, Any>>(key = "inApp")?.let { inAppConfig ->
CustomerIOInAppMessaging.addNativeModuleFromConfig(
builder = this,
config = inAppConfig,
region = givenRegion
)
modules.filterIsInstance<CustomerIOInAppMessaging>().forEach {
it.configureModule(
builder = this,
config = inAppConfig.plus("region" to givenRegion),
)
}
}
// Configure push messaging module based on config provided by customer app
args.getAsTypeOrNull<Map<String, Any>>(key = "push").let { pushConfig ->
CustomerIOPushMessaging.addNativeModuleFromConfig(
builder = this,
config = pushConfig ?: emptyMap()
)
modules.filterIsInstance<CustomerIOPushMessaging>().forEach {
it.configureModule(
builder = this,
config = pushConfig ?: emptyMap()
)
}
}
}.build()

Expand All @@ -303,44 +285,28 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
it.onDetachedFromEngine()
}
}
}

class CustomerIOInAppEventListener(private val invokeMethod: (String, Any?) -> Unit) :
InAppEventListener {
override fun errorWithMessage(message: InAppMessage) {
invokeMethod(
"errorWithMessage", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
modules.forEach {
it.onAttachedToActivity(binding)
}
}

override fun messageActionTaken(
message: InAppMessage, actionValue: String, actionName: String
) {
invokeMethod(
"messageActionTaken", mapOf(
"messageId" to message.messageId,
"deliveryId" to message.deliveryId,
"actionValue" to actionValue,
"actionName" to actionName
)
)
override fun onDetachedFromActivityForConfigChanges() {
modules.forEach {
it.onDetachedFromActivityForConfigChanges()
}
}

override fun messageDismissed(message: InAppMessage) {
invokeMethod(
"messageDismissed", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
modules.forEach {
it.onReattachedToActivityForConfigChanges(binding)
}
}

override fun messageShown(message: InAppMessage) {
invokeMethod(
"messageShown", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
override fun onDetachedFromActivity() {
modules.forEach {
it.onDetachedFromActivity()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
package io.customer.customer_io.messaginginapp

import android.app.Activity
import io.customer.customer_io.CustomerIOPluginModule
import io.customer.customer_io.constant.Keys
import io.customer.customer_io.getAsTypeOrNull
import io.customer.customer_io.invokeNative
import io.customer.messaginginapp.MessagingInAppModuleConfig
import io.customer.messaginginapp.ModuleMessagingInApp
import io.customer.messaginginapp.di.inAppMessaging
import io.customer.messaginginapp.type.InAppEventListener
import io.customer.messaginginapp.type.InAppMessage
import io.customer.sdk.CustomerIO
import io.customer.sdk.CustomerIOBuilder
import io.customer.sdk.core.di.SDKComponent
import io.customer.sdk.data.model.Region
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.lang.ref.WeakReference

/**
* Flutter module implementation for messaging in-app module in native SDKs. All functionality
* linked with the module should be placed here.
*/
internal class CustomerIOInAppMessaging(
pluginBinding: FlutterPlugin.FlutterPluginBinding,
) : CustomerIOPluginModule, MethodChannel.MethodCallHandler {
) : CustomerIOPluginModule, MethodChannel.MethodCallHandler, ActivityAware {
override val moduleName: String = "InAppMessaging"
override val flutterCommunicationChannel: MethodChannel =
MethodChannel(pluginBinding.binaryMessenger, "customer_io_messaging_in_app")
private var activity: WeakReference<Activity>? = null

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
this.activity = WeakReference(binding.activity)
}

override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}

override fun onDetachedFromActivity() {
this.activity = null
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
Expand All @@ -40,30 +63,74 @@ internal class CustomerIOInAppMessaging(
}
}

companion object {
/**
* Adds in-app module to native Android SDK based on the configuration provided by
* customer app.
*
* @param builder instance of CustomerIOBuilder to add push messaging module.
* @param config configuration provided by customer app for in-app messaging module.
* @param region region of the customer app.
*/
internal fun addNativeModuleFromConfig(
builder: CustomerIOBuilder,
config: Map<String, Any>,
region: Region
) {
val siteId = config.getAsTypeOrNull<String>("siteId")
if (siteId.isNullOrBlank()) {
SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module")
return
}
val module = ModuleMessagingInApp(
MessagingInAppModuleConfig.Builder(siteId = siteId, region = region).build(),
)
builder.addCustomerIOModule(module)
/**
* Adds in-app module to native Android SDK based on the configuration provided by
* customer app.
*
* @param builder instance of CustomerIOBuilder to add push messaging module.
* @param config configuration provided by customer app for in-app messaging module.
*/
override fun configureModule(
builder: CustomerIOBuilder,
config: Map<String, Any>
) {
val siteId = config.getAsTypeOrNull<String>("siteId")
val regionRawValue = config.getAsTypeOrNull<String>("region")
val givenRegion = regionRawValue.let { Region.getRegion(it) }

if (siteId.isNullOrBlank()) {
SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module")
return
}
val module = ModuleMessagingInApp(
MessagingInAppModuleConfig.Builder(siteId = siteId, region = givenRegion)
.setEventListener(CustomerIOInAppEventListener { method, args ->
this.activity?.get()?.runOnUiThread {
flutterCommunicationChannel.invokeMethod(method, args)
}
})
.build(),
)
builder.addCustomerIOModule(module)
}
}

class CustomerIOInAppEventListener(private val invokeMethod: (String, Any?) -> Unit) :
InAppEventListener {
override fun errorWithMessage(message: InAppMessage) {
invokeMethod(
"errorWithMessage", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
}

override fun messageActionTaken(
message: InAppMessage, actionValue: String, actionName: String
) {
invokeMethod(
"messageActionTaken", mapOf(
"messageId" to message.messageId,
"deliveryId" to message.deliveryId,
"actionValue" to actionValue,
"actionName" to actionName
)
)
}

override fun messageDismissed(message: InAppMessage) {
invokeMethod(
"messageDismissed", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
}

override fun messageShown(message: InAppMessage) {
invokeMethod(
"messageShown", mapOf(
"messageId" to message.messageId, "deliveryId" to message.deliveryId
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,36 +88,34 @@ internal class CustomerIOPushMessaging(
}
}

companion object {
/**
* Adds push messaging module to native Android SDK based on the configuration provided by
* customer app.
*
* @param builder instance of CustomerIOBuilder to add push messaging module.
* @param config configuration provided by customer app for push messaging module.
*/
internal fun addNativeModuleFromConfig(
builder: CustomerIOBuilder,
config: Map<String, Any>
) {
val androidConfig =
config.getAsTypeOrNull<Map<String, Any>>(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`
/**
* Adds push messaging module to native Android SDK based on the configuration provided by
* customer app.
*
* @param builder instance of CustomerIOBuilder to add push messaging module.
* @param config configuration provided by customer app for push messaging module.
*/
override fun configureModule(
builder: CustomerIOBuilder,
config: Map<String, Any>
) {
val androidConfig =
config.getAsTypeOrNull<Map<String, Any>>(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<String>("pushClickBehavior")
?.takeIf { it.isNotBlank() }
?.let { value ->
runCatching { enumValueOf<PushClickBehavior>(value) }.getOrNull()
} ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART
// Default push click behavior is to prevent restart of activity in Flutter apps
val pushClickBehavior = androidConfig.getAsTypeOrNull<String>("pushClickBehavior")
?.takeIf { it.isNotBlank() }
?.let { value ->
runCatching { enumValueOf<PushClickBehavior>(value) }.getOrNull()
} ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART

val module = ModuleMessagingPushFCM(
moduleConfig = MessagingPushModuleConfig.Builder().apply {
setPushClickBehavior(pushClickBehavior = pushClickBehavior)
}.build(),
)
builder.addCustomerIOModule(module)
}
val module = ModuleMessagingPushFCM(
moduleConfig = MessagingPushModuleConfig.Builder().apply {
setPushClickBehavior(pushClickBehavior = pushClickBehavior)
}.build(),
)
builder.addCustomerIOModule(module)
}
}
Loading

0 comments on commit bd03be8

Please sign in to comment.