From a53ff8b244f84578dc3b0d0dda97e752b25c6bd1 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 10 Nov 2022 02:37:09 +0500 Subject: [PATCH 1/5] added support for config along with android native bindings --- android/build.gradle | 8 +- android/src/main/AndroidManifest.xml | 4 +- .../customer/customer_io/CustomerIoPlugin.kt | 121 ++++++++++--- .../io/customer/customer_io/constant/Keys.kt | 24 +++ .../customer/customer_io/extension/MapExt.kt | 36 ++++ .../customer_io/extension/StringExt.kt | 3 + .../customer_io/extension/TypeConversion.kt | 17 ++ example/android/app/build.gradle | 4 +- .../android/app/src/debug/AndroidManifest.xml | 2 +- .../android/app/src/main/AndroidManifest.xml | 20 +-- .../customer_io_example/MainActivity.kt | 2 +- .../res/drawable-v21/launch_background.xml | 3 +- .../main/res/drawable/launch_background.xml | 3 +- .../app/src/profile/AndroidManifest.xml | 2 +- example/ios/Podfile.lock | 37 ++++ example/ios/Runner.xcodeproj/project.pbxproj | 71 ++++++++ .../contents.xcworkspacedata | 3 + example/ios/Runner/AppDelegate.swift | 2 +- .../AppIcon.appiconset/Contents.json | 160 +++++++++--------- .../LaunchImage.imageset/Contents.json | 26 +-- example/ios/Runner/Base.lproj/Main.storyboard | 13 +- example/lib/main.dart | 19 ++- example/test/widget_test.dart | 7 +- ios/customer_io.podspec | 3 +- lib/customer_io.dart | 16 +- lib/customer_io_config.dart | 48 ++++++ lib/customer_io_enums.dart | 9 +- lib/customer_io_method_channel.dart | 23 ++- lib/customer_io_platform_interface.dart | 36 ++-- test/customer_io_method_channel_test.dart | 4 +- test/customer_io_test.dart | 29 ++-- 31 files changed, 559 insertions(+), 196 deletions(-) create mode 100644 android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt create mode 100644 android/src/main/kotlin/io/customer/customer_io/extension/MapExt.kt create mode 100644 android/src/main/kotlin/io/customer/customer_io/extension/StringExt.kt create mode 100644 android/src/main/kotlin/io/customer/customer_io/extension/TypeConversion.kt create mode 100644 example/ios/Podfile.lock diff --git a/android/build.gradle b/android/build.gradle index b0c3950..d10355c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -18,6 +18,7 @@ rootProject.allprojects { repositories { google() mavenCentral() + maven { url 'https://maven.gist.build' } } } @@ -41,10 +42,15 @@ android { } defaultConfig { - minSdkVersion 16 + minSdkVersion 21 } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + // Customer.io SDK + def cioVersion = "3.1.0" + implementation "io.customer.android:tracking:$cioVersion" + implementation "io.customer.android:messaging-push-fcm:$cioVersion" + implementation "io.customer.android:messaging-in-app:$cioVersion" } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 76f3a50..3c591b3 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1 @@ - - + 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 58a3c12..4b54817 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -1,7 +1,14 @@ package io.customer.customer_io +import android.app.Application +import android.content.Context import androidx.annotation.NonNull - +import io.customer.customer_io.constant.Keys +import io.customer.customer_io.extension.* +import io.customer.sdk.CustomerIO +import io.customer.sdk.CustomerIOShared +import io.customer.sdk.data.store.Client +import io.customer.sdk.util.Logger import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -9,27 +16,97 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result /** CustomerIoPlugin */ -class CustomerIoPlugin: FlutterPlugin, MethodCallHandler { - /// 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 - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "customer_io") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() +class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { + /// 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 + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + private lateinit var context: Context + + private val logger: Logger + get() = CustomerIOShared.instance().diGraph.logger + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + context = flutterPluginBinding.applicationContext + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "customer_io") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + when (call.method) { + "getPlatformVersion" -> { + result.success("Android-${android.os.Build.VERSION.RELEASE}") + CustomerIO.instance().identify("flutt-man") + } + "initialize" -> { + initialize(call, result) + } + else -> { + result.notImplemented() + } + } } - } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } + + private fun initialize(call: MethodCall, result: Result) { + try { + val application: Application = context.applicationContext as Application + val configData = call.argument>("config") ?: emptyMap() + val siteId = configData.getString(Keys.Environment.SITE_ID) + val apiKey = configData.getString(Keys.Environment.API_KEY) + val region = configData.getProperty( + Keys.Environment.REGION + )?.takeIfNotBlank().toRegion() + + CustomerIO.Builder( + siteId = siteId, + apiKey = apiKey, + region = region, + appContext = application, + ).apply { + setClient(client = getUserAgentClient(packageConfig = configData)) + setupConfig(configData) + }.build() + logger.info("Customer.io instance initialized successfully") + result.success(true) + } catch (e: Exception) { + logger.error("Failed to initialize Customer.io instance from app, ${e.message}") + result.error("FlutterSegmentException", e.localizedMessage, null); + } + } + + private fun getUserAgentClient(packageConfig: Map?): Client { + val sourceSDKVersion = packageConfig?.getProperty( + Keys.PackageConfig.SOURCE_SDK_VERSION + )?.takeIfNotBlank() ?: packageConfig?.getProperty( + Keys.PackageConfig.SOURCE_SDK_VERSION_COMPAT + )?.takeIfNotBlank() ?: "n/a" + // TODO: change it to flutter + return Client.ReactNative(sdkVersion = sourceSDKVersion) + } + + private fun CustomerIO.Builder.setupConfig(config: Map?): CustomerIO.Builder { + if (config == null) return this + + val logLevel = config.getProperty(Keys.Config.LOG_LEVEL).toCIOLogLevel() + setLogLevel(level = logLevel) + config.getProperty(Keys.Config.TRACKING_API_URL)?.takeIfNotBlank()?.let { value -> + setTrackingApiURL(value) + } + config.getProperty(Keys.Config.AUTO_TRACK_DEVICE_ATTRIBUTES)?.let { value -> + autoTrackDeviceAttributes(shouldTrackDeviceAttributes = value) + } + config.getProperty(Keys.Config.BACKGROUND_QUEUE_MIN_NUMBER_OF_TASKS)?.let { value -> + setBackgroundQueueMinNumberOfTasks(backgroundQueueMinNumberOfTasks = value.toInt()) + } + config.getProperty(Keys.Config.BACKGROUND_QUEUE_SECONDS_DELAY)?.let { value -> + setBackgroundQueueSecondsDelay(backgroundQueueSecondsDelay = value) + } + return this + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } } 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 new file mode 100644 index 0000000..a778641 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/constant/Keys.kt @@ -0,0 +1,24 @@ +package io.customer.customer_io.constant + +internal object Keys { + object Environment { + const val SITE_ID = "siteId" + const val API_KEY = "apiKey" + const val REGION = "region" + const val ORGANIZATION_ID = "organizationId" + } + + object Config { + const val TRACKING_API_URL = "trackingApiUrl" + const val AUTO_TRACK_DEVICE_ATTRIBUTES = "autoTrackDeviceAttributes" + const val LOG_LEVEL = "logLevel" + const val AUTO_TRACK_PUSH_EVENTS = "autoTrackPushEvents" + const val BACKGROUND_QUEUE_MIN_NUMBER_OF_TASKS = "backgroundQueueMinNumberOfTasks" + const val BACKGROUND_QUEUE_SECONDS_DELAY = "backgroundQueueSecondsDelay" + } + + object PackageConfig { + const val SOURCE_SDK_VERSION = "version" + const val SOURCE_SDK_VERSION_COMPAT = "sdkVersion" + } +} diff --git a/android/src/main/kotlin/io/customer/customer_io/extension/MapExt.kt b/android/src/main/kotlin/io/customer/customer_io/extension/MapExt.kt new file mode 100644 index 0000000..1ae0fe8 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/extension/MapExt.kt @@ -0,0 +1,36 @@ +package io.customer.customer_io.extension + +import io.customer.sdk.CustomerIOShared + +@Throws(IllegalArgumentException::class) +internal inline fun Map.getPropertyUnsafe(key: String): T { + val property = get(key) + + if (property !is T) { + throw IllegalArgumentException( + "Invalid value provided for key: $key, value $property must be of type ${T::class.java.simpleName}" + ) + } + return property +} + +internal inline fun Map.getProperty(key: String): T? = try { + getPropertyUnsafe(key) +} catch (ex: IllegalArgumentException) { + CustomerIOShared.instance().diGraph.logger.error( + ex.message ?: "getProperty($key) -> IllegalArgumentException" + ) + null +} + +@Throws(IllegalArgumentException::class) +internal fun Map.getString(key: String): String = try { + getPropertyUnsafe(key).takeIfNotBlank() ?: throw IllegalArgumentException( + "Invalid value provided for $key, must not be blank" + ) +} catch (ex: IllegalArgumentException) { + CustomerIOShared.instance().diGraph.logger.error( + ex.message ?: "getString($key) -> IllegalArgumentException" + ) + throw ex +} diff --git a/android/src/main/kotlin/io/customer/customer_io/extension/StringExt.kt b/android/src/main/kotlin/io/customer/customer_io/extension/StringExt.kt new file mode 100644 index 0000000..b820b74 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/extension/StringExt.kt @@ -0,0 +1,3 @@ +package io.customer.customer_io.extension + +internal fun String.takeIfNotBlank(): String? = takeIf { it.isNotBlank() } diff --git a/android/src/main/kotlin/io/customer/customer_io/extension/TypeConversion.kt b/android/src/main/kotlin/io/customer/customer_io/extension/TypeConversion.kt new file mode 100644 index 0000000..ce59b89 --- /dev/null +++ b/android/src/main/kotlin/io/customer/customer_io/extension/TypeConversion.kt @@ -0,0 +1,17 @@ +package io.customer.customer_io.extension + +import io.customer.sdk.data.model.Region +import io.customer.sdk.util.CioLogLevel + +internal fun String?.toRegion(fallback: Region = Region.US): Region { + return if (this.isNullOrBlank()) fallback + else listOf( + Region.US, + Region.EU, + ).find { value -> value.code.equals(this, ignoreCase = true) } ?: fallback +} + +internal fun String?.toCIOLogLevel(fallback: CioLogLevel = CioLogLevel.NONE): CioLogLevel { + return CioLogLevel.values().find { value -> value.name.equals(this, ignoreCase = true) } + ?: fallback +} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index fd242e0..0dfb4e7 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -47,8 +47,8 @@ android { applicationId "io.customer.customer_io_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + minSdkVersion 21 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index fa4a476..20a906a 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -4,5 +4,5 @@ the Flutter tool needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> - + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index e48d384..095397f 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,28 +1,28 @@ - + android:icon="@mipmap/ic_launcher" + android:label="customer_io_example"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml index 304732f..0db4a83 100644 --- a/example/android/app/src/main/res/drawable/launch_background.xml +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -1,5 +1,4 @@ - - + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index fa4a476..20a906a 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -4,5 +4,5 @@ the Flutter tool needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> - + diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 0000000..0058890 --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,37 @@ +PODS: + - customer_io (0.0.1): + - CustomerIO/Tracking + - Flutter + - CustomerIO/Tracking (1.2.2): + - CustomerIOTracking (= 1.2.2) + - CustomerIOCommon (1.2.2) + - CustomerIOTracking (1.2.2): + - CustomerIOCommon (= 1.2.2) + - Flutter (1.0.0) + +DEPENDENCIES: + - customer_io (from `.symlinks/plugins/customer_io/ios`) + - Flutter (from `Flutter`) + +SPEC REPOS: + trunk: + - CustomerIO + - CustomerIOCommon + - CustomerIOTracking + +EXTERNAL SOURCES: + customer_io: + :path: ".symlinks/plugins/customer_io/ios" + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + customer_io: 99ae280180a2fe4c0514f28a8da5e7a66734a612 + CustomerIO: 0a31ad1baed6cd952469b9098e2e73fa96cec70e + CustomerIOCommon: 3bf82c3574205819a69baa1f0bc5b47671dd2d19 + CustomerIOTracking: 101dc8c25eff807eadc8fbcf83b76f98fb5832a5 + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + +PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c + +COCOAPODS: 1.11.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6766671..bf0016b 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C7DEDD35097BB3BA070FCF6F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6AE9E5124FF1D696DCD9FA7 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -31,10 +32,12 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3A1291BD28DFAFB7AE8889A7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7D8F4BD0E4278C8B382C7F5D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -42,6 +45,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C84BCEE1C624036AC21C48F8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D6AE9E5124FF1D696DCD9FA7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,24 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C7DEDD35097BB3BA070FCF6F /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0E988BDB51BFF1A62B3D7339 /* Pods */ = { + isa = PBXGroup; + children = ( + 7D8F4BD0E4278C8B382C7F5D /* Pods-Runner.debug.xcconfig */, + 3A1291BD28DFAFB7AE8889A7 /* Pods-Runner.release.xcconfig */, + C84BCEE1C624036AC21C48F8 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +89,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 0E988BDB51BFF1A62B3D7339 /* Pods */, + FB417CA72F75196ECE153A3D /* Frameworks */, ); sourceTree = ""; }; @@ -98,6 +117,14 @@ path = Runner; sourceTree = ""; }; + FB417CA72F75196ECE153A3D /* Frameworks */ = { + isa = PBXGroup; + children = ( + D6AE9E5124FF1D696DCD9FA7 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 7E5DDAC658855372819E954F /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 1FE94DC6C1E0FE32C2765937 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -169,6 +198,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1FE94DC6C1E0FE32C2765937 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -183,6 +229,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 7E5DDAC658855372819E954F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -291,6 +359,7 @@ DEVELOPMENT_TEAM = 2YC97BQN3N; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -420,6 +489,7 @@ DEVELOPMENT_TEAM = 2YC97BQN3N; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -443,6 +513,7 @@ DEVELOPMENT_TEAM = 2YC97BQN3N; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..9c9f54a 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -7,7 +7,7 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + GeneratedPluginRegistrant.register(withRegistry: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..e882ab9 100644 --- a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ { - "images" : [ + "images": [ { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" }, { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" + "size": "20x20", + "idiom": "iphone", + "filename": "Icon-App-20x20@3x.png", + "scale": "3x" }, { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" }, { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" }, { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" + "size": "29x29", + "idiom": "iphone", + "filename": "Icon-App-29x29@3x.png", + "scale": "3x" }, { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" }, { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "size": "40x40", + "idiom": "iphone", + "filename": "Icon-App-40x40@3x.png", + "scale": "3x" }, { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@2x.png", + "scale": "2x" }, { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" + "size": "60x60", + "idiom": "iphone", + "filename": "Icon-App-60x60@3x.png", + "scale": "3x" }, { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@1x.png", + "scale": "1x" }, { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "size": "20x20", + "idiom": "ipad", + "filename": "Icon-App-20x20@2x.png", + "scale": "2x" }, { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@1x.png", + "scale": "1x" }, { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "size": "29x29", + "idiom": "ipad", + "filename": "Icon-App-29x29@2x.png", + "scale": "2x" }, { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@1x.png", + "scale": "1x" }, { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "size": "40x40", + "idiom": "ipad", + "filename": "Icon-App-40x40@2x.png", + "scale": "2x" }, { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@1x.png", + "scale": "1x" }, { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "size": "76x76", + "idiom": "ipad", + "filename": "Icon-App-76x76@2x.png", + "scale": "2x" }, { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon-App-83.5x83.5@2x.png", + "scale": "2x" }, { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon-App-1024x1024@1x.png", + "scale": "1x" } ], - "info" : { - "version" : 1, - "author" : "xcode" + "info": { + "version": 1, + "author": "xcode" } } diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2..781d7cd 100644 --- a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,23 @@ { - "images" : [ + "images": [ { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" + "idiom": "universal", + "filename": "LaunchImage.png", + "scale": "1x" }, { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" + "idiom": "universal", + "filename": "LaunchImage@2x.png", + "scale": "2x" }, { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" + "idiom": "universal", + "filename": "LaunchImage@3x.png", + "scale": "3x" } ], - "info" : { - "version" : 1, - "author" : "xcode" + "info": { + "version": 1, + "author": "xcode" } } diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard index f3c2851..08dbc27 100644 --- a/example/ios/Runner/Base.lproj/Main.storyboard +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + diff --git a/example/lib/main.dart b/example/lib/main.dart index 5651edc..fbce662 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,20 @@ -import 'package:flutter/material.dart'; import 'dart:async'; -import 'package:flutter/services.dart'; import 'package:customer_io/customer_io.dart'; +import 'package:customer_io/customer_io_config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await CustomerIo.initialize( + config: CustomerIOConfig( + siteId: "", + apiKey: "", + ), + ); -void main() { runApp(const MyApp()); } @@ -17,7 +27,6 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { String _platformVersion = 'Unknown'; - final _customerIoPlugin = CustomerIo(); @override void initState() { @@ -32,7 +41,7 @@ class _MyAppState extends State { // We also handle the message potentially returning null. try { platformVersion = - await _customerIoPlugin.getPlatformVersion() ?? 'Unknown platform version'; + await CustomerIo.getPlatformVersion() ?? 'Unknown platform version'; } on PlatformException { platformVersion = 'Failed to get platform version.'; } diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 6620098..3078504 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -5,11 +5,10 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. +import 'package:customer_io_example/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:customer_io_example/main.dart'; - void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. @@ -18,8 +17,8 @@ void main() { // Verify that platform version is retrieved. expect( find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), ), findsOneWidget, ); diff --git a/ios/customer_io.podspec b/ios/customer_io.podspec index c9ec3a3..dbe254a 100644 --- a/ios/customer_io.podspec +++ b/ios/customer_io.podspec @@ -15,7 +15,8 @@ A plugin for Customer.io s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '9.0' + s.dependency 'CustomerIO/Tracking' + s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/customer_io.dart b/lib/customer_io.dart index 6fe92c0..fbe2ae9 100644 --- a/lib/customer_io.dart +++ b/lib/customer_io.dart @@ -1,8 +1,18 @@ - +import 'customer_io_config.dart'; import 'customer_io_platform_interface.dart'; class CustomerIo { - Future getPlatformVersion() { - return CustomerIoPlatform.instance.getPlatformVersion(); + const CustomerIo._(); + + static CustomerIOPlatform get _customerIO => CustomerIOPlatform.instance; + + static Future getPlatformVersion() { + return _customerIO.getPlatformVersion(); + } + + static Future initialize({ + required CustomerIOConfig config, + }) { + return _customerIO.initialize(config: config); } } diff --git a/lib/customer_io_config.dart b/lib/customer_io_config.dart index e69de29..c96b8e3 100644 --- a/lib/customer_io_config.dart +++ b/lib/customer_io_config.dart @@ -0,0 +1,48 @@ +import 'customer_io_enums.dart'; + +/// Configure plugin using CustomerIOConfig + +class CustomerIOConfig { + final String siteId; + final String apiKey; + Region region; + String? organizationId; + + CioLogLevel logLevel; + bool autoTrackDeviceAttributes; + String trackingApiUrl; + bool autoTrackPushEvents; + int backgroundQueueMinNumberOfTasks; + double backgroundQueueSecondsDelay; + + String version; + + CustomerIOConfig( + {required this.siteId, + required this.apiKey, + this.region = Region.us, + this.organizationId, + this.logLevel = CioLogLevel.debug, + this.autoTrackDeviceAttributes = true, + this.trackingApiUrl = "", + this.autoTrackPushEvents = true, + this.backgroundQueueMinNumberOfTasks = 10, + this.backgroundQueueSecondsDelay = 30.0, + this.version = ""}); + + Map toMap() { + return { + 'siteId': siteId, + 'apiKey': apiKey, + 'region': region.name, + 'organizationId': organizationId, + 'logLevel': logLevel.name, + 'autoTrackDeviceAttributes': autoTrackDeviceAttributes, + 'trackingApiUrl': trackingApiUrl, + 'autoTrackPushEvents': autoTrackPushEvents, + 'backgroundQueueMinNumberOfTasks': backgroundQueueMinNumberOfTasks, + 'backgroundQueueSecondsDelay': backgroundQueueSecondsDelay, + 'version': version, + }; + } +} diff --git a/lib/customer_io_enums.dart b/lib/customer_io_enums.dart index 157be08..979ceb7 100644 --- a/lib/customer_io_enums.dart +++ b/lib/customer_io_enums.dart @@ -5,11 +5,4 @@ enum CioLogLevel { none, error, info, debug } /// Use this enum to specify the region your customer.io workspace is present in. /// US - for data center in United States /// EU - for data center in European Union -enum Region { - us(name: "US"), - eu(name: "EU"); - - final String name; - - const Region({required this.name}); -} +enum Region { us, eu } diff --git a/lib/customer_io_method_channel.dart b/lib/customer_io_method_channel.dart index b34fc99..4bb125c 100644 --- a/lib/customer_io_method_channel.dart +++ b/lib/customer_io_method_channel.dart @@ -1,17 +1,34 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'customer_io_config.dart'; import 'customer_io_platform_interface.dart'; -/// An implementation of [CustomerIoPlatform] that uses method channels. -class MethodChannelCustomerIo extends CustomerIoPlatform { +/// An implementation of [CustomerIOPlatform] that uses method channels. +class CustomerIOMethodChannel extends CustomerIOPlatform { /// The method channel used to interact with the native platform. @visibleForTesting final methodChannel = const MethodChannel('customer_io'); @override Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); + final version = + await methodChannel.invokeMethod('getPlatformVersion'); return version; } + + @override + Future initialize({ + required CustomerIOConfig config, + }) async { + try { + await methodChannel.invokeMethod('initialize', { + 'config': config.toMap(), + }); + } on PlatformException catch (exception) { + if (kDebugMode) { + print(exception); + } + } + } } diff --git a/lib/customer_io_platform_interface.dart b/lib/customer_io_platform_interface.dart index bc9231d..ad9986b 100644 --- a/lib/customer_io_platform_interface.dart +++ b/lib/customer_io_platform_interface.dart @@ -1,24 +1,26 @@ +import 'package:customer_io/customer_io_config.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'customer_io_method_channel.dart'; +import 'customer_io_platform_interface.dart'; -abstract class CustomerIoPlatform extends PlatformInterface { - /// Constructs a CustomerIoPlatform. - CustomerIoPlatform() : super(token: _token); +/// The default instance of [CustomerIOPlatform] to use +/// +/// Platform-specific plugins should override this with their own +/// platform-specific class that extends [CustomerIOPlatform] when they +/// register themselves. +/// +/// Defaults to [CustomerIOMethodChannel] +abstract class CustomerIOPlatform extends PlatformInterface { + CustomerIOPlatform() : super(token: _token); static final Object _token = Object(); - static CustomerIoPlatform _instance = MethodChannelCustomerIo(); - - /// The default instance of [CustomerIoPlatform] to use. - /// - /// Defaults to [MethodChannelCustomerIo]. - static CustomerIoPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [CustomerIoPlatform] when - /// they register themselves. - static set instance(CustomerIoPlatform instance) { + static CustomerIOPlatform _instance = CustomerIOMethodChannel(); + + static CustomerIOPlatform get instance => _instance; + + static set instance(CustomerIOPlatform instance) { PlatformInterface.verifyToken(instance, _token); _instance = instance; } @@ -26,4 +28,10 @@ abstract class CustomerIoPlatform extends PlatformInterface { Future getPlatformVersion() { throw UnimplementedError('platformVersion() has not been implemented.'); } + + Future initialize({ + required CustomerIOConfig config, + }) { + throw UnimplementedError('config() has not been implemented.'); + } } diff --git a/test/customer_io_method_channel_test.dart b/test/customer_io_method_channel_test.dart index 68c868e..c7ff93a 100644 --- a/test/customer_io_method_channel_test.dart +++ b/test/customer_io_method_channel_test.dart @@ -1,9 +1,9 @@ +import 'package:customer_io/customer_io_method_channel.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:customer_io/customer_io_method_channel.dart'; void main() { - MethodChannelCustomerIo platform = MethodChannelCustomerIo(); + CustomerIOMethodChannel platform = CustomerIOMethodChannel(); const MethodChannel channel = MethodChannel('customer_io'); TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/test/customer_io_test.dart b/test/customer_io_test.dart index 161be5b..4830528 100644 --- a/test/customer_io_test.dart +++ b/test/customer_io_test.dart @@ -1,29 +1,34 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:customer_io/customer_io.dart'; -import 'package:customer_io/customer_io_platform_interface.dart'; +import 'package:customer_io/customer_io_config.dart'; import 'package:customer_io/customer_io_method_channel.dart'; +import 'package:customer_io/customer_io_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -class MockCustomerIoPlatform +class MockCustomerIoPlatform with MockPlatformInterfaceMixin - implements CustomerIoPlatform { - + implements CustomerIOPlatform { @override Future getPlatformVersion() => Future.value('42'); + + @override + Future initialize({required CustomerIOConfig config}) { + // TODO: implement config + throw UnimplementedError(); + } } void main() { - final CustomerIoPlatform initialPlatform = CustomerIoPlatform.instance; + final CustomerIOPlatform initialPlatform = CustomerIOPlatform.instance; - test('$MethodChannelCustomerIo is the default instance', () { - expect(initialPlatform, isInstanceOf()); + test('$CustomerIOMethodChannel is the default instance', () { + expect(initialPlatform, isInstanceOf()); }); test('getPlatformVersion', () async { - CustomerIo customerIoPlugin = CustomerIo(); MockCustomerIoPlatform fakePlatform = MockCustomerIoPlatform(); - CustomerIoPlatform.instance = fakePlatform; - - expect(await customerIoPlugin.getPlatformVersion(), '42'); + CustomerIOPlatform.instance = fakePlatform; + + expect(await CustomerIo.getPlatformVersion(), '42'); }); } From c0b7e3ff8833719c6de7732f5e53aac542fa6494 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Sat, 12 Nov 2022 00:35:01 +0500 Subject: [PATCH 2/5] added ios config bindings --- .../customer/customer_io/CustomerIoPlugin.kt | 5 +- example/ios/Runner.xcodeproj/project.pbxproj | 5 +- example/ios/Runner/AppDelegate.swift | 14 ++-- ios/Classes/CustomerIOExtensions.swift | 39 +++++++++++ ios/Classes/Keys.swift | 32 +++++++++ ios/Classes/SwiftCustomerIoPlugin.swift | 69 ++++++++++++++++--- lib/customer_io_config.dart | 1 - lib/customer_io_method_channel.dart | 4 +- lib/customer_io_platform_interface.dart | 1 - 9 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 ios/Classes/CustomerIOExtensions.swift create mode 100644 ios/Classes/Keys.swift 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 4b54817..d93ac7e 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -37,7 +37,6 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { when (call.method) { "getPlatformVersion" -> { result.success("Android-${android.os.Build.VERSION.RELEASE}") - CustomerIO.instance().identify("flutt-man") } "initialize" -> { initialize(call, result) @@ -52,13 +51,15 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { private fun initialize(call: MethodCall, result: Result) { try { val application: Application = context.applicationContext as Application - val configData = call.argument>("config") ?: emptyMap() + val configData = call.arguments as? Map ?: emptyMap() val siteId = configData.getString(Keys.Environment.SITE_ID) val apiKey = configData.getString(Keys.Environment.API_KEY) val region = configData.getProperty( Keys.Environment.REGION )?.takeIfNotBlank().toRegion() + logger.info("Config: $configData") + CustomerIO.Builder( siteId = siteId, apiKey = apiKey, diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index bf0016b..1793d2f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -68,7 +68,6 @@ 3A1291BD28DFAFB7AE8889A7 /* Pods-Runner.release.xcconfig */, C84BCEE1C624036AC21C48F8 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -486,7 +485,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2YC97BQN3N; + DEVELOPMENT_TEAM = RH7U864PLZ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 9c9f54a..d2a14bf 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -3,11 +3,11 @@ import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(withRegistry: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } } diff --git a/ios/Classes/CustomerIOExtensions.swift b/ios/Classes/CustomerIOExtensions.swift new file mode 100644 index 0000000..bc2657b --- /dev/null +++ b/ios/Classes/CustomerIOExtensions.swift @@ -0,0 +1,39 @@ +// +// CustomerIOExtensions.swift +// customer_io +// +// Created by ShahrozAli on 11/11/22. +// + +import Foundation +import CioTracking + +extension Region{ + static func from(regionStr : String) -> Region { + switch regionStr { + case "us" : + return Region.US + case "eu" : + return Region.EU + default: + return Region.US + } + } +} + +extension CioLogLevel { + static func from(for level : String) -> CioLogLevel { + switch level { + case "none": + return .none + case "error": + return .error + case "info": + return .info + case "debug": + return .debug + default: + return .error + } + } +} diff --git a/ios/Classes/Keys.swift b/ios/Classes/Keys.swift new file mode 100644 index 0000000..e45303e --- /dev/null +++ b/ios/Classes/Keys.swift @@ -0,0 +1,32 @@ +// +// Keys.swift +// customer_io +// +// Created by ShahrozAli on 11/11/22. +// + +import Foundation + +struct Keys { + + struct Environment{ + static let siteId = "siteId" + static let apiKey = "apiKey" + static let region = "region" + static let organizationId = "organizationId" + } + + struct Config{ + static let trackingApiUrl = "trackingApiUrl" + static let autoTrackDeviceAttributes = "autoTrackDeviceAttributes" + static let logLevel = "logLevel" + static let autoTrackPushEvents = "autoTrackPushEvents" + static let backgroundQueueMinNumberOfTasks = "backgroundQueueMinNumberOfTasks" + static let backgroundQueueSecondsDelay = "backgroundQueueSecondsDelay" + } + + struct PackageConfig{ + static let version = "version" + static let sdkVersion = "sdkVersion" + } +} diff --git a/ios/Classes/SwiftCustomerIoPlugin.swift b/ios/Classes/SwiftCustomerIoPlugin.swift index 04dec58..f0ea7b2 100644 --- a/ios/Classes/SwiftCustomerIoPlugin.swift +++ b/ios/Classes/SwiftCustomerIoPlugin.swift @@ -1,14 +1,65 @@ import Flutter import UIKit +import CioTracking +import Common public class SwiftCustomerIoPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "customer_io", binaryMessenger: registrar.messenger()) - let instance = SwiftCustomerIoPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - result("iOS " + UIDevice.current.systemVersion) - } + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "customer_io", binaryMessenger: registrar.messenger()) + let instance = SwiftCustomerIoPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch(call.method) { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + case "initialize": + if let params = call.arguments as? Dictionary { + print(params) + initialize(params: params) + result(true) + } else{ + print("initialize: params not available") + result(FlutterError()) + } + default: + result(FlutterMethodNotImplemented) + } + } + + private func initialize(params : Dictionary){ + guard let siteId = params[Keys.Environment.siteId] as? String, + let apiKey = params[Keys.Environment.apiKey] as? String, + let region = params[Keys.Environment.region] as? String + else { + return + } + + CustomerIO.initialize(siteId: siteId, apiKey: apiKey, region: Region.from(regionStr: region)) + setUserAgentClient(params: params) + setupConfig(params: params) + } + + private func setUserAgentClient(params : Dictionary){ + let version = params[Keys.PackageConfig.version] as? String ?? "n/a" + let sdkSource = SdkWrapperConfig.Source.flutter + CustomerIO.config { + $0._sdkWrapperConfig = SdkWrapperConfig(source: sdkSource, version: version ) + } + } + + private func setupConfig(params : Dictionary){ + CustomerIO.config { + $0.autoTrackDeviceAttributes = params[Keys.Config.autoTrackDeviceAttributes] as! Bool + $0.logLevel = CioLogLevel.from(for: params[Keys.Config.logLevel] as! String) + $0.autoTrackPushEvents = params[Keys.Config.autoTrackPushEvents] as! Bool + $0.backgroundQueueMinNumberOfTasks = params[Keys.Config.backgroundQueueMinNumberOfTasks] as! Int + $0.backgroundQueueSecondsDelay = params[Keys.Config.backgroundQueueSecondsDelay] as! Seconds + if let trackingApiUrl = params[Keys.Config.trackingApiUrl] as? String, !trackingApiUrl.isEmpty { + $0.trackingApiUrl = trackingApiUrl + } + } + } } diff --git a/lib/customer_io_config.dart b/lib/customer_io_config.dart index c96b8e3..2d1f7f9 100644 --- a/lib/customer_io_config.dart +++ b/lib/customer_io_config.dart @@ -1,7 +1,6 @@ import 'customer_io_enums.dart'; /// Configure plugin using CustomerIOConfig - class CustomerIOConfig { final String siteId; final String apiKey; diff --git a/lib/customer_io_method_channel.dart b/lib/customer_io_method_channel.dart index 4bb125c..9b167e4 100644 --- a/lib/customer_io_method_channel.dart +++ b/lib/customer_io_method_channel.dart @@ -22,9 +22,7 @@ class CustomerIOMethodChannel extends CustomerIOPlatform { required CustomerIOConfig config, }) async { try { - await methodChannel.invokeMethod('initialize', { - 'config': config.toMap(), - }); + await methodChannel.invokeMethod('initialize', config.toMap()); } on PlatformException catch (exception) { if (kDebugMode) { print(exception); diff --git a/lib/customer_io_platform_interface.dart b/lib/customer_io_platform_interface.dart index ad9986b..b1d33c7 100644 --- a/lib/customer_io_platform_interface.dart +++ b/lib/customer_io_platform_interface.dart @@ -2,7 +2,6 @@ import 'package:customer_io/customer_io_config.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'customer_io_method_channel.dart'; -import 'customer_io_platform_interface.dart'; /// The default instance of [CustomerIOPlatform] to use /// From 15841c4ee3166d2326565b6353bb8b16dd770798 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Sat, 12 Nov 2022 00:36:19 +0500 Subject: [PATCH 3/5] removed logging config --- .../main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 d93ac7e..ee522d0 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -57,9 +57,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { val region = configData.getProperty( Keys.Environment.REGION )?.takeIfNotBlank().toRegion() - - logger.info("Config: $configData") - + CustomerIO.Builder( siteId = siteId, apiKey = apiKey, From 8ff88ca081fa3db1d2638605a8e41377f6f4e9af Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Wed, 16 Nov 2022 16:06:32 +0500 Subject: [PATCH 4/5] PR suggestions --- .../customer/customer_io/CustomerIoPlugin.kt | 17 ++++++---- ios/Classes/SwiftCustomerIoPlugin.swift | 34 ++++++++----------- 2 files changed, 24 insertions(+), 27 deletions(-) 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 ee522d0..0511226 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -15,13 +15,16 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -/** CustomerIoPlugin */ -class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { +/** + * Android implementation of plugin that will let Flutter developers to + * interact with a Android platform + * */ +class CustomerIOPlugin : FlutterPlugin, MethodCallHandler { /// 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 /// when the Flutter Engine is detached from the Activity - private lateinit var channel: MethodChannel + private lateinit var flutterCommunicationChannel: MethodChannel private lateinit var context: Context private val logger: Logger @@ -29,8 +32,8 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "customer_io") - channel.setMethodCallHandler(this) + flutterCommunicationChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "customer_io") + flutterCommunicationChannel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { @@ -57,7 +60,7 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { val region = configData.getProperty( Keys.Environment.REGION )?.takeIfNotBlank().toRegion() - + CustomerIO.Builder( siteId = siteId, apiKey = apiKey, @@ -106,6 +109,6 @@ class CustomerIoPlugin : FlutterPlugin, MethodCallHandler { } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) + flutterCommunicationChannel.setMethodCallHandler(null) } } diff --git a/ios/Classes/SwiftCustomerIoPlugin.swift b/ios/Classes/SwiftCustomerIoPlugin.swift index f0ea7b2..d2d9b56 100644 --- a/ios/Classes/SwiftCustomerIoPlugin.swift +++ b/ios/Classes/SwiftCustomerIoPlugin.swift @@ -37,29 +37,23 @@ public class SwiftCustomerIoPlugin: NSObject, FlutterPlugin { return } - CustomerIO.initialize(siteId: siteId, apiKey: apiKey, region: Region.from(regionStr: region)) - setUserAgentClient(params: params) - setupConfig(params: params) + CustomerIO.initialize(siteId: siteId, apiKey: apiKey, region: Region.from(regionStr: region)){ + config in + config._sdkWrapperConfig = self.getUserAgent(params: params) + config.autoTrackDeviceAttributes = params[Keys.Config.autoTrackDeviceAttributes] as! Bool + config.logLevel = CioLogLevel.from(for: params[Keys.Config.logLevel] as! String) + config.autoTrackPushEvents = params[Keys.Config.autoTrackPushEvents] as! Bool + config.backgroundQueueMinNumberOfTasks = params[Keys.Config.backgroundQueueMinNumberOfTasks] as! Int + config.backgroundQueueSecondsDelay = params[Keys.Config.backgroundQueueSecondsDelay] as! Seconds + if let trackingApiUrl = params[Keys.Config.trackingApiUrl] as? String, !trackingApiUrl.isEmpty { + config.trackingApiUrl = trackingApiUrl + } + } } - private func setUserAgentClient(params : Dictionary){ + private func getUserAgent(params : Dictionary) -> SdkWrapperConfig{ let version = params[Keys.PackageConfig.version] as? String ?? "n/a" let sdkSource = SdkWrapperConfig.Source.flutter - CustomerIO.config { - $0._sdkWrapperConfig = SdkWrapperConfig(source: sdkSource, version: version ) - } - } - - private func setupConfig(params : Dictionary){ - CustomerIO.config { - $0.autoTrackDeviceAttributes = params[Keys.Config.autoTrackDeviceAttributes] as! Bool - $0.logLevel = CioLogLevel.from(for: params[Keys.Config.logLevel] as! String) - $0.autoTrackPushEvents = params[Keys.Config.autoTrackPushEvents] as! Bool - $0.backgroundQueueMinNumberOfTasks = params[Keys.Config.backgroundQueueMinNumberOfTasks] as! Int - $0.backgroundQueueSecondsDelay = params[Keys.Config.backgroundQueueSecondsDelay] as! Seconds - if let trackingApiUrl = params[Keys.Config.trackingApiUrl] as? String, !trackingApiUrl.isEmpty { - $0.trackingApiUrl = trackingApiUrl - } - } + return SdkWrapperConfig(source: sdkSource, version: version ) } } From 258967532a1f998ba9935c86d54c44b13412ad48 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Thu, 24 Nov 2022 14:51:53 +0500 Subject: [PATCH 5/5] removed unused config attributes --- android/build.gradle | 2 +- .../main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt | 5 +---- .../src/main/kotlin/io/customer/customer_io/constant/Keys.kt | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index d10355c..4e01a53 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Customer.io SDK - def cioVersion = "3.1.0" + def cioVersion = "3.2.0-alpha.1" implementation "io.customer.android:tracking:$cioVersion" implementation "io.customer.android:messaging-push-fcm:$cioVersion" implementation "io.customer.android:messaging-in-app:$cioVersion" 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 0511226..cd9aa8d 100644 --- a/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt +++ b/android/src/main/kotlin/io/customer/customer_io/CustomerIoPlugin.kt @@ -81,11 +81,8 @@ class CustomerIOPlugin : FlutterPlugin, MethodCallHandler { private fun getUserAgentClient(packageConfig: Map?): Client { val sourceSDKVersion = packageConfig?.getProperty( Keys.PackageConfig.SOURCE_SDK_VERSION - )?.takeIfNotBlank() ?: packageConfig?.getProperty( - Keys.PackageConfig.SOURCE_SDK_VERSION_COMPAT )?.takeIfNotBlank() ?: "n/a" - // TODO: change it to flutter - return Client.ReactNative(sdkVersion = sourceSDKVersion) + return Client.Flutter(sdkVersion = sourceSDKVersion) } private fun CustomerIO.Builder.setupConfig(config: Map?): CustomerIO.Builder { 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 a778641..f5ca372 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 @@ -12,13 +12,11 @@ internal object Keys { const val TRACKING_API_URL = "trackingApiUrl" const val AUTO_TRACK_DEVICE_ATTRIBUTES = "autoTrackDeviceAttributes" const val LOG_LEVEL = "logLevel" - const val AUTO_TRACK_PUSH_EVENTS = "autoTrackPushEvents" const val BACKGROUND_QUEUE_MIN_NUMBER_OF_TASKS = "backgroundQueueMinNumberOfTasks" const val BACKGROUND_QUEUE_SECONDS_DELAY = "backgroundQueueSecondsDelay" } object PackageConfig { const val SOURCE_SDK_VERSION = "version" - const val SOURCE_SDK_VERSION_COMPAT = "sdkVersion" } }