Skip to content

Remove need for context in Sentry.init for Android #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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