Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor native android call forwarding #182

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

123 changes: 41 additions & 82 deletions android/src/main/kotlin/io/customer/customer_io/CustomerIOPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ 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.bridge.nativeMapArgs
import io.customer.customer_io.bridge.nativeNoArgs
import io.customer.customer_io.messaginginapp.CustomerIOInAppMessaging
import io.customer.customer_io.messagingpush.CustomerIOPushMessaging
import io.customer.customer_io.utils.getAs
Expand Down Expand Up @@ -58,85 +59,28 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}

private fun MethodCall.toNativeMethodCall(
result: Result, performAction: (params: Map<String, Any>) -> Unit
) {
try {
val params = this.arguments as? Map<String, Any> ?: emptyMap()
performAction(params)
result.success(true)
} catch (e: Exception) {
result.error(this.method, e.localizedMessage, null)
}
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
Keys.Methods.INITIALIZE -> {
call.toNativeMethodCall(result) {
initialize(it)
}
}

Keys.Methods.IDENTIFY -> {
call.toNativeMethodCall(result) {
identify(it)
}
}

Keys.Methods.SCREEN -> {
call.toNativeMethodCall(result) {
screen(it)
}
}

Keys.Methods.TRACK -> {
call.toNativeMethodCall(result) {
track(it)
}
}

Keys.Methods.TRACK_METRIC -> {
call.toNativeMethodCall(result) {
trackMetric(it)
}
}

Keys.Methods.REGISTER_DEVICE_TOKEN -> {
call.toNativeMethodCall(result) {
registerDeviceToken(it)
}
}

Keys.Methods.SET_DEVICE_ATTRIBUTES -> {
call.toNativeMethodCall(result) {
setDeviceAttributes(it)
}
}

Keys.Methods.SET_PROFILE_ATTRIBUTES -> {
call.toNativeMethodCall(result) {
setProfileAttributes(it)
}
}

Keys.Methods.CLEAR_IDENTIFY -> {
clearIdentity()
}

else -> {
result.notImplemented()
}
"clearIdentify" -> call.nativeNoArgs(result, ::clearIdentify)
"identify" -> call.nativeMapArgs(result, ::identify)
"initialize" -> call.nativeMapArgs(result, ::initialize)
"registerDeviceToken" -> call.nativeMapArgs(result, ::registerDeviceToken)
"screen" -> call.nativeMapArgs(result, ::screen)
"setDeviceAttributes" -> call.nativeMapArgs(result, ::setDeviceAttributes)
"setProfileAttributes" -> call.nativeMapArgs(result, ::setProfileAttributes)
"track" -> call.nativeMapArgs(result, ::track)
"trackMetric" -> call.nativeMapArgs(result, ::trackMetric)
else -> result.notImplemented()
}
}

