Skip to content

Commit

Permalink
feat: finish swift SDK integrate.
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoweii committed Oct 26, 2023
1 parent 53a2187 commit c79d15d
Show file tree
Hide file tree
Showing 19 changed files with 900 additions and 118 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "ios/Clickstream"]
path = ios/Clickstream
url = https://github.com/awslabs/clickstream-swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
package software.aws.solution.clickstream_flutter

import android.app.Activity
import com.amazonaws.logging.Log
import com.amazonaws.logging.LogFactory
import com.amplifyframework.AmplifyException
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.AmplifyConfiguration
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 io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import org.json.JSONObject
import software.aws.solution.clickstream.AWSClickstreamPlugin
import software.aws.solution.clickstream.ClickstreamAnalytics
import software.aws.solution.clickstream.ClickstreamAttribute
import software.aws.solution.clickstream.ClickstreamEvent
import software.aws.solution.clickstream.ClickstreamUserAttribute
import software.aws.solution.clickstream.client.util.ThreadUtil
import java.util.Objects
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors


/** ClickstreamFlutterPlugin */
Expand All @@ -17,6 +31,12 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
///
/// 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 val cachedThreadPool: ExecutorService by lazy { Executors.newCachedThreadPool() }

private val log: Log = LogFactory.getLog(
ClickstreamFlutterPlugin::class.java
)
private lateinit var channel: MethodChannel

private var mActivity: Activity? = null
Expand All @@ -27,26 +47,222 @@ class ClickstreamFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware
}

override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "init") {
if (mActivity != null) {
ClickstreamAnalytics.init(mActivity!!.applicationContext)
ClickstreamAnalytics.getClickStreamConfiguration()
.withAppId("shopping")
.withEndpoint("http://Clicks-Inges-m6f4WJ0DDSWv-478806672.us-east-1.elb.amazonaws.com/collect")
.withLogEvents(true)
result.success(true)
} else {
result.success(false)
val arguments = call.arguments() as HashMap<String, Any>?
when (call.method) {
"init" -> {
result.success(initSDK(arguments!!))
}

"record" -> {
recordEvent(arguments)
}

"setUserId" -> {
setUserId(arguments)
}

"setUserAttributes" -> {
setUserAttributes(arguments)
}

"setGlobalAttributes" -> {
setGlobalAttributes(arguments)
}

"deleteGlobalAttributes" -> {
deleteGlobalAttributes(arguments)
}

"updateConfigure" -> {
updateConfigure(arguments)
}
} else if (call.method == "record") {
ClickstreamAnalytics.recordEvent(call.arguments.toString())

"flushEvents" -> {
ClickstreamAnalytics.flushEvents()
}

else -> {
result.notImplemented()
}
}
}


private fun initSDK(arguments: HashMap<String, Any>): Boolean {
if (getIsInitialized()) return false
if (mActivity != null) {
val context = mActivity!!.applicationContext
if (ThreadUtil.notInMainThread()) {
log.error("Clickstream SDK initialization failed, please initialize in the main thread")
return false
}
val amplifyObject = JSONObject()
val analyticsObject = JSONObject()
val pluginsObject = JSONObject()
val awsClickstreamPluginObject = JSONObject()
awsClickstreamPluginObject.put("appId", arguments["appId"])
awsClickstreamPluginObject.put("endpoint", arguments["endpoint"])
pluginsObject.put("awsClickstreamPlugin", awsClickstreamPluginObject)
analyticsObject.put("plugins", pluginsObject)
amplifyObject.put("analytics", analyticsObject)
val configure = AmplifyConfiguration.fromJson(amplifyObject)
try {
Amplify.addPlugin<AWSClickstreamPlugin>(AWSClickstreamPlugin(context))
Amplify.configure(configure, context)
} catch (exception: AmplifyException) {
log.error("Clickstream SDK initialization failed with error: " + exception.message)
return false
}
val sessionTimeoutDuration = arguments["sessionTimeoutDuration"]
.let { (it as? Int)?.toLong() ?: (it as Long) }
val sendEventsInterval = arguments["sendEventsInterval"]
.let { (it as? Int)?.toLong() ?: (it as Long) }
ClickstreamAnalytics.getClickStreamConfiguration()
.withLogEvents(arguments["isLogEvents"] as Boolean)
.withTrackScreenViewEvents(arguments["isTrackScreenViewEvents"] as Boolean)
.withTrackUserEngagementEvents(arguments["isTrackUserEngagementEvents"] as Boolean)
.withTrackAppExceptionEvents(arguments["isTrackAppExceptionEvents"] as Boolean)
.withSendEventsInterval(sendEventsInterval)
.withSessionTimeoutDuration(sessionTimeoutDuration)
.withCompressEvents(arguments["isCompressEvents"] as Boolean)
.withAuthCookie(arguments["authCookie"] as String)
return true
} else {
result.notImplemented()
return false
}
}

private fun recordEvent(arguments: HashMap<String, Any>?) {
cachedThreadPool.execute {
arguments?.let {
val eventName = it["eventName"] as String
val attributes = it["attributes"] as HashMap<*, *>
val eventBuilder = ClickstreamEvent.builder().name(eventName)
for ((key, value) in attributes) {
if (value is String) {
eventBuilder.add(key.toString(), value)
} else if (value is Double) {
eventBuilder.add(key.toString(), value)
} else if (value is Boolean) {
eventBuilder.add(key.toString(), value)
} else if (value is Int) {
eventBuilder.add(key.toString(), value)
} else if (value is Long) {
eventBuilder.add(key.toString(), value)
}
}
ClickstreamAnalytics.recordEvent(eventBuilder.build())
}
}
}


private fun setUserId(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
val userId = arguments["userId"]
if (userId == null) {
ClickstreamAnalytics.setUserId(null)
} else {
ClickstreamAnalytics.setUserId(userId.toString())
}
}
}

