diff --git a/.github/workflows/swift-lint.yml b/.github/workflows/swift-lint.yml new file mode 100644 index 0000000..a0c497a --- /dev/null +++ b/.github/workflows/swift-lint.yml @@ -0,0 +1,17 @@ +name: Swift Lint + +on: [pull_request] + +jobs: + lint: + runs-on: macos-12 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set Xcode 14 + run: | + sudo xcode-select -switch /Applications/Xcode_14.1.app + + - name: Lint + run: cd ios && swiftlint --strict # force to fix warnings too diff --git a/android/build.gradle b/android/build.gradle index 6619741..abcc973 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,16 +21,10 @@ rootProject.allprojects { } } -ext { - PUBLISH_VERSION = '4.0.0-beta.0' -} - apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - buildFeatures.buildConfig true - compileSdkVersion 34 // Condition for namespace compatibility in AGP 8 if (project.android.hasProperty("namespace")) { @@ -42,9 +36,6 @@ android { } defaultConfig { minSdkVersion 16 - - buildConfigField 'String', 'SDK_VERSION', "\"${PUBLISH_VERSION}\"" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } lintOptions { diff --git a/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt b/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt index ae1e9ba..81bf7a7 100644 --- a/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt +++ b/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt @@ -28,7 +28,6 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var channel: MethodChannel - companion object { private const val methodChannelName = "amplitude_flutter" } @@ -66,7 +65,11 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { amplitude = Amplitude(configuration) // Set library - amplitude.add(FlutterLibraryPlugin()) + amplitude.add( + FlutterLibraryPlugin( + call.argument("library") ?: "amplitude-flutter/unknown" + ) + ) call.argument("logLevel")?.let { amplitude.logger.logMode = Logger.LogMode.valueOf(it.uppercase()) @@ -81,44 +84,12 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.success("init called..") } - "track" -> { - val event = getEvent(call) - amplitude.track(event) - amplitude.logger.debug("Track event: ${call.arguments}") - - result.success("track called..") - } - - "identify" -> { - val event = getEvent(call) - amplitude.track(event) - amplitude.logger.debug("Track identify event: ${call.arguments}") - - result.success("identify called..") - } - - "groupIdentify" -> { - val event = getEvent(call) - amplitude.track(event) - amplitude.logger.debug("Track group identify event: ${call.arguments}") - - result.success("groupIdentify called..") - } - - "setGroup" -> { - val event = getEvent(call) - amplitude.track(event) - amplitude.logger.debug("Track set group event: ${call.arguments}") - - result.success("setGroup called..") - } - - "revenue" -> { + "track", "identify", "groupIdentify", "setGroup", "revenue" -> { val event = getEvent(call) amplitude.track(event) - amplitude.logger.debug("Track revenue event: ${call.arguments}") + amplitude.logger.debug("Track ${call.method} event: ${call.arguments}") - result.success("revenue called..") + result.success("${call.method} called..") } "setUserId" -> { @@ -196,6 +167,7 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { call.argument("useBatch")?.let { configuration.useBatch = it } call.argument("serverZone") ?.let { configuration.serverZone = com.amplitude.core.ServerZone.valueOf(it.uppercase()) } + call.argument("serverUrl")?.let { configuration.serverUrl = it } call.argument("minTimeBetweenSessionsMillis") ?.let { configuration.minTimeBetweenSessionsMillis = it.toLong() } call.argument>("defaultTracking")?.let { map -> @@ -237,6 +209,7 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { (map["deviceManufacturer"] as? Boolean)?.let { if (!it) trackingOptions.disableDeviceManufacturer() } (map["osVersion"] as? Boolean)?.let { if (!it) trackingOptions.disableOsVersion() } (map["osName"] as? Boolean)?.let { if (!it) trackingOptions.disableOsName() } + (map["versionName"] as? Boolean)?.let { if (!it) trackingOptions.disableVersionName() } (map["adid"] as? Boolean)?.let { if (!it) trackingOptions.disableAdid() } (map["appSetId"] as? Boolean)?.let { if (!it) trackingOptions.disableAppSetId() } (map["deviceBrand"] as? Boolean)?.let { if (!it) trackingOptions.disableDeviceBrand() } diff --git a/android/src/main/kotlin/com/amplitude/amplitude_flutter/FlutterLibraryPlugin.kt b/android/src/main/kotlin/com/amplitude/amplitude_flutter/FlutterLibraryPlugin.kt index 8f15a30..c5b853e 100644 --- a/android/src/main/kotlin/com/amplitude/amplitude_flutter/FlutterLibraryPlugin.kt +++ b/android/src/main/kotlin/com/amplitude/amplitude_flutter/FlutterLibraryPlugin.kt @@ -3,19 +3,13 @@ package com.amplitude.amplitude_flutter import com.amplitude.core.Amplitude import com.amplitude.core.events.BaseEvent import com.amplitude.core.platform.Plugin -import com.amplitude.amplitude_flutter.BuildConfig -class FlutterLibraryPlugin: Plugin { +class FlutterLibraryPlugin(val library: String): Plugin { override val type: Plugin.Type = Plugin.Type.Before override lateinit var amplitude: Amplitude - companion object { - const val SDK_LIBRARY = "amplitude-flutter" - const val SDK_VERSION = BuildConfig.SDK_VERSION - } - override fun execute(event: BaseEvent): BaseEvent? { - event.library = "$SDK_LIBRARY/$SDK_VERSION" + event.library = library return super.execute(event) } } diff --git a/example/ios/Podfile b/example/ios/Podfile index 9411102..10f3c9b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 214938e..1ac8370 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,8 +1,10 @@ PODS: - - Amplitude (8.16.1) - amplitude_flutter (0.0.1): - - Amplitude (= 8.16.1) + - AmplitudeSwift (~> 1.0.0) - Flutter + - AmplitudeSwift (1.0.0): + - AnalyticsConnector (~> 1.0.1) + - AnalyticsConnector (1.0.3) - Flutter (1.0.0) DEPENDENCIES: @@ -11,7 +13,8 @@ DEPENDENCIES: SPEC REPOS: trunk: - - Amplitude + - AmplitudeSwift + - AnalyticsConnector EXTERNAL SOURCES: amplitude_flutter: @@ -20,10 +23,11 @@ EXTERNAL SOURCES: :path: Flutter SPEC CHECKSUMS: - Amplitude: ef9ed339ddd33c9183edf63fa4bbaa86cf873321 - amplitude_flutter: 8ddb231989e68ed8c005c838d7fc59edbca09833 - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + amplitude_flutter: 16812bda98a0de430b0021dbd59648c84b76f9ed + AmplitudeSwift: 17755f7599198721c32e26b884423759c2daec7d + AnalyticsConnector: a53214d38ae22734c6266106c0492b37832633a9 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 -PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea +PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index b0b9f94..387b2e8 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -215,12 +215,14 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Amplitude/Amplitude.framework", + "${BUILT_PRODUCTS_DIR}/AmplitudeSwift/AmplitudeSwift.framework", + "${BUILT_PRODUCTS_DIR}/AnalyticsConnector/AnalyticsConnector.framework", "${BUILT_PRODUCTS_DIR}/amplitude_flutter/amplitude_flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Amplitude.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AmplitudeSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AnalyticsConnector.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/amplitude_flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/.swiftlint.yml b/ios/.swiftlint.yml new file mode 100644 index 0000000..836179a --- /dev/null +++ b/ios/.swiftlint.yml @@ -0,0 +1,21 @@ +disabled_rules: + - file_length + - line_length + - function_body_length + - type_body_length + - trailing_comma + - opening_brace + - todo + - cyclomatic_complexity +identifier_name: + allowed_symbols: "_" + min_length: 1 +nesting: + type_level: + warning: 3 + error: 6 + function_level: + warning: 5 + error: 10 +excluded: + - Examples diff --git a/ios/Classes/FlutterLibraryPlugin.swift b/ios/Classes/FlutterLibraryPlugin.swift new file mode 100644 index 0000000..d505751 --- /dev/null +++ b/ios/Classes/FlutterLibraryPlugin.swift @@ -0,0 +1,16 @@ +import Foundation +import AmplitudeSwift + +class FlutterLibraryPlugin: BeforePlugin { + let library: String + + init(library: String) { + self.library = library + } + + override func execute(event: BaseEvent) -> BaseEvent? { + event.library = library + + return event + } +} diff --git a/ios/Classes/SwiftAmplitudeFlutterPlugin.swift b/ios/Classes/SwiftAmplitudeFlutterPlugin.swift index 3c5ff32..39abc1d 100644 --- a/ios/Classes/SwiftAmplitudeFlutterPlugin.swift +++ b/ios/Classes/SwiftAmplitudeFlutterPlugin.swift @@ -1,252 +1,389 @@ import Flutter import UIKit -import Amplitude +import AmplitudeSwift @objc public class SwiftAmplitudeFlutterPlugin: NSObject, FlutterPlugin { + var amplitude: Amplitude? + static let methodChannelName = "amplitude_flutter" + public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "amplitude_flutter", binaryMessenger: registrar.messenger()) + let channel = FlutterMethodChannel(name: methodChannelName, binaryMessenger: registrar.messenger()) let instance = SwiftAmplitudeFlutterPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } - public func getPropertiesFromArguments(_ callArguments: Any?) throws -> [String:Any]? { - if let arguments = callArguments, let data = (arguments as! String).data(using: .utf8) { + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "init": + guard let args = call.arguments as? [String: Any] else { + print("\(call.method) called but call.arguments type casting failed.") + return + } + + do { + amplitude = Amplitude(configuration: try getConfiguration(args: args)) + } catch { + print("Initialization failed.") + } + + // Set library + amplitude?.add( + plugin: FlutterLibraryPlugin( + library: args["library"] as? String ?? "amplitude-flutter/unknown" + ) + ) + + amplitude?.logger?.debug(message: "Amplitude has been successfully initialized.") + + // TODO(xinyi): add app lifecycle events - let properties = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any] - return properties; + result("init called..") + + case "track", "identify", "groupIdentify", "setGroup", "revenue": + guard let args = call.arguments as? [String: Any] else { + print("\(call.method) called but call.arguments type casting failed.") + return + } + + do { + let event = try getEvent(args: args) + amplitude?.track(event: event) + amplitude?.logger?.debug(message: "Track \(call.method) event: \(String(describing: call.arguments))") + + result("\(call.method) called..") + } catch { + amplitude?.logger?.warn(message: "\(call.method) called but failed.") + } + + case "setUserId": + guard let args = call.arguments as? [String: Any] else { + print("\(call.method) called but call.arguments type casting failed.") + return + } + + guard let userId = args["setUserId"] as? String else { + amplitude?.logger?.warn(message: "setUserId type casting to String failed.") + return + } + amplitude?.setUserId(userId: userId) + amplitude?.logger?.debug(message: "Set user Id to \(String(describing: userId))") + + result("serUserId called..") + + case "setDeviceId": + guard let args = call.arguments as? [String: Any] else { + print("\(call.method) called but call.arguments type casting failed.") + return + } + guard let deviceId = args["setDeviceId"] as? String else { + amplitude?.logger?.warn(message: "setDeviceId type casting to String failed.") + return + } + amplitude?.setDeviceId(deviceId: deviceId) + amplitude?.logger?.debug(message: "Set device Id to \(String(describing: deviceId))") + + result("setDeviceId called..") + + case "reset": + amplitude?.reset() + amplitude?.logger?.debug(message: "Reset userId and deviceId.") + + result("reset called..") + + case "flush": + amplitude?.flush() + amplitude?.logger?.debug(message: "Flush events.") + + result("flush called..") + + default: + amplitude?.logger?.debug(message: "Method \(call.method) is not recognized.") + result(FlutterMethodNotImplemented) } - return nil; } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - do { - if let args = try getPropertiesFromArguments(call.arguments) { - let instanceName = args["instanceName"] as! String - - switch (call.method) { - // Init - case "init": - let apiKey = args["apiKey"] as! String - - if (apiKey.isEmpty) { - result(FlutterError.init(code: "EMPTY_APP_KEY", - message: "Please initialize the Amplitude with a valid API Key.", details: nil)) - return - } - - if let userId = args["userId"] as? String { - Amplitude.instance(withName: instanceName).initializeApiKey(apiKey, userId: userId) - } - else { - Amplitude.instance(withName: instanceName).initializeApiKey(apiKey) - } - - result(true) - - // Get userId - case "getUserId": - result(Amplitude.instance(withName: instanceName).userId); - // Get deviceId - case "getDeviceId": - let deviceId = Amplitude.instance(withName: instanceName).getDeviceId() - result(deviceId) - // Get sessionId - case "getSessionId": - let sessionId = Amplitude.instance(withName: instanceName).getSessionId() - result(sessionId) - - // Setters - case "enableCoppaControl": - Amplitude.instance(withName: instanceName).enableCoppaControl(); - result(true) - case "disableCoppaControl": - Amplitude.instance(withName: instanceName).disableCoppaControl(); - result(true) - case "setOptOut": - let optOut = args["optOut"] as! Bool - Amplitude.instance(withName: instanceName).optOut = optOut - result(true) - case "setLibraryName": - let libraryName = args["libraryName"] as! String - Amplitude.instance(withName: instanceName).libraryName = libraryName - result(true) - case "setLibraryVersion": - let libraryVersion = args["libraryVersion"] as! String - Amplitude.instance(withName: instanceName).libraryVersion = libraryVersion - result(true) - case "setEventUploadThreshold": - let eventUploadThreshold = args["eventUploadThreshold"] as! Int32 - Amplitude.instance(withName: instanceName).eventUploadThreshold = eventUploadThreshold - result(true) - case "setEventUploadPeriodMillis": - let eventUploadPeriodMillis = args["eventUploadPeriodMillis"] as! Int32 - Amplitude.instance(withName: instanceName).eventUploadPeriodSeconds = eventUploadPeriodMillis / 1000 - result(true) - case "trackingSessionEvents": - let trackingSessionEvents = args["trackingSessionEvents"] as! Bool - Amplitude.instance(withName: instanceName).trackingSessionEvents = trackingSessionEvents - result(true) - case "setUseDynamicConfig": - let useDynamicConfig = args["useDynamicConfig"] as! Bool - Amplitude.instance(withName: instanceName).useDynamicConfig = useDynamicConfig - result(true) - case "setUserId": - var userId: String? = nil - if !(args["userId"] is NSNull) { - userId = args["userId"] as! String? - } - let startNewSession = args["startNewSession"] == nil ? false : (args["startNewSession"] as! Bool) - - Amplitude.instance(withName: instanceName).setUserId(userId, startNewSession: startNewSession) - result(true) - case "setDeviceId": - if !(args["deviceId"] is NSNull) { - if let deviceId = args["deviceId"] as! String? { - Amplitude.instance(withName: instanceName).setDeviceId(deviceId) - } - } - - result(true) - case "setServerUrl": - if !(args["serverUrl"] is NSNull) { - if let serverUrl = args["serverUrl"] as! String? { - Amplitude.instance(withName: instanceName).setServerUrl(serverUrl) - } - } - - result(true) - - // Regenerates a new random deviceId for current user - case "regenerateDeviceId": - Amplitude.instance(withName: instanceName).regenerateDeviceId() - result(true) - - // Event logging - case "logEvent": - let eventType = args["eventType"] as! String - let eventProperties = args["eventProperties"] as! [String: Any]? - let outOfSession = args["outOfSession"] == nil ? false : (args["outOfSession"] as! Bool) - - Amplitude.instance(withName: instanceName).logEvent(eventType, - withEventProperties: eventProperties, - outOfSession: outOfSession) - result(true) - case "logRevenue": - let revenue = AMPRevenue() - revenue.setProductIdentifier((args["productIdentifier"] as! String)) - revenue.setQuantity(args["quantity"] as! Int) - revenue.setPrice(NSNumber(value: args["price"] as! Double)) - - Amplitude.instance(withName: instanceName).logRevenueV2(revenue) - - result(true) - - case "logRevenueAmount": - let revenue = AMPRevenue() - revenue.setPrice(NSNumber(value: args["amount"] as! Double)) - Amplitude.instance(withName: instanceName).logRevenueV2(revenue) - - result(true) - - case "identify": - let userProperties = args["userProperties"] as! [String: [String : NSObject]] - let identify: AMPIdentify = createIdentify(userProperties) - Amplitude.instance(withName: instanceName).identify(identify) - result(true) - - case "setGroup": - let groupType = args["groupType"] as! String - let groupName = args["groupName"] as! NSObject - Amplitude.instance(withName: instanceName).setGroup(groupType, groupName: groupName) - - result(true) - case "groupIdentify": - let groupType = args["groupType"] as! String - let groupName = args["groupName"] as! NSObject - let userProperties = args["userProperties"] as! [String: [String : NSObject]] - let outOfSession = args["outOfSession"] == nil ? false : (args["outOfSession"] as! Bool) - let identify: AMPIdentify = createIdentify(userProperties) - Amplitude.instance(withName: instanceName).groupIdentify(withGroupType: groupType, - groupName: groupName, - groupIdentify: identify, - outOfSession: outOfSession) - result(true) - - // User properties - case "setUserProperties": - let userProperties = args["userProperties"] as! [String: Any]? ?? [:] - Amplitude.instance(withName: instanceName).setUserProperties(userProperties) - result(true) - case "clearUserProperties": - Amplitude.instance(withName: instanceName).clearUserProperties() - result(true) - - case "uploadEvents": - Amplitude.instance(withName: instanceName).uploadEvents() - result(true) - - // this method is for android only - case "useAppSetIdForDeviceId": - result(false) - - case "setMinTimeBetweenSessionsMillis": - let timeInMillis = args["timeInMillis"] as! Int - Amplitude.instance(withName: instanceName).minTimeBetweenSessionsMillis = timeInMillis - result(true) - - case "setServerZone": - let serverZone = args["serverZone"] as! String - let updateServerUrl = args["updateServerUrl"] as! Bool - let ampServerZone = serverZone == "EU" ? AMPServerZone.EU : AMPServerZone.US - Amplitude.instance(withName: instanceName).setServerZone(ampServerZone, updateServerUrl: updateServerUrl) - result(true) - - case "setOffline": - let offline = args["offline"] as! Bool - Amplitude.instance(withName: instanceName).setOffline(offline) - result(true) - - default: - result(FlutterMethodNotImplemented) - } - } - } catch { - result(FlutterError.init(code: "EXCEPTION_IN_HANDLE", - message: "Exception happened in handle.", details: nil)) + private func getConfiguration(args: [String: Any]) throws -> Configuration { + guard let apiKey = args["apiKey"] as? String else { + print("apiKey type casting failed.") + throw AmplitudeFlutterPluginError.apiKeyNotFound } + + let instanceName = args["instanceName"] as? String ?? Constants.Configuration.DEFAULT_INSTANCE + let migrateLegacyData = args["migrateLegacyData"] as? Bool ?? true + + let configuration = Configuration( + apiKey: apiKey, + instanceName: instanceName, + migrateLegacyData: migrateLegacyData) + + if let flushQueueSize = args["flushQueueSize"] as? Int { + configuration.flushQueueSize = flushQueueSize + } + if let flushIntervalMillis = args["flushIntervalMillis"] as? Int { + configuration.flushIntervalMillis = flushIntervalMillis + } + if let optOut = args["optOut"] as? Bool { + configuration.optOut = optOut + } + if let logLevel = args["logLevel"] as? String { + configuration.logLevel = logLevelFromString(logLevel) + } + if let minIdLength = args["minIdLength"] as? Int { + configuration.minIdLength = minIdLength + } + if let partnerId = args["partnerId"] as? String { + configuration.partnerId = partnerId + } + if let flushMaxRetries = args["flushMaxRetries"] as? Int { + configuration.flushMaxRetries = flushMaxRetries + } + if let useBatch = args["useBatch"] as? Bool { + configuration.useBatch = useBatch + } + if let serverZone = args["serverZone"] as? String, let serverZoneValue = ServerZone( + rawValue: serverZone.uppercased()) { + configuration.serverZone = serverZoneValue + } + if let serverUrl = args["serverUrl"] as? String { + configuration.serverUrl = serverUrl + } + if let trackingOptionsDict = args["trackingOptions"] as? [String: Any] { + configuration.trackingOptions = convertMapToTrackingOptions(map: trackingOptionsDict) + } + if let enableCoppaControl = args["enableCoppaControl"] as? Bool { + configuration.enableCoppaControl = enableCoppaControl + } + if let flushEventsOnClose = args["flushEventsOnClose"] as? Bool { + configuration.flushEventsOnClose = flushEventsOnClose + } + if let minTimeBetweenSessionsMillis = args["minTimeBetweenSessionsMillis"] as? Int { + configuration.minTimeBetweenSessionsMillis = minTimeBetweenSessionsMillis + } + if let identifyBatchIntervalMillis = args["identifyBatchIntervalMillis"] as? Int { + configuration.identifyBatchIntervalMillis = identifyBatchIntervalMillis + } + if let defaultTrackingDict = args["defaultTracking"] as? [String: Bool] { + let sessions = defaultTrackingDict["sessions"] ?? true + let appLifecycles = defaultTrackingDict["appLifecycles"] ?? false + let screenViews = defaultTrackingDict["screenViews"] ?? false + configuration.defaultTracking = DefaultTrackingOptions( + sessions: sessions, + appLifecycles: appLifecycles, + screenViews: screenViews + ) + } + + return configuration } - private func createIdentify(_ userProperties: [String: [String : NSObject]]) -> AMPIdentify { - let identify = AMPIdentify() - - for (operation, properties) in userProperties { - for (key, value) in properties { - switch operation { - case "$add": - identify.add(key, value: value) - case "$append": - identify.append(key, value: value) - case "$prepend": - identify.prepend(key, value: value) - case "$set": - identify.set(key, value: value) - case "$setOnce": - identify.setOnce(key, value: value) - case "$unset": - identify.unset(key) // value is default to `-` - case "$preInsert": - identify.preInsert(key, value: value) - case "$postInsert": - identify.postInsert(key, value: value) - case "$remove": - identify.remove(key, value: value) - case "$clearAll": - identify.clearAll() - default: - break - } - } + private func logLevelFromString(_ logLevelString: String) -> LogLevelEnum { + switch logLevelString.lowercased() { + case "off": + return .OFF + case "error": + return .ERROR + case "warn": + return .WARN + case "log": + return .LOG + case "debug": + return .DEBUG + default: + return .DEBUG + } + } + + private func convertMapToTrackingOptions(map: [String: Any]) -> TrackingOptions { + let trackingOptions = TrackingOptions() + + if let ipAddress = map["ipAddress"] as? Bool, !ipAddress { + trackingOptions.disableTrackIpAddress() + } + if let language = map["language"] as? Bool, !language { + trackingOptions.disableTrackLanguage() + } + if let platform = map["platform"] as? Bool, !platform { + trackingOptions.disableTrackPlatform() + } + if let region = map["region"] as? Bool, !region { + trackingOptions.disableTrackRegion() + } + if let dma = map["dma"] as? Bool, !dma { + trackingOptions.disableTrackDMA() } - return identify + if let country = map["country"] as? Bool, !country { + trackingOptions.disableTrackCountry() + } + if let city = map["city"] as? Bool, !city { + trackingOptions.disableTrackCity() + } + if let carrier = map["carrier"] as? Bool, !carrier { + trackingOptions.disableTrackCarrier() + } + if let deviceModel = map["deviceModel"] as? Bool, !deviceModel { + trackingOptions.disableTrackDeviceModel() + } + if let deviceManufacturer = map["deviceManufacturer"] as? Bool, !deviceManufacturer { + trackingOptions.disableTrackDeviceManufacturer() + } + if let osVersion = map["osVersion"] as? Bool, !osVersion { + trackingOptions.disableTrackOsVersion() + } + if let osName = map["osName"] as? Bool, !osName { + trackingOptions.disableTrackOsName() + } + if let versionName = map["versionName"] as? Bool, !versionName { + trackingOptions.disableTrackVersionName() + } + if let idfv = map["idfv"] as? Bool, !idfv { + trackingOptions.disableTrackIDFV() + } + + return trackingOptions + } + + private func getEvent(args: [String: Any]) throws -> BaseEvent { + guard let eventType = args["event_type"] as? String else { + amplitude?.logger?.warn(message: "eventType type casting failed.") + throw AmplitudeFlutterPluginError.eventTypeNotFound + } + + let event = BaseEvent(eventType: eventType) + + if let eventProperties = args["event_properties"] as? [String: Any] { + event.eventProperties = eventProperties + } + if let userProperties = args["user_properties"] as? [String: Any] { + event.userProperties = userProperties + } + if let groups = args["groups"] as? [String: Any] { + event.groups = groups + } + if let groupProperties = args["group_properties"] as? [String: Any] { + event.groupProperties = groupProperties + } + if let userId = args["user_id"] as? String { + event.userId = userId + } + if let deviceId = args["device_id"] as? String { + event.deviceId = deviceId + } + if let timestamp = args["timestamp"] as? Int { + event.timestamp = Int64(timestamp) + } + if let eventId = args["event_id"] as? Int { + event.eventId = Int64(eventId) + } + if let sessionId = args["session_id"] as? Int { + event.sessionId = Int64(sessionId) + } + if let insertId = args["insert_id"] as? String { + event.insertId = insertId + } + if let locationLat = args["location_lat"] as? Double { + event.locationLat = locationLat + } + if let locationLng = args["location_lng"] as? Double { + event.locationLng = locationLng + } + if let appVersion = args["app_version"] as? String { + event.appVersion = appVersion + } + if let versionName = args["version_name"] as? String { + event.versionName = versionName + } + if let platform = args["platform"] as? String { + event.platform = platform + } + if let osName = args["os_name"] as? String { + event.osName = osName + } + if let osVersion = args["os_version"] as? String { + event.osVersion = osVersion + } + if let deviceBrand = args["device_brand"] as? String { + event.deviceBrand = deviceBrand + } + if let deviceManufacturer = args["device_manufacturer"] as? String { + event.deviceManufacturer = deviceManufacturer + } + if let deviceModel = args["device_model"] as? String { + event.deviceModel = deviceModel + } + if let carrier = args["carrier"] as? String { + event.carrier = carrier + } + if let country = args["country"] as? String { + event.country = country + } + if let region = args["region"] as? String { + event.region = region + } + if let city = args["city"] as? String { + event.city = city + } + if let dma = args["dma"] as? String { + event.dma = dma + } + if let idfa = args["idfa"] as? String { + event.idfa = idfa + } + if let idfv = args["idfv"] as? String { + event.idfv = idfv + } + if let adid = args["adid"] as? String { + event.adid = adid + } + if let language = args["language"] as? String { + event.language = language + } + if let library = args["library"] as? String { + event.library = library + } + if let ip = args["ip"] as? String { + event.ip = ip + } + if let planMap = args["plan"] as? [String: Any] { + event.plan = Plan( + branch: planMap["branch"] as? String, + source: planMap["source"] as? String, + version: planMap["version"] as? String, + versionId: planMap["versionId"] as? String + ) + } + if let ingestionMetadataMap = args["ingestion_metadata"] as? [String: Any] { + event.ingestionMetadata = IngestionMetadata( + sourceName: ingestionMetadataMap["sourceName"] as? String, + sourceVersion: ingestionMetadataMap["sourceVersion"] as? String + ) + } + if let revenue = args["revenue"] as? Double { + event.revenue = revenue + } + if let price = args["price"] as? Double { + event.price = price + } + if let quantity = args["quantity"] as? Int { + event.quantity = quantity + } + if let productId = args["product_id"] as? String { + event.productId = productId + } + if let revenueType = args["revenue_type"] as? String { + event.revenueType = revenueType + } + if let extra = args["extra"] as? [String: Any] { + event.extra = extra + } + if let partnerId = args["partner_id"] as? String { + event.partnerId = partnerId + } + + return event + } + + enum AmplitudeFlutterPluginError: Error { + case apiKeyNotFound + case eventTypeNotFound } } diff --git a/ios/amplitude_flutter.podspec b/ios/amplitude_flutter.podspec index 5b5cadf..169a40b 100644 --- a/ios/amplitude_flutter.podspec +++ b/ios/amplitude_flutter.podspec @@ -12,8 +12,8 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'Amplitude', '8.16.1' + s.dependency 'AmplitudeSwift', '~> 1.0.0' - s.ios.deployment_target = '10.0' + s.ios.deployment_target = '13.0' end diff --git a/lib/configuration.dart b/lib/configuration.dart index ce89b9f..958c2f0 100644 --- a/lib/configuration.dart +++ b/lib/configuration.dart @@ -93,6 +93,9 @@ class Configuration { 'useAdvertisingIdForDeviceId': useAdvertisingIdForDeviceId, 'useAppSetIdForDeviceId': useAppSetIdForDeviceId, 'appVersion': appVersion, + // This field doesn't belong to Configuration + // Pass it for FlutterLibraryPlugin + 'library': "${Constants.packageName}/${Constants.packageVersion}" }; } } diff --git a/lib/constants.dart b/lib/constants.dart index a11aab5..bc9480e 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1,4 +1,6 @@ class Constants { + static const packageName = "amplitude-flutter"; + static const packageVersion = "3.16.1"; static const identify_event = "\$identify"; static const group_identify_event = "\$groupidentify"; static const revenue_event = "revenue_amount"; diff --git a/release.config.js b/release.config.js index 7b5b234..28bebf7 100644 --- a/release.config.js +++ b/release.config.js @@ -63,20 +63,6 @@ module.exports = { ], "countMatches": true }, - { - "files": ["android/build.gradle"], - "from": "PUBLISH_VERSION = \'.*\'", - "to": "PUBLISH_VERSION = \'${nextRelease.version}\'", - "results": [ - { - "file": "android/build.gradle", - "hasChanged": true, - "numMatches": 1, - "numReplacements": 1 - } - ], - "countMatches": true - }, ] } ], diff --git a/test/amplitude_test.dart b/test/amplitude_test.dart index 34c7724..a089422 100644 --- a/test/amplitude_test.dart +++ b/test/amplitude_test.dart @@ -83,6 +83,9 @@ void main() { "useAdvertisingIdForDeviceId": false, "useAppSetIdForDeviceId": false, "appVersion": null, + // This field doesn't belong to Configuration + // Pass it for FlutterLibraryPlugin + "library": "${Constants.packageName}/${Constants.packageVersion}" }; final testEvent = BaseEvent(eventType: "testEvent"); final testEventMap = {