diff --git a/android/build.gradle b/android/build.gradle index 365374b..1811f6f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,14 +2,14 @@ group 'com.amplitude.amplitude_flutter' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.5.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:8.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -25,7 +25,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 28 + compileSdkVersion 34 // Condition for namespace compatibility in AGP 8 if (project.android.hasProperty("namespace")) { namespace 'com.amplitude.amplitude_flutter' @@ -44,6 +44,20 @@ android { } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "com.amplitude:android-sdk:2.39.8" - implementation "com.squareup.okhttp3:okhttp:4.2.2" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.10" + + implementation 'com.amplitude:analytics-android:[1.0,2.0)' + testImplementation 'org.mockito:mockito-core:4.0.0' + testImplementation "io.mockk:mockk:1.12.4" + testImplementation "io.mockk:mockk-agent-jvm:1.11.0" + testImplementation('junit:junit:4.13') + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.7.2") + testImplementation 'org.robolectric:robolectric:4.8.2' +} + +tasks.withType(Test) { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } } diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..fe1a99c --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega 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 f811637..88693bb 100644 --- a/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt +++ b/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt @@ -1,260 +1,136 @@ package com.amplitude.amplitude_flutter -import android.app.Application import android.content.Context -import com.amplitude.api.Amplitude -import com.amplitude.api.AmplitudeServerZone -import com.amplitude.api.Identify -import com.amplitude.api.Revenue +import com.amplitude.android.Amplitude +import com.amplitude.android.Configuration +import com.amplitude.android.DefaultTrackingOptions +import com.amplitude.android.TrackingOptions +import com.amplitude.android.events.IngestionMetadata +import com.amplitude.android.events.Plan +import com.amplitude.common.Logger +import com.amplitude.core.events.BaseEvent import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar -import org.json.JSONArray import org.json.JSONObject class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler { - companion object { + lateinit var amplitude: Amplitude - private const val methodChannelName = "amplitude_flutter" + lateinit var ctxt: Context - var ctxt: Context? = null + private lateinit var channel: MethodChannel - @JvmStatic - fun registerWith(registrar: Registrar) { - ctxt = registrar.context() - val channel = MethodChannel(registrar.messenger(), methodChannelName) - channel.setMethodCallHandler(AmplitudeFlutterPlugin()) - } + + companion object { + private const val methodChannelName = "amplitude_flutter" + private const val defaultMinIdLength = 5 } override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { ctxt = binding.applicationContext - val channel = MethodChannel(binding.binaryMessenger, methodChannelName) - channel.setMethodCallHandler(AmplitudeFlutterPlugin()) + channel = MethodChannel(binding.binaryMessenger, methodChannelName) + channel.setMethodCallHandler(this) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) } override fun onMethodCall(call: MethodCall, result: Result) { - val json = JSONObject(call.arguments.toString()) - val instanceName = json["instanceName"].toString() + val json = JSONObject(call.arguments?.toString() ?: "{}") when (call.method) { "init" -> { - val client = Amplitude.getInstance(instanceName) - client.initialize( - ctxt, json.getString("apiKey"), - json.optString("userId", null) + val trackingOptions = json.getJSONObject("trackingOptions") + val defaultTracking = json.getJSONObject("defaultTracking") + amplitude = Amplitude( + Configuration( + apiKey = json.getString("apiKey"), + context = ctxt!!, + flushQueueSize = json.getInt("flushQueueSize"), + flushIntervalMillis = json.getInt("flushIntervalMillis"), + instanceName = json.getString("instanceName"), + optOut = json.getBoolean("optOut"), + minIdLength = if (json.optInt("minIdLength") < 1) defaultMinIdLength else json.optInt( + "minIdLength" + ), + partnerId = json.getString("partnerId"), + flushMaxRetries = json.getInt("flushMaxRetries"), + useBatch = json.getBoolean("useBatch"), + serverZone = com.amplitude.core.ServerZone.valueOf( + json.getString("serverZone").uppercase() + ), + serverUrl = json.getString("serverUrl"), + minTimeBetweenSessionsMillis = json.getLong("minTimeBetweenSessionsMillis"), + defaultTracking = DefaultTrackingOptions( + sessions = defaultTracking.getBoolean("sessions"), + appLifecycles = defaultTracking.getBoolean("appLifecycles"), + deepLinks = defaultTracking.getBoolean("deepLinks"), + screenViews = defaultTracking.getBoolean("screenViews"), + ), + trackingOptions = getTrackingOptions(trackingOptions), + enableCoppaControl = json.getBoolean("enableCoppaControl"), + flushEventsOnClose = json.getBoolean("flushEventsOnClose"), + identifyBatchIntervalMillis = json.getLong("identifyBatchIntervalMillis"), + migrateLegacyData = json.getBoolean("migrateLegacyData"), + locationListening = json.getBoolean("locationListening"), + useAdvertisingIdForDeviceId = json.getBoolean("useAdvertisingIdForDeviceId"), + useAppSetIdForDeviceId = json.getBoolean("useAppSetIdForDeviceId"), + ) ) - val application = ctxt?.applicationContext - if (application is Application) { - client.enableForegroundTracking(application) - } - - result.success("Init success..") - } - // Get userId - "getUserId" -> { - val client = Amplitude.getInstance(instanceName) - result.success(client.userId) - } - // Get deviceId - "getDeviceId" -> { - val client = Amplitude.getInstance(instanceName) - val deviceId = client.getDeviceId() - - result.success(deviceId) - } - // Get sessionId - "getSessionId" -> { - val client = Amplitude.getInstance(instanceName) - val sessionId = client.getSessionId() - - result.success(sessionId) - } - // Settings - "enableCoppaControl" -> { - val client = Amplitude.getInstance(instanceName) - client.enableCoppaControl() - - result.success("enableCoppaControl called..") - } - "disableCoppaControl" -> { - val client = Amplitude.getInstance(instanceName) - client.disableCoppaControl() - - result.success("disableCoppaControl called..") - } - "setOptOut" -> { - val client = Amplitude.getInstance(instanceName) - client.setOptOut(json.getBoolean("optOut")) - - result.success("setOptOut called..") - } - "setLibraryName" -> { - val client = Amplitude.getInstance(instanceName) - client.setLibraryName(json.getString("libraryName")) - - result.success("setLibraryName called..") - } - "setLibraryVersion" -> { - val client = Amplitude.getInstance(instanceName) - client.setLibraryVersion(json.getString("libraryVersion")) - - result.success("setLibraryVersion called..") - } - "setEventUploadThreshold" -> { - val client = Amplitude.getInstance(instanceName) - client.setEventUploadThreshold(json.getInt("eventUploadThreshold")) - - result.success("setEventUploadThreshold called..") - } - "setEventUploadPeriodMillis" -> { - val client = Amplitude.getInstance(instanceName) - client.setEventUploadPeriodMillis(json.getInt("eventUploadPeriodMillis")) - - result.success("setEventUploadPeriodMillis called..") - } - "trackingSessionEvents" -> { - val client = Amplitude.getInstance(instanceName) - client.trackSessionEvents(json.getBoolean("trackingSessionEvents")) - - result.success("trackingSessionEvents called..") - } - "setUseDynamicConfig" -> { - val client = Amplitude.getInstance(instanceName) - client.setUseDynamicConfig(json.getBoolean("useDynamicConfig")) - - result.success("setUseDynamicConfig called..") - } - "setUserId" -> { - val client = Amplitude.getInstance(instanceName) - client.setUserId(json.optString("userId", null), json.optBoolean("startNewSession", false)) - - result.success("setUserId called..") - } - "setDeviceId" -> { - val client = Amplitude.getInstance(instanceName) - client.setDeviceId(json.optString("deviceId", null)) - - result.success("setDeviceId called..") - } - "setServerUrl" -> { - val client = Amplitude.getInstance(instanceName) - client.setServerUrl(json.optString("serverUrl", null)) - - result.success("setServerUrl called..") - } - - // Regenerate new deviceId - "regenerateDeviceId" -> { - val client = Amplitude.getInstance(instanceName) - client.regenerateDeviceId() - result.success("regenerateDeviceId called..") - } - - // Event logging - "logEvent" -> { - val client = Amplitude.getInstance(instanceName) - client.logEvent( - json.getString("eventType"), - json.optJSONObject("eventProperties"), - json.optBoolean("outOfSession", false) + amplitude.logger.logMode = Logger.LogMode.valueOf( + json.getString("logLevel").uppercase() ) - result.success("logEvent called..") + result.success("init called..") } - "logRevenue" -> { - val client = Amplitude.getInstance(instanceName) - val revenue = Revenue().setProductId(json.getString("productIdentifier")) - .setPrice(json.getDouble("price")) - .setQuantity(json.getInt("quantity")) - client.logRevenueV2(revenue) - result.success("logRevenue called..") + "track" -> { + amplitude.track(getEvent(json)) + result.success("track called..") } - "logRevenueAmount" -> { - val client = Amplitude.getInstance(instanceName) - val revenue = Revenue().setPrice(json.getDouble("amount")) - client.logRevenueV2(revenue) - result.success("logRevenueAmount called..") - } "identify" -> { - val client = Amplitude.getInstance(instanceName) - val identify = createIdentify(json.getJSONObject("userProperties")) - client.identify(identify) - + amplitude.track(getEvent(json)) result.success("identify called..") } - "setGroup" -> { - val client = Amplitude.getInstance(instanceName) - client.setGroup(json.getString("groupType"), json.get("groupName")) - result.success("identify called..") - } "groupIdentify" -> { - val client = Amplitude.getInstance(instanceName) - val identify = createIdentify(json.getJSONObject("userProperties")) - client.groupIdentify( - json.getString("groupType"), - json.getString("groupName"), - identify, - json.optBoolean("outOfSession", false) - ) - - result.success("identify called..") + amplitude.track(getEvent(json)) + result.success("groupIdentify called..") } - "setUserProperties" -> { - val client = Amplitude.getInstance(instanceName) - client.setUserProperties(json.getJSONObject("userProperties")) - result.success("setUserProperties called..") - } - "clearUserProperties" -> { - val client = Amplitude.getInstance(instanceName) - client.clearUserProperties() - - result.success("clearUserProperties called..") + "setGroup" -> { + amplitude.track(getEvent(json)) + result.success("setGroup called..") } - "uploadEvents" -> { - val client = Amplitude.getInstance(instanceName) - client.uploadEvents() - result.success("uploadEvents called..") + "revenue" -> { + amplitude.track(getEvent(json)) + result.success("revenue called..") } - "useAppSetIdForDeviceId" -> { - val client = Amplitude.getInstance(instanceName) - client.useAppSetIdForDeviceId() - - result.success("useAppSetIdForDeviceId called..") + "setUserId" -> { + amplitude.setUserId(json.getString("setUserId")) + result.success("setUserId called..") } - "setMinTimeBetweenSessionsMillis" -> { - val client = Amplitude.getInstance(instanceName) - client.setMinTimeBetweenSessionsMillis(json.getLong("timeInMillis")) - result.success("setMinTimeBetweenSessionsMillis called..") + "setDeviceId" -> { + amplitude.setDeviceId(json.getString("setDeviceId")) + result.success("setDeviceId called..") } - "setServerZone" -> { - val client = Amplitude.getInstance(instanceName) - val serverZone = json.getString("serverZone") - val amplitudeServerZone = if (serverZone == "EU") AmplitudeServerZone.EU else AmplitudeServerZone.US - client.setServerZone(amplitudeServerZone, json.getBoolean("updateServerUrl")) - - result.success("setServerZone called..") + "reset" -> { + amplitude.reset() + result.success("reset called..") } - "setOffline" -> { - val client = Amplitude.getInstance(instanceName) - client.setOffline(json.getBoolean("offline")) - - result.success("setOffline called..") + "flush" -> { + amplitude.flush() + result.success("flush called..") } else -> { @@ -263,236 +139,157 @@ class AmplitudeFlutterPlugin : FlutterPlugin, MethodCallHandler { } } - private fun createIdentify(userProperties: JSONObject): Identify { - var identify = Identify() - - for (operation in userProperties.keys()) { - val properties = userProperties.getJSONObject(operation) - for (key in properties.keys()) { - when (operation) { - // ADD - "\$add" -> { - when (properties.get(key)) { - is Int -> { - identify.add(key, properties.getInt(key)) - } - is Long -> { - identify.add(key, properties.getLong(key)) - } - is Double -> { - identify.add(key, properties.getDouble(key)) - } - is String -> { - identify.add(key, properties.getString(key)) - } - is JSONObject -> { - identify.add(key, properties.getJSONObject(key)) - } - } - } - - // APPEND - "\$append" -> { - when (properties.get(key)) { - is Int -> { - identify.append(key, properties.getInt(key)) - } - is Long -> { - identify.append(key, properties.getLong(key)) - } - is Double -> { - identify.append(key, properties.getDouble(key)) - } - is String -> { - identify.append(key, properties.getString(key)) - } - is Boolean -> { - identify.append(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.append(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.prepend(key, properties.getJSONArray(key)) - } - } - } - - // REMOVE - "\$remove" -> { - when (properties.get(key)) { - is Int -> { - identify.remove(key, properties.getInt(key)) - } - is Long -> { - identify.remove(key, properties.getLong(key)) - } - is Double -> { - identify.remove(key, properties.getDouble(key)) - } - is String -> { - identify.remove(key, properties.getString(key)) - } - is Boolean -> { - identify.remove(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.remove(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.remove(key, properties.getJSONArray(key)) - } - } - } - - // PREPEND - "\$prepend" -> { - when (properties.get(key)) { - is Int -> { - identify.prepend(key, properties.getInt(key)) - } - is Long -> { - identify.prepend(key, properties.getLong(key)) - } - is Double -> { - identify.prepend(key, properties.getDouble(key)) - } - is String -> { - identify.prepend(key, properties.getString(key)) - } - is Boolean -> { - identify.prepend(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.prepend(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.prepend(key, properties.getJSONArray(key)) - } - } - } - - // SET - "\$set" -> { - when (properties.get(key)) { - is Int -> { - identify.set(key, properties.getInt(key)) - } - is Long -> { - identify.set(key, properties.getLong(key)) - } - is Double -> { - identify.set(key, properties.getDouble(key)) - } - is String -> { - identify.set(key, properties.getString(key)) - } - is Boolean -> { - identify.set(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.set(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.set(key, properties.getJSONArray(key)) - } - } - } - - // SETONCE - "\$setOnce" -> { - when (properties.get(key)) { - is Int -> { - identify.setOnce(key, properties.getInt(key)) - } - is Long -> { - identify.setOnce(key, properties.getLong(key)) - } - is Double -> { - identify.setOnce(key, properties.getDouble(key)) - } - is String -> { - identify.setOnce(key, properties.getString(key)) - } - is Boolean -> { - identify.setOnce(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.setOnce(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.setOnce(key, properties.getJSONArray(key)) - } - } - } - - // PREINSERT - "\$preInsert" -> { - when (properties.get(key)) { - is Int -> { - identify.preInsert(key, properties.getInt(key)) - } - is Long -> { - identify.preInsert(key, properties.getLong(key)) - } - is Double -> { - identify.preInsert(key, properties.getDouble(key)) - } - is String -> { - identify.preInsert(key, properties.getString(key)) - } - is Boolean -> { - identify.preInsert(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.preInsert(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.preInsert(key, properties.getJSONArray(key)) - } - } - } + internal fun getTrackingOptions(jsonObject: JSONObject): TrackingOptions { + val trackingOptions = TrackingOptions() + if (!jsonObject.getBoolean("ipAddress")) { + trackingOptions.disableIpAddress() + } + if (!jsonObject.getBoolean("language")) { + trackingOptions.disableLanguage() + } + if (!jsonObject.getBoolean("platform")) { + trackingOptions.disablePlatform() + } + if (!jsonObject.getBoolean("region")) { + trackingOptions.disableRegion() + } + if (!jsonObject.getBoolean("dma")) { + trackingOptions.disableDma() + } + if (!jsonObject.getBoolean("country")) { + trackingOptions.disableCountry() + } + if (!jsonObject.getBoolean("city")) { + trackingOptions.disableCity() + } + if (!jsonObject.getBoolean("carrier")) { + trackingOptions.disableCarrier() + } + if (!jsonObject.getBoolean("deviceModel")) { + trackingOptions.disableDeviceModel() + } + if (!jsonObject.getBoolean("deviceManufacturer")) { + trackingOptions.disableDeviceManufacturer() + } + if (!jsonObject.getBoolean("osVersion")) { + trackingOptions.disableOsVersion() + } + if (!jsonObject.getBoolean("osName")) { + trackingOptions.disableOsName() + } + if (!jsonObject.getBoolean("adid")) { + trackingOptions.disableAdid() + } + if (!jsonObject.getBoolean("appSetId")) { + trackingOptions.disableAppSetId() + } + if (!jsonObject.getBoolean("deviceBrand")) { + trackingOptions.disableDeviceBrand() + } + if (!jsonObject.getBoolean("latLag")) { + trackingOptions.disableLatLng() + } + if (!jsonObject.getBoolean("apiLevel")) { + trackingOptions.disableApiLevel() + } - // POSTINSERT - "\$postInsert" -> { - when (properties.get(key)) { - is Int -> { - identify.postInsert(key, properties.getInt(key)) - } - is Long -> { - identify.postInsert(key, properties.getLong(key)) - } - is Double -> { - identify.postInsert(key, properties.getDouble(key)) - } - is String -> { - identify.postInsert(key, properties.getString(key)) - } - is Boolean -> { - identify.postInsert(key, properties.getBoolean(key)) - } - is JSONObject -> { - identify.postInsert(key, properties.getJSONObject(key)) - } - is JSONArray -> { - identify.postInsert(key, properties.getJSONArray(key)) - } - } - } + return trackingOptions + } - // UNSET - "\$unset" -> { - identify.unset(key) - } + internal fun getEvent(json: JSONObject): BaseEvent { + val plan = json.getJSONObject("plan") + val ingestionMetadata = json.getJSONObject("ingestion_metadata") + val event = BaseEvent() + event.eventType = json.getString("event_type") + event.eventProperties = json.optJSONObject("event_properties")?.let { + it.toMutableMap() + } ?: null + event.userProperties = json.optJSONObject("user_properties")?.let { + it.toMutableMap() + } ?: null + event.groups = json.optJSONObject("groups")?.let { + it.toMutableMap() + } ?: null + event.groupProperties = json.optJSONObject("group_properties")?.let { + it.toMutableMap() + } ?: null + event.userId = json.getString("user_id") + event.deviceId = json.getString("device_id") + event.timestamp = json.optLong("timestamp") + event.eventId = json.optLong("event_id") + event.sessionId = json.optLong("session_id") + event.insertId = json.optString("insert_id") + event.locationLat = json.optDouble("location_lat") + event.locationLng = json.optDouble("location_lng") + event.appVersion = json.getString("app_version") + event.versionName = json.getString("version_name") + event.platform = json.getString("platform") + event.osName = json.getString("os_name") + event.osVersion = json.getString("os_version") + event.deviceBrand = json.getString("device_brand") + event.deviceManufacturer = json.getString("device_manufacturer") + event.deviceModel = json.getString("device_model") + event.carrier = json.getString("carrier") + event.country = json.getString("country") + event.region = json.getString("region") + event.city = json.getString("city") + event.dma = json.getString("dma") + event.idfa = json.getString("idfa") + event.idfv = json.getString("idfv") + event.adid = json.getString("adid") + event.appSetId = json.getString("app_set_id") + event.androidId = json.getString("android_id") + event.language = json.getString("language") + event.library = json.getString("library") + event.ip = json.getString("ip") + event.plan = Plan( + plan.getString("branch"), + plan.getString("source"), + plan.getString("version"), + plan.getString("versionId") + ) + event.ingestionMetadata = IngestionMetadata( + ingestionMetadata.getString("sourceName"), + ingestionMetadata.getString("sourceVersion") + ) + event.revenue = json.optDouble("revenue") + event.price = json.optDouble("price") + event.quantity = json.optInt("quantity") + event.productId = json.getString("product_id") + event.revenueType = json.getString("revenue_type") + event.extra = json.optJSONObject("extra")?.let { + it.toMap() + } ?: null + event.partnerId = json.getString("partner_id") + + return event + } - // CLEARALL - "\$clearAll" -> { - identify.clearAll() - } - } + internal fun JSONObject.toMutableMap(): MutableMap { + val map = mutableMapOf() + val keys = keys() + while (keys.hasNext()) { + val key = keys.next() + var value = get(key) + if (value is JSONObject) { + value = value.toMap() } + map[key] = value } + return map + } - return identify + internal fun JSONObject.toMap(): Map { + val map = mutableMapOf() + val keys = keys() + while (keys.hasNext()) { + val key = keys.next() + var value = get(key) + if (value is JSONObject) { + value = value.toMap() + } + map[key] = value + } + return map } } diff --git a/android/src/test/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPluginTest.kt b/android/src/test/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPluginTest.kt new file mode 100644 index 0000000..2ca00a8 --- /dev/null +++ b/android/src/test/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPluginTest.kt @@ -0,0 +1,283 @@ +package com.amplitude.amplitude_flutter + +import android.content.Context +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.json.JSONObject +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@Config(manifest=Config.NONE) +@RunWith(RobolectricTestRunner::class) +class AmplitudeFlutterPluginTest { + private val plugin = AmplitudeFlutterPlugin() + private val result = spyk() + private val binding = mockk(relaxed = true) + private val context = mockk() + + private lateinit var testConfigurationMap: MutableMap + private lateinit var testEventMap: MutableMap + + @Before + fun setup() { + testConfigurationMap = mutableMapOf( + "apiKey" to "test-api-key", + "flushQueueSize" to 30, + "flushIntervalMillis" to 30 * 1000, // 30 seconds + "instanceName" to "\$default_instance", + "optOut" to false, + "logLevel" to "info", + "minIdLength" to null, + "partnerId" to null, + "flushMaxRetries" to 5, + "useBatch" to false, + "serverZone" to "us", + "serverUrl" to null, + "minTimeBetweenSessionsMillis" to 5 * 60 * 1000, // 5 minutes + "defaultTracking" to JSONObject(mapOf( + "sessions" to true, + "appLifecycles" to false, + "screenViews" to false, + "deepLinks" to false, + "attribution" to true, + "pageViews" to true, + "formInteractions" to true, + "fileDownloads" to true + )), + "trackingOptions" to JSONObject(mapOf( + "ipAddress" to true, + "language" to true, + "platform" to true, + "region" to true, + "dma" to true, + "country" to true, + "city" to true, + "carrier" to true, + "deviceModel" to true, + "deviceManufacturer" to true, + "osVersion" to true, + "osName" to true, + "versionName" to true, + "adid" to true, + "appSetId" to true, + "deviceBrand" to true, + "latLag" to true, + "apiLevel" to true, + "idfv" to true + )), + "enableCoppaControl" to false, + "flushEventsOnClose" to true, + "identifyBatchIntervalMillis" to 30 * 1000, + "migrateLegacyData" to true, + "locationListening" to true, + "useAdvertisingIdForDeviceId" to false, + "useAppSetIdForDeviceId" to false, + "appVersion" to null + ) + testEventMap = mutableMapOf( + "event_type" to "testEvent", + "event_properties" to null, + "user_properties" to null, + "groups" to null, + "group_properties" to null, + "user_id" to null, + "device_id" to "test device id", + "timestamp" to null, + "event_id" to null, + "session_id" to null, + "insert_id" to null, + "location_lat" to null, + "location_lng" to null, + "app_version" to null, + "version_name" to null, + "platform" to null, + "os_name" to null, + "os_version" to null, + "device_brand" to null, + "device_manufacturer" to null, + "device_model" to null, + "carrier" to null, + "country" to null, + "region" to null, + "city" to null, + "dma" to null, + "idfa" to null, + "idfv" to null, + "adid" to null, + "app_set_id" to null, + "android_id" to null, + "language" to null, + "library" to null, + "ip" to null, + "plan" to JSONObject(mapOf( + "branch" to null, + "source" to null, + "version" to null, + "versionId" to null, + )), + "ingestion_metadata" to JSONObject(mapOf( + "sourceName" to null, + "sourceVersion" to null, + )), + "revenue" to null, + "price" to null, + "quantity" to null, + "product_id" to null, + "revenue_type" to null, + "extra" to null, + "partner_id" to null, + "attempts" to 0 + ) + + every { binding.applicationContext } returns context + + plugin.onAttachedToEngine(binding) + } + + @Test + fun shouldInit() { + val methodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("init called..") } + } + + @Test + fun shouldTrack() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + val methodCall = MethodCall("track", JSONObject(testEventMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("track called..") } + } + + @Test + fun shouldIdentify() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + testEventMap["event_type"] = "\$identify" + testEventMap["user_properties"] = mapOf( + "\$set" to mapOf("testProperty" to "testValue") + ) + val methodCall = MethodCall("identify", JSONObject(testEventMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("identify called..") } + } + + @Test + fun shouldGroupIdentify() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + testEventMap["event_type"] = "\$groupidentify" + testEventMap["groups"] = mapOf( + "testGroupType" to "testGroupName" + ) + testEventMap["group_properties"] = mapOf( + "\$set" to mapOf("testProperty" to "testValue") + ) + val methodCall = MethodCall("groupIdentify", JSONObject(testEventMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("groupIdentify called..") } + } + + @Test + fun shouldSetGroup() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + testEventMap["event_type"] = "\$identify" + testEventMap["groups"] = mapOf( + "testGroupType" to "testGroupName" + ) + testEventMap["user_properties"] = mapOf( + "\$set" to mapOf("testProperty" to "testValue") + ) + val methodCall = MethodCall("setGroup", JSONObject(testEventMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("setGroup called..") } + } + + @Test + fun shouldRevenue() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + testEventMap["event_type"] = "revenue_amount" + testEventMap["groups"] = mapOf( + "testGroupType" to "testGroupName" + ) + testEventMap["event_properties"] = mapOf( + "\$price" to "testPrice", + "\$quantity" to "testQuantity", + "\$productId" to "testProductId" + ) + val methodCall = MethodCall("revenue", JSONObject(testEventMap)) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("revenue called..") } + } + + @Test + fun shouldSetUserId() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + val methodCall = MethodCall("setUserId", JSONObject(mapOf( + "setUserId" to "testUserId" + ))) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("setUserId called..") } + } + + @Test + fun shouldSetDeviceId() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + val methodCall = MethodCall("setDeviceId", JSONObject(mapOf( + "setDeviceId" to "testDeviceId" + ))) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("setDeviceId called..") } + } + + @Test + fun shouldReset() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + val methodCall = MethodCall("reset", null) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("reset called..") } + } + + @Test + fun shouldFlush() { + val initMethodCall = MethodCall("init", JSONObject(testConfigurationMap)) + plugin.onMethodCall(initMethodCall, result) + + val methodCall = MethodCall("flush", null) + plugin.onMethodCall(methodCall, result) + + verify(exactly = 1) { result.success("flush called..") } + } + +} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7c31022..a2144e9 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -21,12 +21,13 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +apply plugin: 'idea' apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +41,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.amplitude_flutter_example" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -61,7 +62,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - testImplementation 'junit:junit:4.12' + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 555f3ff..e3b2b67 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -21,7 +21,8 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:exported="true"> diff --git a/example/android/build.gradle b/example/android/build.gradle index a806dd9..80fd514 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.6.21' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:8.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bf271a4..fc4b4bc 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.2-bin.zip diff --git a/example/pubspec.lock b/example/pubspec.lock index 46bfb77..2ee6e2b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -12,10 +12,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.10.0" boolean_selector: dependency: transitive description: @@ -28,10 +28,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.1" clock: dependency: transitive description: @@ -44,10 +44,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.17.0" cupertino_icons: dependency: "direct main" description: @@ -83,42 +83,42 @@ packages: dependency: transitive description: name: js - sha256: a5e201311cb08bf3912ebbe9a2be096e182d703f881136ec1e81a2338a9e120d + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.8.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -128,10 +128,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -168,10 +168,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.4.16" vector_math: dependency: transitive description: @@ -180,14 +180,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=2.0.0" diff --git a/lib/amplitude.dart b/lib/amplitude.dart index 3c87892..aad65da 100644 --- a/lib/amplitude.dart +++ b/lib/amplitude.dart @@ -147,7 +147,7 @@ class Amplitude extends _Amplitude { event.mergeEventOptions(options); } - return await _channel.invokeMethod("track", jsonEncode(event.toMap())); + return await _channel.invokeMethod("setGroup", jsonEncode(event.toMap())); } /// Tracks revenue generated by a user. diff --git a/lib/events/identify.dart b/lib/events/identify.dart index 70483b3..43905cc 100644 --- a/lib/events/identify.dart +++ b/lib/events/identify.dart @@ -1,5 +1,3 @@ -import 'package:logging/logging.dart'; - class IdentifyOperation { final String operationType; @@ -20,7 +18,6 @@ class IdentifyOperation { class Identify { Set propertySet = {}; Map properties = {}; - final log = Logger('Amplitude-Identify'); Identify set({required String property, required dynamic value}) { // TODO(xinyi): data type check @@ -77,19 +74,23 @@ class Identify { void _setUserProperty(IdentifyOperation operation, String property, dynamic value) { if (property.isEmpty) { - log.warning("Attempting to perform operation ${operation.operationType} with a null or empty string property, ignoring"); + // TODO(xinyi): add logs + // log.warning("Attempting to perform operation ${operation.operationType} with a null or empty string property, ignoring"); return; } if (value == null) { - log.warning("Attempting to perform operation ${operation.operationType} with null value for property $property, ignoring"); + // TODO(xinyi): add logs + // log.warning("Attempting to perform operation ${operation.operationType} with null value for property $property, ignoring"); return; } if (properties.containsKey(IdentifyOperation.clearAll.operationType)) { - log.warning("This Identify already contains a \$clearAll operation, ignoring operation ${operation.operationType}"); + // TODO(xinyi): add logs + // log.warning("This Identify already contains a \$clearAll operation, ignoring operation ${operation.operationType}"); return; } if (propertySet.contains(property)) { - log.warning("Already used property $property in previous operation, ignoring operation ${operation.operationType}"); + // TODO(xinyi): add logs + // log.warning("Already used property $property in previous operation, ignoring operation ${operation.operationType}"); return; } if (!properties.containsKey(operation.operationType)) { diff --git a/test/amplitude_test.dart b/test/amplitude_test.dart index e5cd603..edb099f 100644 --- a/test/amplitude_test.dart +++ b/test/amplitude_test.dart @@ -289,7 +289,7 @@ void main() { "\$set": {testGroupType: testGroupName} }; - verify(mockChannel.invokeMethod("track", jsonEncode(testIdentifyMap))) + verify(mockChannel.invokeMethod("setGroup", jsonEncode(testIdentifyMap))) .called(1); }); @@ -309,7 +309,7 @@ void main() { }; testIdentifyMap["user_id"] = testUserId; - verify(mockChannel.invokeMethod("track", jsonEncode(testIdentifyMap))) + verify(mockChannel.invokeMethod("setGroup", jsonEncode(testIdentifyMap))) .called(1); });