private fun setUserAttributes(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
val builder = ClickstreamUserAttribute.Builder()
for ((key, value) in arguments) {
if (value is String) {
builder.add(key, value)
} else if (value is Double) {
builder.add(key, value)
} else if (value is Boolean) {
builder.add(key, value)
} else if (value is Int) {
builder.add(key, value)
} else if (value is Long) {
builder.add(key, value)
}
}
ClickstreamAnalytics.addUserAttributes(builder.build())
}
}

private fun setGlobalAttributes(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
val builder = ClickstreamAttribute.Builder()
for ((key, value) in arguments) {
if (value is String) {
builder.add(key, value)
} else if (value is Double) {
builder.add(key, value)
} else if (value is Boolean) {
builder.add(key, value)
} else if (value is Int) {
builder.add(key, value)
} else if (value is Long) {
builder.add(key, value)
}
}
ClickstreamAnalytics.addGlobalAttributes(builder.build())
}
}

private fun deleteGlobalAttributes(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
@Suppress("UNCHECKED_CAST")
val attributes = arguments["attributes"] as ArrayList<String>
ClickstreamAnalytics.deleteGlobalAttributes(*attributes.toTypedArray())
}
}

private fun updateConfigure(arguments: java.util.HashMap<String, Any>?) {
arguments?.let {
val configure = ClickstreamAnalytics.getClickStreamConfiguration()
arguments["appId"]?.let {
configure.withAppId(it as String)
}
arguments["endpoint"]?.let {
configure.withEndpoint(it as String)
}
arguments["isLogEvents"]?.let {
configure.withLogEvents(it as Boolean)
}
arguments["isTrackScreenViewEvents"]?.let {
configure.withTrackScreenViewEvents(it as Boolean)
}
arguments["isTrackUserEngagementEvents"]?.let {
configure.withTrackUserEngagementEvents(it as Boolean)
}
arguments["isTrackAppExceptionEvents"]?.let {
configure.withTrackAppExceptionEvents(it as Boolean)
}
arguments["sessionTimeoutDuration"]?.let {
val sessionTimeoutDuration = arguments["sessionTimeoutDuration"]
.let { (it as? Int)?.toLong() ?: (it as Long) }
configure.withSessionTimeoutDuration(sessionTimeoutDuration)
}
arguments["isCompressEvents"]?.let {
configure.withCompressEvents(it as Boolean)
}
arguments["authCookie"]?.let {
configure.withAuthCookie(it as String)
}
}
}

private fun getIsInitialized(): Boolean {
return invokeSuperMethod(Amplify.Analytics, "isConfigured") as Boolean
}

@Throws(Exception::class)
fun invokeSuperMethod(`object`: Any, methodName: String): Any? {
val method =
Objects.requireNonNull(`object`.javaClass.superclass).getDeclaredMethod(methodName)
method.isAccessible = true
return method.invoke(`object`)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
Expand Down
1 change: 1 addition & 0 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@
import android.app.Application;
import android.content.Context;
import androidx.annotation.CallSuper;
import androidx.multidex.MultiDex;

/**
* Extension of {@link android.app.Application}, adding multidex support.
*/
public class FlutterMultiDexApplication extends Application {
@Override
@CallSuper
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
@CallSuper
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
}
11 changes: 5 additions & 6 deletions example/integration_test/plugin_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import 'package:clickstream_flutter/clickstream_flutter.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final ClickstreamFlutter plugin = ClickstreamFlutter();
final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
testWidgets('test init SDK success', (WidgetTester tester) async {
final ClickstreamAnalytics analytics = ClickstreamAnalytics();
final bool result = await analytics.init(
appId: "testAppId", endpoint: "https://example.com/collect");
expect(result, true);
});
}
24 changes: 23 additions & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
PODS:
- Amplify (1.30.3):
- Amplify/Default (= 1.30.3)
- Amplify/Default (1.30.3)
- clickstream_flutter (0.0.1):
- clickstream_flutter/Clickstream (= 0.0.1)
- Flutter
- clickstream_flutter/Clickstream (0.0.1):
- Amplify (= 1.30.3)
- Flutter
- GzipSwift (= 5.1.1)
- SQLite.swift (= 0.13.2)
- Flutter (1.0.0)
- GzipSwift (5.1.1)
- integration_test (0.0.1):
- Flutter
- SQLite.swift (0.13.2):
- SQLite.swift/standard (= 0.13.2)
- SQLite.swift/standard (0.13.2)

DEPENDENCIES:
- clickstream_flutter (from `.symlinks/plugins/clickstream_flutter/ios`)
- Flutter (from `Flutter`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)

SPEC REPOS:
trunk:
- Amplify
- GzipSwift
- SQLite.swift

EXTERNAL SOURCES:
clickstream_flutter:
:path: ".symlinks/plugins/clickstream_flutter/ios"
Expand All @@ -19,9 +38,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/integration_test/ios"

SPEC CHECKSUMS:
clickstream_flutter: 2df0c1ea137129668289dc8ab4d4a1394ee246b9
Amplify: 516e5da5f256f62841b6bc659e1644bc999d7b6e
clickstream_flutter: d5dd3ad04cb641b27c1014c23b4cdbc98f6956dc
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
integration_test: 13825b8a9334a850581300559b8839134b124670
SQLite.swift: 4fc2be46c36392e3b87afe6fe7f1801c1daa07ef

PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189

Expand Down
Loading

0 comments on commit c79d15d

Please sign in to comment.