diff --git a/CHANGELOG.md b/CHANGELOG.md index 749a3f04..68ae979a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Remove need for context in Sentry.init for Android ([#117](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/117)) + ## 0.2.1 ### Fixes diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 26d9dbe2..79c228a9 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -53,6 +53,7 @@ object Config { val roboelectric = "org.robolectric:robolectric:4.9" val junitKtx = "androidx.test.ext:junit-ktx:1.1.5" + val mockitoCore = "org.mockito:mockito-core:5.4.0" } object Android { diff --git a/sentry-kotlin-multiplatform/build.gradle.kts b/sentry-kotlin-multiplatform/build.gradle.kts index fbc52077..369ee4c9 100644 --- a/sentry-kotlin-multiplatform/build.gradle.kts +++ b/sentry-kotlin-multiplatform/build.gradle.kts @@ -67,6 +67,7 @@ kotlin { dependencies { implementation(Config.TestLibs.roboelectric) implementation(Config.TestLibs.junitKtx) + implementation(Config.TestLibs.mockitoCore) } } val jvmMain by getting diff --git a/sentry-kotlin-multiplatform/src/androidMain/AndroidManifest.xml b/sentry-kotlin-multiplatform/src/androidMain/AndroidManifest.xml index 8c41501c..f0aaedea 100644 --- a/sentry-kotlin-multiplatform/src/androidMain/AndroidManifest.xml +++ b/sentry-kotlin-multiplatform/src/androidMain/AndroidManifest.xml @@ -8,7 +8,14 @@ android:name="io.sentry.android.core.SentryInitProvider" android:authorities="${applicationId}.SentryInitProvider" android:exported="false" - tools:node="remove"/> + tools:node="remove"> + + + diff --git a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt index 8777b6eb..da017c2c 100644 --- a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt +++ b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt @@ -1,15 +1,72 @@ package io.sentry.kotlin.multiplatform +import android.content.ContentProvider +import android.content.ContentValues import android.content.Context +import android.database.Cursor +import android.net.Uri import io.sentry.android.core.SentryAndroid import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback -internal actual fun initSentry(context: Context?, configuration: OptionsConfiguration) { +internal actual fun initSentry(configuration: OptionsConfiguration) { val options = SentryOptions() configuration.invoke(options) - context?.let { - SentryAndroid.init(it, options.toAndroidSentryOptionsCallback()) - } + SentryAndroid.init(applicationContext, options.toAndroidSentryOptionsCallback()) } +internal lateinit var applicationContext: Context + private set + public actual typealias Context = Context + +/** + * A ContentProvider that does NOT store or provide any data for read or write operations. + * + * It's only purpose is to retrieve and store the application context in an internal top-level + * variable [applicationContext]. The context is used for [SentryAndroid.init]. + * + * This does not allow for overriding the abstract query, insert, update, and delete operations + * of the [ContentProvider]. + */ +internal class SentryContextProvider : ContentProvider() { + override fun onCreate(): Boolean { + val context = context + if (context != null) { + applicationContext = context.applicationContext + } else { + error("Context cannot be null") + } + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + error("Not allowed.") + } + + override fun getType(uri: Uri): String? { + error("Not allowed.") + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + error("Not allowed.") + } + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + error("Not allowed.") + } + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { + error("Not allowed.") + } +} diff --git a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index 47356110..87a14629 100644 --- a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -1,15 +1,22 @@ package io.sentry.kotlin.multiplatform import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry import org.junit.runner.RunWith +import org.robolectric.Robolectric +import kotlin.test.BeforeTest @RunWith(AndroidJUnit4::class) actual abstract class BaseSentryTest { actual val platform: String = "Android" actual val authToken: String? = System.getenv("SENTRY_AUTH_TOKEN") actual fun sentryInit(optionsConfiguration: OptionsConfiguration) { - val context = InstrumentationRegistry.getInstrumentation().targetContext - Sentry.init(context, optionsConfiguration) + Sentry.init(optionsConfiguration) + } + + @BeforeTest + open fun setUp() { + // Set up the provider needed for Sentry.init on Android + val provider = Robolectric.buildContentProvider(SentryContextProvider::class.java) + provider.create() } } diff --git a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/SentryContextProviderTest.kt b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/SentryContextProviderTest.kt new file mode 100644 index 00000000..86f53329 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/SentryContextProviderTest.kt @@ -0,0 +1,59 @@ +package io.sentry.kotlin.multiplatform + +import android.content.ContentValues +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@RunWith(AndroidJUnit4::class) +class SentryContextProviderTest : BaseSentryTest() { + private lateinit var provider: SentryContextProvider + + @Before + override fun setUp() { + provider = SentryContextProvider() + } + + // We create a nested class so this test is executed with the BeforeEach method that initializes + // the actual content provider and not just a mock. + class SentryContextOnCreateTest : BaseSentryTest() { + @Test + fun `onCreate initializes applicationContext`() { + // Simple call to the lateinit applicationContext to make sure it's initialized + applicationContext + } + } + + fun `create does not throw Exception`() { + provider.onCreate() + } + + @Test(expected = IllegalStateException::class) + fun `insert throws Exception`() { + val uri = mock(Uri::class.java) + val values = ContentValues() + provider.insert(uri, values) + } + + @Test(expected = IllegalStateException::class) + fun `update throws Exception`() { + val uri = mock(Uri::class.java) + val values = ContentValues() + provider.update(uri, values, null, null) + } + + @Test(expected = IllegalStateException::class) + fun `delete throws Exception`() { + val uri = mock(Uri::class.java) + provider.delete(uri, null, null) + } + + @Test(expected = IllegalStateException::class) + fun `getType throws Exception`() { + val uri = mock(Uri::class.java) + provider.getType(uri) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt index e26dde05..585ae762 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt @@ -9,12 +9,12 @@ import io.sentry.kotlin.multiplatform.protocol.SentryId import io.sentry.kotlin.multiplatform.protocol.User import io.sentry.kotlin.multiplatform.protocol.UserFeedback -internal expect fun initSentry(context: Context? = null, configuration: OptionsConfiguration) +internal expect fun initSentry(configuration: OptionsConfiguration) internal actual object SentryBridge { actual fun init(context: Context, configuration: OptionsConfiguration) { - initSentry(context, configuration) + initSentry(configuration) } actual fun init(configuration: OptionsConfiguration) { diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt index 549ed698..56f6f39e 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt @@ -11,6 +11,7 @@ public typealias ScopeCallback = (Scope) -> Unit public typealias OptionsConfiguration = (SentryOptions) -> Unit /** The context used for Android initialization. */ +@Deprecated("No longer necessary to initialize Sentry on Android.") public expect abstract class Context /** Sentry Kotlin Multiplatform SDK API entry point. */ @@ -24,6 +25,10 @@ public object Sentry { */ @OptIn(ExperimentalObjCRefinement::class) @HiddenFromObjC + @Deprecated( + "Use init(OptionsConfiguration) instead.", + ReplaceWith("Sentry.init(configuration)") + ) public fun init(context: Context, configuration: OptionsConfiguration) { SentryBridge.init(context, configuration) } diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt index 836d200e..547712f1 100644 --- a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt +++ b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt @@ -3,7 +3,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.Sentry import io.sentry.kotlin.multiplatform.extensions.toJvmSentryOptionsCallback -internal actual fun initSentry(context: Context?, configuration: OptionsConfiguration) { +internal actual fun initSentry(configuration: OptionsConfiguration) { val options = SentryOptions() configuration.invoke(options) Sentry.init(options.toJvmSentryOptionsCallback()) diff --git a/sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt index 1b693381..388efb17 100644 --- a/sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt +++ b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt @@ -42,7 +42,7 @@ class SentryApplication : Application() { super.onCreate() // Initialize Sentry using shared code - initializeSentry(this) + initializeSentry() // Shared scope across all platforms configureSentryScope() diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt index 331f1017..ffdd4af6 100644 --- a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt +++ b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt @@ -1,7 +1,6 @@ package sample.kmp.app import io.sentry.kotlin.multiplatform.Attachment -import io.sentry.kotlin.multiplatform.Context import io.sentry.kotlin.multiplatform.HttpStatusCodeRange import io.sentry.kotlin.multiplatform.OptionsConfiguration import io.sentry.kotlin.multiplatform.Sentry @@ -24,15 +23,6 @@ fun configureSentryScope() { * Initializes Sentry with given options. * Make sure to hook this into your native platforms as early as possible */ -fun initializeSentry(context: Context) { - Sentry.init(context, optionsConfiguration()) -} - -/** - * Convenience initializer for Cocoa targets. - * Kotlin -> ObjC doesn't support default parameters (yet). - * Otherwise, you would need to do this: AppSetupKt.initializeSentry(context: nil) in Swift. - */ fun initializeSentry() { Sentry.init(optionsConfiguration()) } diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt index 1d60baaa..05ad4a62 100644 --- a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt @@ -4,14 +4,14 @@ import android.app.Application import android.content.Context import org.koin.dsl.module import sentry.kmp.demo.initKoin -import sentry.kmp.demo.sentry.initSentry +import sentry.kmp.demo.sentry.initializeSentry class MainApp : Application() { override fun onCreate() { super.onCreate() - initSentry(this) + initializeSentry() initKoin( module { diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift index f863295a..cc0ba4c7 100644 --- a/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift @@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { startKoin() - SentrySetupKt.start() + SentrySetupKt.initializeSentry() let viewController = UIHostingController(rootView: HomeScreen()) diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt index 920aa3a2..60f1f6e1 100644 --- a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt @@ -1,11 +1,8 @@ package sentry.kmp.demo.sentry import io.sentry.kotlin.multiplatform.Attachment -import io.sentry.kotlin.multiplatform.Context import io.sentry.kotlin.multiplatform.OptionsConfiguration import io.sentry.kotlin.multiplatform.Sentry -import kotlin.experimental.ExperimentalObjCRefinement -import kotlin.native.HiddenFromObjC /** Shared options configuration */ private val optionsConfiguration: OptionsConfiguration = { @@ -31,18 +28,7 @@ private val optionsConfiguration: OptionsConfiguration = { * Initializes Sentry with given options. * Make sure to hook this into your native platforms as early as possible */ -@OptIn(ExperimentalObjCRefinement::class) -@HiddenFromObjC -fun initSentry(context: Context) { - Sentry.init(context, optionsConfiguration) - configureSentryScope() -} - -/** - * Convenience initializer for Cocoa targets. - * Kotlin -> ObjC doesn't support default parameters (yet). - */ -fun start() { +fun initializeSentry() { Sentry.init(optionsConfiguration) configureSentryScope() } diff --git a/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt b/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt index 1b693381..388efb17 100644 --- a/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt +++ b/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt @@ -42,7 +42,7 @@ class SentryApplication : Application() { super.onCreate() // Initialize Sentry using shared code - initializeSentry(this) + initializeSentry() // Shared scope across all platforms configureSentryScope() diff --git a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt index c094d11d..be7e6e27 100644 --- a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt +++ b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt @@ -1,7 +1,6 @@ package sample.kmp.app import io.sentry.kotlin.multiplatform.Attachment -import io.sentry.kotlin.multiplatform.Context import io.sentry.kotlin.multiplatform.HttpStatusCodeRange import io.sentry.kotlin.multiplatform.OptionsConfiguration import io.sentry.kotlin.multiplatform.Sentry @@ -24,15 +23,6 @@ fun configureSentryScope() { * Initializes Sentry with given options. * Make sure to hook this into your native platforms as early as possible */ -fun initializeSentry(context: Context) { - Sentry.init(context, optionsConfiguration()) -} - -/** - * Convenience initializer for Cocoa targets. - * Kotlin -> ObjC doesn't support default parameters (yet). - * Otherwise, you would need to do this: AppSetupKt.initializeSentry(context: nil) in Swift. - */ fun initializeSentry() { Sentry.init(optionsConfiguration()) }