diff --git a/changes-for-exceptions-2.diff b/changes-for-exceptions-2.diff new file mode 100644 index 00000000..615bbd19 --- /dev/null +++ b/changes-for-exceptions-2.diff @@ -0,0 +1,93 @@ +diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt +index 3c6ddbc..cf70141 100644 +--- a/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt ++++ b/core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt +@@ -8,6 +8,7 @@ import com.segment.analytics.kotlin.core.platform.plugins.ContextPlugin + import com.segment.analytics.kotlin.core.platform.plugins.SegmentDestination + import com.segment.analytics.kotlin.core.platform.plugins.StartupQueue + import com.segment.analytics.kotlin.core.platform.plugins.UserInfoPlugin ++import com.segment.analytics.kotlin.core.platform.plugins.logger.LogFilterKind + import com.segment.analytics.kotlin.core.platform.plugins.logger.SegmentLog + import com.segment.analytics.kotlin.core.platform.plugins.logger.log + import kotlinx.coroutines.* +@@ -83,7 +84,10 @@ open class Analytics protected constructor( + constructor(configuration: Configuration) : this(configuration, + object : CoroutineConfiguration { + override val store = Store() +- override val analyticsScope = CoroutineScope(SupervisorJob()) ++ val exceptionHandler = ++ CoroutineExceptionHandler { coroutineContext, throwable -> ++ println("From: $coroutineContext caught: $throwable") } ++ override val analyticsScope = CoroutineScope(SupervisorJob()+exceptionHandler) + override val analyticsDispatcher : CloseableCoroutineDispatcher = + Executors.newCachedThreadPool().asCoroutineDispatcher() + override val networkIODispatcher : CloseableCoroutineDispatcher = +@@ -95,28 +99,32 @@ open class Analytics protected constructor( + // This function provides a default state to the store & attaches the storage and store instances + // Initiates the initial call to settings and adds default system plugins + internal fun build() { +- // because startup queue doesn't depend on a state, we can add it first +- add(SegmentLog()) +- add(StartupQueue()) +- add(ContextPlugin()) +- add(UserInfoPlugin()) +- +- // Setup store +- analyticsScope.launch(analyticsDispatcher) { +- store.also { +- // load memory with initial value +- it.provide(userInfo) +- it.provide(System.defaultState(configuration, storage)) ++ try { ++ // because startup queue doesn't depend on a state, we can add it first ++ add(SegmentLog()) ++ add(StartupQueue()) ++ add(ContextPlugin()) ++ add(UserInfoPlugin()) ++ ++ // Setup store ++ analyticsScope.launch(analyticsDispatcher) { ++ store.also { ++ // load memory with initial value ++ it.provide(userInfo) ++ it.provide(System.defaultState(configuration, storage)) ++ ++ // subscribe to store after state is provided ++ storage.subscribeToStore() ++ } + +- // subscribe to store after state is provided +- storage.subscribeToStore() +- } ++ if (configuration.autoAddSegmentDestination) { ++ add(SegmentDestination()) ++ } + +- if (configuration.autoAddSegmentDestination) { +- add(SegmentDestination()) ++ checkSettings() + } +- +- checkSettings() ++ } catch (t: Throwable) { ++ log("Error calling build(): $t") + } + } + +diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/platform/Mediator.kt b/core/src/main/java/com/segment/analytics/kotlin/core/platform/Mediator.kt +index c3a599c..55bfecf 100644 +--- a/core/src/main/java/com/segment/analytics/kotlin/core/platform/Mediator.kt ++++ b/core/src/main/java/com/segment/analytics/kotlin/core/platform/Mediator.kt +@@ -51,7 +51,11 @@ internal class Mediator(internal var plugins: CopyOnWriteArrayList = Cop + + fun applyClosure(closure: (Plugin) -> Unit) { + plugins.forEach { +- closure(it) ++ try { ++ closure(it) ++ } catch (t : Throwable) { ++ Analytics.segmentLog("Caught Exception while applying closure to plugin: $it: $t") ++ } + } + } + diff --git a/copy-aar-to-migration-sample.sh b/copy-aar-to-migration-sample.sh new file mode 100755 index 00000000..6def2b77 --- /dev/null +++ b/copy-aar-to-migration-sample.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +cp android/build/outputs/aar/android-debug.aar ../sample-java-to-kotlin-migration/app/libs/analytics-kotlin.aar && echo "Copy successful!" diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Errors.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Errors.kt index bf538464..ef2348c6 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Errors.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Errors.kt @@ -1,6 +1,26 @@ package com.segment.analytics.kotlin.core import com.segment.analytics.kotlin.core.platform.plugins.logger.segmentLog +sealed class AnalyticsError(): Throwable() { + data class StorageUnableToCreate(override val message: String?): AnalyticsError() + data class StorageUnableToWrite(override val message: String?): AnalyticsError() + data class StorageUnableToRename(override val message: String?): AnalyticsError() + data class StorageUnableToOpen(override val message: String?): AnalyticsError() + data class StorageUnableToClose(override val message: String?): AnalyticsError() + data class StorageInvalid(override val message: String?): AnalyticsError() + data class StorageUnknown(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class NetworkUnexpectedHTTPCode(override val message: String?): AnalyticsError() + data class NetworkServerLimited(override val message: String?): AnalyticsError() + data class NetworkServerRejected(override val message: String?): AnalyticsError() + data class NetworkUnknown(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class NetworkInvalidData(override val message: String?): AnalyticsError() + data class JsonUnableToSerialize(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class JsonUnableToDeserialize(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class JsonUnknown(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class PluginError(override val message: String?, override val cause: Throwable?): AnalyticsError() + data class EnrichmentError(override val message: String?): AnalyticsError() + data class SettingsFetchError(override val message: String?, override val cause: Throwable?): AnalyticsError() +} /** * Reports an internal error to the user-defined error handler. diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt index d4db087c..97799fb4 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt @@ -115,7 +115,7 @@ internal fun Analytics.fetchSettings( log("Fetched Settings: $settingsString") LenientJson.decodeFromString(settingsString) } catch (ex: Exception) { - reportErrorWithMetrics(this, ex, "Failed to fetch settings", + reportErrorWithMetrics(this, AnalyticsError.SettingsFetchError(ex.message, ex), "Failed to fetch settings", Telemetry.INVOKE_ERROR_METRIC, ex.stackTraceToString()) { it["error"] = ex.toString() it["writekey"] = writeKey diff --git a/samples/kotlin-android-api21-app/.gitignore b/samples/kotlin-android-api21-app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/samples/kotlin-android-api21-app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/build.gradle b/samples/kotlin-android-api21-app/build.gradle new file mode 100644 index 00000000..14af88be --- /dev/null +++ b/samples/kotlin-android-api21-app/build.gradle @@ -0,0 +1,52 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' +} + +android { + namespace 'com.segment.analytics.next.kotlin_android_api21_app' + compileSdk 33 + + defaultConfig { + applicationId "com.segment.analytics.next.kotlin_android_api21_app" + minSdk 21 + //noinspection ExpiredTargetSdkVersion + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation project(':android') + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/proguard-rules.pro b/samples/kotlin-android-api21-app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/samples/kotlin-android-api21-app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/androidTest/java/com/segment/analytics/next/kotlin_android_api21_app/ExampleInstrumentedTest.kt b/samples/kotlin-android-api21-app/src/androidTest/java/com/segment/analytics/next/kotlin_android_api21_app/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..3dfb2478 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/androidTest/java/com/segment/analytics/next/kotlin_android_api21_app/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.segment.analytics.next.kotlin_android_api21_app + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.segment.analytics.next.kotlin_android_api21_app", appContext.packageName) + } +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/AndroidManifest.xml b/samples/kotlin-android-api21-app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c4359594 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/FirstFragment.kt b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/FirstFragment.kt new file mode 100644 index 00000000..196c0728 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/FirstFragment.kt @@ -0,0 +1,51 @@ +package com.segment.analytics.next.kotlin_android_api21_app + +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import com.segment.analytics.next.kotlin_android_api21_app.databinding.FragmentFirstBinding + +/** + * A simple [Fragment] subclass as the default destination in the navigation. + */ +class FirstFragment : Fragment() { + + private val TAG = SampleApplication.TAG + "/FirstFragment" + private var _binding: FragmentFirstBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + _binding = FragmentFirstBinding.inflate(inflater, container, false) + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.buttonFirst.setOnClickListener { + findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) + } + + binding.buttonTrack.setOnClickListener { + Log.d(TAG, "onViewCreated: trying to track") + SampleApplication.analytics.track("test-track") + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/MainActivity.kt b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/MainActivity.kt new file mode 100644 index 00000000..5ef8eabc --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/MainActivity.kt @@ -0,0 +1,58 @@ +package com.segment.analytics.next.kotlin_android_api21_app + +import android.os.Bundle +import com.google.android.material.snackbar.Snackbar +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import android.view.Menu +import android.view.MenuItem +import com.segment.analytics.next.kotlin_android_api21_app.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + val navController = findNavController(R.id.nav_host_fragment_content_main) + appBarConfiguration = AppBarConfiguration(navController.graph) + setupActionBarWithNavController(navController, appBarConfiguration) + + binding.fab.setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + return when (item.itemId) { + R.id.action_settings -> true + else -> super.onOptionsItemSelected(item) + } + } + + override fun onSupportNavigateUp(): Boolean { + val navController = findNavController(R.id.nav_host_fragment_content_main) + return navController.navigateUp(appBarConfiguration) + || super.onSupportNavigateUp() + } +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SampleApplication.kt b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SampleApplication.kt new file mode 100644 index 00000000..727c9b6e --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SampleApplication.kt @@ -0,0 +1,38 @@ +package com.segment.analytics.next.kotlin_android_api21_app + +import android.app.Application +import android.util.Log +import com.segment.analytics.kotlin.core.Analytics +import com.segment.analytics.kotlin.core.platform.policies.CountBasedFlushPolicy +import com.segment.analytics.kotlin.core.platform.policies.FrequencyFlushPolicy + +class SampleApplication: Application() { + + companion object { + val TAG = "KotlinApi21Sample" + lateinit var analytics: Analytics + } + + override fun onCreate() { + super.onCreate() + + Log.d(TAG, "onCreate: Running....") + + analytics = com.segment.analytics.kotlin.android.Analytics( + "ETcIuuIISx43DvIO1FudJ3qHNHhxkX04", + applicationContext + ) { + this.collectDeviceId = true + this.trackApplicationLifecycleEvents = true + this.trackDeepLinks = true + this.flushPolicies = listOf( + CountBasedFlushPolicy(1), // Flush after 3 events +// FrequencyFlushPolicy(5000), // Flush after 5 secs + ) + } + + + + Log.d(TAG, "onCreate: Analytics configured") + } +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SecondFragment.kt b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SecondFragment.kt new file mode 100644 index 00000000..6cbd2d76 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/java/com/segment/analytics/next/kotlin_android_api21_app/SecondFragment.kt @@ -0,0 +1,44 @@ +package com.segment.analytics.next.kotlin_android_api21_app + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import com.segment.analytics.next.kotlin_android_api21_app.databinding.FragmentSecondBinding + +/** + * A simple [Fragment] subclass as the second destination in the navigation. + */ +class SecondFragment : Fragment() { + + private var _binding: FragmentSecondBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + _binding = FragmentSecondBinding.inflate(inflater, container, false) + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.buttonSecond.setOnClickListener { + findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samples/kotlin-android-api21-app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/res/drawable/ic_launcher_background.xml b/samples/kotlin-android-api21-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/kotlin-android-api21-app/src/main/res/layout/activity_main.xml b/samples/kotlin-android-api21-app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..5a0fb17d --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/res/layout/content_main.xml b/samples/kotlin-android-api21-app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..e416e1c1 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/res/layout/content_main.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/samples/kotlin-android-api21-app/src/main/res/layout/fragment_first.xml b/samples/kotlin-android-api21-app/src/main/res/layout/fragment_first.xml new file mode 100644 index 00000000..ecb5d441 --- /dev/null +++ b/samples/kotlin-android-api21-app/src/main/res/layout/fragment_first.xml @@ -0,0 +1,39 @@ + + + + + + +