Skip to content
Draft
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- `ClientReportRecorder` now reads the item count from the envelope item header instead of deserializing the payload, which under sustained rate limiting could pin CPU cores while repeatedly throwing exceptions
- Report tasks handed to a no-op `ISentryExecutorService` as cancelled ([#5874](https://github.com/getsentry/sentry-java/pull/5874))
- `NoOpSentryExecutorService` previously returned a `Future` that was never run and never cancelled, so callers could not tell a dropped task from a queued one and `get()` would block until its timeout
- Fix `NoSuchMethodError` when using `SentryTraced` or `SentryUserFeedbackButton` with Jetpack Compose older than 1.8 ([#5887](https://github.com/getsentry/sentry-java/pull/5887))
- Since `8.32.0`, `sentry-compose` was compiled against a newer `androidx.compose.material3`, whose transitive Compose versions were inlined into the SDK by composables like `Box`. This made the SDK reference Compose internals (`Composer.shouldExecute`, `BoxKt.maybeCachedBoxMeasurePolicy`) that do not exist on older Compose versions.

### Performance

Expand Down
14 changes: 14 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ androidxLifecycle = "2.2.0"
androidxNavigation = "2.4.2"
androidxTestCore = "1.7.0"
androidxCompose = "1.6.3"
# Oldest material3 we support. Kept in lockstep with androidxCompose: material3 drags its own
# transitive Compose versions onto the compile classpath, so a newer value here raises the effective
# Compose floor of sentry-compose's published bytecode.
androidxComposeMaterial3Floor = "1.2.1"
asyncProfiler = "4.4"
camerax = "1.4.0"
composeCompiler = "1.5.14"
Expand Down Expand Up @@ -89,6 +93,12 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidxCompose" }
androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "androidxCompose" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3", version = "1.4.0" }
# Compiling against material3 pulls its transitive compose-runtime/foundation onto the classpath, and
# inline composables like Box bake those internals straight into our bytecode. Keep this at the oldest
# material3 we support so sentry-compose stays loadable on androidxCompose above.
# Note: don't change without testing backwards compatibility.
# :sentry-android-integration-tests:compose-floor-integration-tests guards this.
androidx-compose-material3-floor = { module = "androidx.compose.material3:material3", version.ref = "androidxComposeMaterial3Floor" }
androidx-compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version="1.7.8" }
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version="1.7.8" }
androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidxCompose" }
Expand Down Expand Up @@ -240,6 +250,10 @@ tomcat-embed-jasper-jakarta = { module = "org.apache.tomcat.embed:tomcat-embed-j
# test libraries
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version = "1.4.1" }
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version = "1.9.5" }
# Floor-matched test harness for :sentry-android-integration-tests:compose-floor-integration-tests.
# Must track androidxCompose, otherwise the test runtime pulls a newer Compose and the check silently
# passes.
androidx-compose-ui-test-junit4-floor = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidxCompose" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestCore" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.detekt)
}