private fun clearIdentity() {
private fun clearIdentify() {
CustomerIO.instance().clearIdentify()
}

private fun identify(params: Map<String, Any>) {
val userId = params.getAs<String>(Keys.Tracking.USER_ID)
val traits = params.getAs<Map<String, Any>>(Keys.Tracking.TRAITS) ?: emptyMap()
val userId = params.getAs<String>(Args.USER_ID)
val traits = params.getAs<Map<String, Any>>(Args.TRAITS) ?: emptyMap()

if (userId == null && traits.isEmpty()) {
logger.error("Please provide either an ID or traits to identify.")
Expand All @@ -153,10 +97,10 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

private fun track(params: Map<String, Any>) {
val name = requireNotNull(params.getAs<String>(Keys.Tracking.NAME)) {
val name = requireNotNull(params.getAs<String>(Args.NAME)) {
"Event name is missing in params: $params"
}
val properties = params.getAs<Map<String, Any>>(Keys.Tracking.PROPERTIES)
val properties = params.getAs<Map<String, Any>>(Args.PROPERTIES)

if (properties.isNullOrEmpty()) {
CustomerIO.instance().track(name)
Expand All @@ -166,16 +110,16 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

private fun registerDeviceToken(params: Map<String, Any>) {
val token = requireNotNull(params.getAs<String>(Keys.Tracking.TOKEN)) {
val token = requireNotNull(params.getAs<String>(Args.TOKEN)) {
"Device token is missing in params: $params"
}
CustomerIO.instance().registerDeviceToken(token)
}

private fun trackMetric(params: Map<String, Any>) {
val deliveryId = params.getAs<String>(Keys.Tracking.DELIVERY_ID)
val deliveryToken = params.getAs<String>(Keys.Tracking.DELIVERY_TOKEN)
val eventName = params.getAs<String>(Keys.Tracking.METRIC_EVENT)
val deliveryId = params.getAs<String>(Args.DELIVERY_ID)
val deliveryToken = params.getAs<String>(Args.DELIVERY_TOKEN)
val eventName = params.getAs<String>(Args.METRIC_EVENT)

if (deliveryId == null || deliveryToken == null || eventName == null) {
throw IllegalArgumentException("Missing required parameters")
Expand All @@ -193,7 +137,7 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

private fun setDeviceAttributes(params: Map<String, Any>) {
val attributes = params.getAs<Map<String, Any>>(Keys.Tracking.ATTRIBUTES)
val attributes = params.getAs<Map<String, Any>>(Args.ATTRIBUTES)

if (attributes.isNullOrEmpty()) {
logger.error("Device attributes are missing in params: $params")
Expand All @@ -204,7 +148,7 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

private fun setProfileAttributes(params: Map<String, Any>) {
val attributes = params.getAs<Map<String, Any>>(Keys.Tracking.ATTRIBUTES)
val attributes = params.getAs<Map<String, Any>>(Args.ATTRIBUTES)

if (attributes.isNullOrEmpty()) {
logger.error("Profile attributes are missing in params: $params")
Expand All @@ -215,10 +159,10 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

private fun screen(params: Map<String, Any>) {
val title = requireNotNull(params.getAs<String>(Keys.Tracking.TITLE)) {
val title = requireNotNull(params.getAs<String>(Args.TITLE)) {
"Screen title is missing in params: $params"
}
val properties = params.getAs<Map<String, Any>>(Keys.Tracking.PROPERTIES)
val properties = params.getAs<Map<String, Any>>(Args.PROPERTIES)

if (properties.isNullOrEmpty()) {
CustomerIO.instance().screen(title)
Expand Down Expand Up @@ -310,4 +254,19 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
it.onDetachedFromActivity()
}
}

companion object {
object Args {
const val ATTRIBUTES = "attributes"
const val DELIVERY_ID = "deliveryId"
const val DELIVERY_TOKEN = "deliveryToken"
const val METRIC_EVENT = "metricEvent"
const val NAME = "name"
const val PROPERTIES = "properties"
const val TITLE = "title"
const val TOKEN = "token"
const val TRAITS = "traits"
const val USER_ID = "userId"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.customer.customer_io.bridge

import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

/**
* Handles native method call by transforming the arguments and invoking the handler.
*
* @param result The result object to send the response back to Flutter.
* @param transformer A function to transform the incoming arguments.
* @param handler A function to handle the transformed arguments and produce a result.
*
* - If the handler returns `Unit`, it sends `true` to Flutter to avoid errors.
* - Catches and sends any exceptions as errors to Flutter.
*/
internal fun <Arguments, Result> MethodCall.native(
result: MethodChannel.Result,
transformer: (Any?) -> Arguments,
handler: (Arguments) -> Result,
) = runCatching {
val args = transformer(arguments)
val response = handler(args)
// If the result is Unit, then return true to the Flutter side
// As returning Unit will throw an error on the Flutter side
result.success(
when (response) {
is Unit -> true
else -> response
}
)
}.onFailure { ex ->
result.error(method, ex.localizedMessage, ex)
}

/**
* Handles a native method call that requires no arguments.
*
* @param result The result object to send the response back to Flutter.
* @param handler A function to handle the call and produce a result.
*/
internal fun <Result> MethodCall.nativeNoArgs(
result: MethodChannel.Result,
handler: () -> Result,
) = native(result, { }, { handler() })

/**
* Handles a native method call with arguments passed as a map.
*
* @param result The result object to send the response back to Flutter.
* @param handler A function to handle the map arguments and produce a result.
*/
@Suppress("UNCHECKED_CAST")
internal fun <Result> MethodCall.nativeMapArgs(
result: MethodChannel.Result,
handler: (Map<String, Any>) -> Result,
) = native(result, { it as? Map<String, Any> ?: emptyMap() }, handler)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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.MethodCall
import io.flutter.plugin.common.MethodChannel

/**
Expand Down Expand Up @@ -40,6 +41,14 @@ internal interface NativeModuleBridge : MethodChannel.MethodCallHandler, Activit
flutterCommunicationChannel.setMethodCallHandler(null)
}

/**
* Handles incoming method calls from Flutter and invokes the appropriate native method handler.
* If the method is not implemented, the result is marked as not implemented.
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
result.notImplemented()
}

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

override fun onDetachedFromActivity() {}
Expand Down
35 changes: 0 additions & 35 deletions android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package io.customer.customer_io.messaginginapp

import android.app.Activity
import io.customer.customer_io.bridge.NativeModuleBridge
import io.customer.customer_io.constant.Keys
import io.customer.customer_io.invokeNative
import io.customer.customer_io.bridge.nativeNoArgs
import io.customer.customer_io.utils.getAs
import io.customer.messaginginapp.MessagingInAppModuleConfig
import io.customer.messaginginapp.ModuleMessagingInApp
Expand Down Expand Up @@ -51,18 +50,15 @@ internal class CustomerIOInAppMessaging(

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
Keys.Methods.DISMISS_MESSAGE -> {
call.invokeNative(result) {
CustomerIO.instance().inAppMessaging().dismissMessage()
}
}

else -> {
result.notImplemented()
}
"dismissMessage" -> call.nativeNoArgs(result, ::dismissMessage)
else -> super.onMethodCall(call, result)
}
}

private fun dismissMessage() {
CustomerIO.instance().inAppMessaging().dismissMessage()
}

/**
* Adds in-app module to native Android SDK based on the configuration provided by
* customer app.
Expand Down
Loading
Loading