Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions sentry-kotlin-multiplatform/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ kotlin {
dependencies {
implementation(Config.TestLibs.roboelectric)
implementation(Config.TestLibs.junitKtx)
implementation(Config.TestLibs.mockitoCore)
}
}
val jvmMain by getting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
android:name="io.sentry.android.core.SentryInitProvider"
android:authorities="${applicationId}.SentryInitProvider"
android:exported="false"
tools:node="remove"/>
tools:node="remove">
</provider>
<provider
android:name="io.sentry.kotlin.multiplatform.SentryContextProvider"
android:authorities="${applicationId}.SentryContextProvider"
android:exported="false"
tools:node="merge">
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
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<out String>?): Int {
error("Not allowed.")
}

override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
error("Not allowed.")
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class SentryApplication : Application() {
super.onCreate()

// Initialize Sentry using shared code
initializeSentry(this)
initializeSentry()

// Shared scope across all platforms
configureSentryScope()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

startKoin()

SentrySetupKt.start()
SentrySetupKt.initializeSentry()

let viewController = UIHostingController(rootView: HomeScreen())

Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class SentryApplication : Application() {
super.onCreate()

// Initialize Sentry using shared code
initializeSentry(this)
initializeSentry()

// Shared scope across all platforms
configureSentryScope()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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())
}
Expand Down