android {
compileSdk = libs.versions.compileSdk.get().toInt()
namespace = "io.sentry.compose.floortest"

defaultConfig { minSdk = libs.versions.minSdk.get().toInt() }

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

// AGP 9 only generates unit tests for the testBuildType. The debug variant is
// disabled, so unit tests must target release to run at all.
testBuildType = "release"

kotlin {
compilerOptions.jvmTarget = JvmTarget.JVM_1_8
compilerOptions.languageVersion = KotlinVersion.KOTLIN_1_9
compilerOptions.apiVersion = KotlinVersion.KOTLIN_1_9
}

testOptions {
animationsDisabled = true
unitTests.apply {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
}

lint {
warningsAsErrors = true
checkDependencies = true
checkReleaseBuilds = false
}

androidComponents.beforeVariants {
it.enable = !Config.Android.shouldSkipDebugVariant(it.buildType)
}
}

// This module exists purely to run sentry-compose against the oldest Compose we claim to support.
// Compose artifacts are inlined into consumer bytecode, so building sentry-compose against a newer
// Compose can emit references to internals that do not exist on the floor, which only surfaces as a
// NoSuchMethodError at runtime on a consumer's older Compose. Pinning here makes that a test
// failure.
// The pins must stay at the floor: raising them silently disables the check this module provides.
configurations.configureEach {
resolutionStrategy {
val floor = libs.versions.androidxCompose.get()
eachDependency {
when (requested.group) {
"androidx.compose.material3" ->
useVersion(libs.versions.androidxComposeMaterial3Floor.get())
"androidx.compose.runtime",
"androidx.compose.foundation",
"androidx.compose.ui",
"androidx.compose.animation" -> useVersion(floor)
}
}
}
}

dependencies {
implementation(projects.sentryCompose)

testImplementation(libs.androidx.compose.material3.floor)
testImplementation(libs.androidx.compose.ui.test.junit4.floor)
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.roboelectric)
}

tasks.withType<Detekt>().configureEach { jvmTarget = JavaVersion.VERSION_1_8.toString() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.sentry.compose.floortest

import android.app.Application
import android.content.ComponentName
import androidx.activity.ComponentActivity
import androidx.compose.material3.Text
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.compose.SentryTraced
import io.sentry.compose.SentryUserFeedbackButton
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.junit.runner.RunWith
import org.robolectric.Shadows
import org.robolectric.annotation.Config

/**
* Composes the public entry points of sentry-compose against the oldest Compose version we support.
*
* Compose inlines composables such as `Box` into calling bytecode, so sentry-compose can end up
* referencing Compose internals from whatever version it was compiled against. Those references
* resolve fine in our own test suite, which runs on a recent Compose, but throw NoSuchMethodError
* on a consumer using an older one. Compose is pinned to the floor for this module so that mismatch
* fails here instead of in a user's app.
*/
@RunWith(AndroidJUnit4::class)
@Config(sdk = [30])
class ComposeFloorCompatibilityTest {
// workaround for robolectric tests with composeRule
// from https://github.com/robolectric/robolectric/pull/4736#issuecomment-1831034882
@get:Rule(order = 1)
val addActivityToRobolectricRule =
object : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
val appContext: Application = ApplicationProvider.getApplicationContext()
Shadows.shadowOf(appContext.packageManager)
.addActivityIfNotPresent(
ComponentName(appContext.packageName, ComponentActivity::class.java.name)
)
}
}

@get:Rule(order = 2) val rule = createAndroidComposeRule<ComponentActivity>()

@OptIn(ExperimentalComposeUiApi::class)
@Test
fun `SentryTraced composes on the oldest supported Compose`() {
rule.setContent { SentryTraced("floor-tag") { Text("content") } }

rule.onNodeWithText("content").assertExists()
}

@Suppress("DEPRECATION")
@Test
fun `SentryUserFeedbackButton composes on the oldest supported Compose`() {
rule.setContent { SentryUserFeedbackButton(text = "Report a Bug") }

rule.onNodeWithText("Report a Bug").assertExists()
}
}
2 changes: 1 addition & 1 deletion sentry-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ kotlin {
api(projects.sentry)
api(projects.sentryAndroidNavigation)

compileOnly(libs.androidx.compose.material3)
compileOnly(libs.androidx.compose.material3.floor)
compileOnly(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.common.java8)
}
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ include(
"sentry-samples:sentry-samples-spring-boot-4-otlp",
"sentry-samples:sentry-samples-spring-boot-4-webflux",
"sentry-samples:sentry-samples-netflix-dgs",
"sentry-android-integration-tests:compose-floor-integration-tests",
"sentry-android-integration-tests:sentry-uitest-android-critical",
"sentry-android-integration-tests:sentry-uitest-android-benchmark",
"sentry-android-integration-tests:sentry-uitest-android-macrobenchmark",
Expand Down
Loading