Skip to content

Commit 05aa61d

Browse files
authored
chore(samples-android): Adapt SQLite demo screen to SAGP build mode (#5568)
Exposes a `BuildConfig.USE_SAGP` property from the recently introduced -PuseSagp flag ([#5538](#5538)). Lets us update the SQLite screen in the Android sample app so that it swizzles between auto-instrumenting vs manually wrapping `SQLiteDriver`, depending on the whether SAGP was applied to the build.
1 parent 69943b8 commit 05aa61d

3 files changed

Lines changed: 112 additions & 56 deletions

File tree

sentry-samples/sentry-samples-android/build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import com.android.build.api.artifact.SingleArtifact
2+
import com.android.build.api.variant.BuildConfigField
23
import com.android.build.api.variant.impl.VariantImpl
34
import io.sentry.android.gradle.extensions.InstrumentationFeature
45
import io.sentry.android.gradle.extensions.SentryPluginExtension
@@ -135,6 +136,17 @@ android {
135136
}
136137

137138
androidComponents.onVariants { variant ->
139+
variant.buildConfigFields?.put(
140+
"USE_SAGP",
141+
providers.provider {
142+
BuildConfigField(
143+
type = "boolean",
144+
value = providers.gradleProperty("useSagp").isPresent.toString(),
145+
comment = "Whether the Sentry Android Gradle Plugin was applied",
146+
)
147+
},
148+
)
149+
138150
val taskName = "toggle${variant.name.capitalized()}NativeLogging"
139151
val toggleNativeLoggingTask =
140152
project.tasks.register<ToggleNativeLoggingTask>(taskName) {

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/sqlite/SQLiteActivity.kt

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding
2222
import androidx.compose.foundation.layout.size
2323
import androidx.compose.foundation.layout.statusBarsPadding
2424
import androidx.compose.foundation.rememberScrollState
25+
import androidx.compose.foundation.shape.RoundedCornerShape
2526
import androidx.compose.foundation.verticalScroll
2627
import androidx.compose.material.icons.Icons
2728
import androidx.compose.material.icons.outlined.HelpOutline
@@ -45,6 +46,7 @@ import androidx.compose.material3.Text
4546
import androidx.compose.material3.TooltipBox
4647
import androidx.compose.material3.TooltipDefaults
4748
import androidx.compose.material3.rememberTooltipState
49+
import androidx.compose.runtime.Composable
4850
import androidx.compose.runtime.LaunchedEffect
4951
import androidx.compose.runtime.getValue
5052
import androidx.compose.runtime.mutableStateOf
@@ -70,6 +72,7 @@ import io.sentry.SpanStatus
7072
import io.sentry.TransactionContext
7173
import io.sentry.TransactionOptions
7274
import io.sentry.protocol.SentryId
75+
import io.sentry.samples.android.BuildConfig
7376
import kotlinx.coroutines.Dispatchers
7477
import kotlinx.coroutines.delay
7578
import kotlinx.coroutines.launch
@@ -93,33 +96,45 @@ private val CONTROL_SECTION_GAP = TOGGLE_SECTION_GAP * 2
9396

9497
private val SECTION_HEADER_HEIGHT = 28.dp
9598

99+
private const val SAGP_DIRECT_DRIVER_MESSAGE =
100+
"SAGP doesn't auto-instrument SQLiteDriver for direct use"
101+
96102
/** Which sentry-android-sqlite integration the demo currently targets. */
97103
private enum class IntegrationMode(
98104
val color: Color,
99105
val segmentLabel: String,
100106
val apiName: String,
101-
val subtitle: String,
102107
) {
103-
DRIVER(
104-
SentryPurple,
105-
"SQLiteDriver",
106-
"SQLiteDriver",
107-
"SentrySQLiteDriver.create(BundledSQLiteDriver)",
108-
),
109-
OPEN_HELPER(
110-
SentryPink,
111-
"OpenHelper",
112-
"SupportSQLiteOpenHelper",
113-
"SentrySupportSQLiteOpenHelper.create(...)",
114-
),
108+
109+
DRIVER(SentryPurple, "SQLiteDriver", "SQLiteDriver"),
110+
OPEN_HELPER(SentryPink, "OpenHelper", "SupportSQLiteOpenHelper"),
115111
// Not directly-supported, but lets us verify behavior when both the DRIVER and OPEN_HELPER
116112
// integrations are used together via the SupportSQLiteDriver bridge.
117-
BRIDGE(
118-
SentryOrange,
119-
"Bridge",
120-
"SupportSQLiteDriver bridge",
121-
"SentrySQLiteDriver.create(SupportSQLiteDriver(Sentry helper))",
122-
),
113+
BRIDGE(SentryOrange, "Bridge", "SupportSQLiteDriver bridge");
114+
115+
fun subtitle(): String =
116+
when (this) {
117+
DRIVER ->
118+
if (BuildConfig.USE_SAGP) {
119+
"BundledSQLiteDriver (SAGP auto-wrap)"
120+
} else {
121+
"SentrySQLiteDriver.create(BundledSQLiteDriver)"
122+
}
123+
124+
OPEN_HELPER ->
125+
if (BuildConfig.USE_SAGP) {
126+
"FrameworkSQLiteOpenHelperFactory (SAGP auto-wrap)"
127+
} else {
128+
"SentrySupportSQLiteOpenHelper.create(...)"
129+
}
130+
131+
BRIDGE ->
132+
if (BuildConfig.USE_SAGP) {
133+
"SupportSQLiteDriver(open helper) (SAGP auto-wrap)"
134+
} else {
135+
"SentrySQLiteDriver.create(SupportSQLiteDriver(Sentry helper))"
136+
}
137+
}
123138
}
124139

125140
/**
@@ -318,6 +333,7 @@ class SQLiteActivity : ComponentActivity() {
318333
lerp(MaterialTheme.colorScheme.outline, integration.color, shimmer.value)
319334

320335
Text(text = "SQLite Instrumentation", style = MaterialTheme.typography.headlineSmall)
336+
SagpBuildPill()
321337

322338
Spacer(Modifier.height(titleGap))
323339

@@ -364,6 +380,7 @@ class SQLiteActivity : ComponentActivity() {
364380
label = row.label,
365381
color = integration.color,
366382
variant = variant,
383+
sagpDisabledReason = sagpDisabledReason(integration, row),
367384
disabledReason = "${row.label} doesn't support the ${integration.apiName} stack",
368385
)
369386
}
@@ -447,7 +464,7 @@ class SQLiteActivity : ComponentActivity() {
447464
}
448465

449466
@OptIn(ExperimentalMaterial3Api::class)
450-
@androidx.compose.runtime.Composable
467+
@Composable
451468
private fun IntegrationModeSelector(
452469
selected: IntegrationMode,
453470
onSelected: (IntegrationMode) -> Unit,
@@ -471,18 +488,36 @@ class SQLiteActivity : ComponentActivity() {
471488
}
472489

473490
Text(
474-
text = selected.subtitle,
491+
text = selected.subtitle(),
475492
style = MaterialTheme.typography.bodySmall,
476493
color = Color.Gray,
477494
modifier = Modifier.padding(top = 6.dp),
478495
)
479496
}
480497

498+
@Composable
499+
private fun SagpBuildPill() {
500+
val useSagp = BuildConfig.USE_SAGP
501+
502+
Surface(
503+
shape = RoundedCornerShape(percent = 50),
504+
color = if (useSagp) SentryPurple.copy(alpha = 0.15f) else Color.Gray.copy(alpha = 0.2f),
505+
modifier = Modifier.padding(top = 6.dp),
506+
) {
507+
Text(
508+
text = if (useSagp) "Built with SAGP" else "Built without SAGP",
509+
style = MaterialTheme.typography.labelSmall,
510+
color = if (useSagp) SentryPurple else Color.DarkGray,
511+
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
512+
)
513+
}
514+
}
515+
481516
/**
482517
* A compact, left-justified labeled switch. [labelColor] defaults to [Color.Unspecified] so the
483518
* label inherits the default text color.
484519
*/
485-
@androidx.compose.runtime.Composable
520+
@Composable
486521
private fun ToggleRow(
487522
label: String,
488523
checked: Boolean,
@@ -509,11 +544,11 @@ class SQLiteActivity : ComponentActivity() {
509544
}
510545
}
511546

512-
@androidx.compose.runtime.Composable
547+
@Composable
513548
private fun SectionHeader(
514549
title: String,
515550
topPadding: Dp = 8.dp,
516-
trailing: (@androidx.compose.runtime.Composable () -> Unit)? = null,
551+
trailing: (@Composable () -> Unit)? = null,
517552
) {
518553
Column(modifier = Modifier.fillMaxWidth().padding(top = topPadding)) {
519554
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -529,7 +564,7 @@ class SQLiteActivity : ComponentActivity() {
529564
* tooltip that auto-dismisses after a few seconds.
530565
*/
531566
@OptIn(ExperimentalMaterial3Api::class)
532-
@androidx.compose.runtime.Composable
567+
@Composable
533568
private fun HelpTooltip() {
534569
val tooltipState = rememberTooltipState(isPersistent = true)
535570
val scope = rememberCoroutineScope()
@@ -565,16 +600,19 @@ class SQLiteActivity : ComponentActivity() {
565600
* dimmed and, when clicked, explains why via a toast ([disabledReason]) instead of running.
566601
*/
567602
@OptIn(ExperimentalFoundationApi::class)
568-
@androidx.compose.runtime.Composable
603+
@Composable
569604
private fun DemoRowButton(
570605
label: String,
571606
color: Color,
572607
variant: DemoVariant?,
608+
sagpDisabledReason: String?,
573609
disabledReason: String,
574610
) {
575611
val context = LocalContext.current
576-
val enabled = variant != null
577-
val explain = { Toast.makeText(context, disabledReason, Toast.LENGTH_SHORT).show() }
612+
val enabled = variant != null && sagpDisabledReason == null
613+
val explain = {
614+
Toast.makeText(context, sagpDisabledReason ?: disabledReason, Toast.LENGTH_SHORT).show()
615+
}
578616

579617
Surface(
580618
modifier = Modifier.fillMaxWidth(),
@@ -585,8 +623,8 @@ class SQLiteActivity : ComponentActivity() {
585623
Box(
586624
modifier =
587625
Modifier.combinedClickable(
588-
onClick = { if (variant != null) onTap(variant) else explain() },
589-
onLongClick = { if (variant != null) onLongPress(variant) else explain() },
626+
onClick = { if (enabled) onTap(variant) else explain() },
627+
onLongClick = { if (enabled) onLongPress(variant) else explain() },
590628
)
591629
.fillMaxWidth()
592630
.heightIn(min = 44.dp)
@@ -598,7 +636,7 @@ class SQLiteActivity : ComponentActivity() {
598636
}
599637
}
600638

601-
@androidx.compose.runtime.Composable
639+
@Composable
602640
private fun ResetButton(dbOperationInFlight: Boolean, resetInProgress: Boolean) {
603641
// Debounce demo-driven disablement so fast taps don't flicker the button; reset disables
604642
// immediately via [resetInProgress]. [dbOperationInFlight] still guards [onClick] either way.
@@ -642,7 +680,7 @@ class SQLiteActivity : ComponentActivity() {
642680
}
643681
}
644682

645-
@androidx.compose.runtime.Composable
683+
@Composable
646684
private fun DetailField(label: String, value: String, borderColor: Color) {
647685
OutlinedTextField(
648686
value = value,
@@ -699,6 +737,15 @@ class SQLiteActivity : ComponentActivity() {
699737
}
700738
}
701739

740+
private fun sagpDisabledReason(mode: IntegrationMode, row: DemoRow): String? {
741+
if (!BuildConfig.USE_SAGP) return null
742+
val demo = row.variantFor(mode)?.demo ?: return null
743+
return when (demo) {
744+
SqlDemo.DRIVER_DIRECT -> SAGP_DIRECT_DRIVER_MESSAGE
745+
else -> null
746+
}
747+
}
748+
702749
/** Closes + deletes every demo database file (via [SampleDatabases]), then re-warms them. */
703750
private suspend fun resetDatabases(): String {
704751
val cleared = SampleDatabases.reset(applicationContext)

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/sqlite/SampleDatabases.kt

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.util.Log
55
import androidx.room.Room
66
import androidx.room3.Room as Room3
77
import androidx.sqlite.SQLiteConnection
8+
import androidx.sqlite.SQLiteDriver
89
import androidx.sqlite.db.SupportSQLiteDatabase
910
import androidx.sqlite.db.SupportSQLiteOpenHelper
1011
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
@@ -13,6 +14,7 @@ import androidx.sqlite.driver.bundled.BundledSQLiteDriver
1314
import androidx.sqlite.execSQL
1415
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
1516
import io.sentry.android.sqlite.SentrySupportSQLiteOpenHelper
17+
import io.sentry.samples.android.BuildConfig
1618
import io.sentry.samples.android.sqlite.SampleDatabases.driverDirectLock
1719
import io.sentry.samples.android.sqlite.SampleDatabases.openHelperDirectLock
1820
import io.sentry.samples.android.sqlite.SampleDatabases.reset
@@ -85,12 +87,10 @@ object SampleDatabases {
8587
fun driverConnection(context: Context): SQLiteConnection =
8688
synchronized(driverDirectLock) {
8789
driverConnection
88-
?: SentrySQLiteDriver.create(BundledSQLiteDriver())
89-
.open(databaseFile(context, "driver_direct.db"))
90-
.also {
91-
it.execSQL(SqlStatements.CREATE_SONG) // one-time table setup, at open
92-
driverConnection = it
93-
}
90+
?: wrapDriver(BundledSQLiteDriver()).open(databaseFile(context, "driver_direct.db")).also {
91+
it.execSQL(SqlStatements.CREATE_SONG) // one-time table setup, at open
92+
driverConnection = it
93+
}
9494
}
9595

9696
/**
@@ -104,7 +104,7 @@ object SampleDatabases {
104104
// SupportSQLiteDriver.open() requires fileName to match the helper's databaseName();
105105
// use the absolute path Room and the direct driver path both pass to open().
106106
val dbPath = databaseFile(context, "bridge_direct.db")
107-
SentrySQLiteDriver.create(SupportSQLiteDriver(buildBridgeDirectHelper(context, dbPath)))
107+
wrapDriver(SupportSQLiteDriver(buildBridgeDirectHelper(context, dbPath)))
108108
.open(dbPath)
109109
.also {
110110
it.execSQL(SqlStatements.CREATE_SONG)
@@ -122,9 +122,7 @@ object SampleDatabases {
122122
"bridge_room2.db",
123123
)
124124
.setDriver(
125-
SentrySQLiteDriver.create(
126-
SupportSQLiteDriver(buildBridgeRoom2Helper(context.applicationContext))
127-
)
125+
wrapDriver(SupportSQLiteDriver(buildBridgeRoom2Helper(context.applicationContext)))
128126
)
129127
.setQueryCoroutineContext(Dispatchers.IO)
130128
.fallbackToDestructiveMigration(true)
@@ -140,7 +138,7 @@ object SampleDatabases {
140138
SampleRoom2Database::class.java,
141139
"driver_room2.db",
142140
)
143-
.setDriver(SentrySQLiteDriver.create(BundledSQLiteDriver()))
141+
.setDriver(wrapDriver(BundledSQLiteDriver()))
144142
.setQueryCoroutineContext(Dispatchers.IO)
145143
.fallbackToDestructiveMigration(true)
146144
.build()
@@ -151,7 +149,7 @@ object SampleDatabases {
151149
synchronized(this) {
152150
driverRoom3Db
153151
?: Room3.databaseBuilder<SampleRoom3Database>(context.applicationContext, "driver_room3.db")
154-
.setDriver(SentrySQLiteDriver.create(BundledSQLiteDriver()))
152+
.setDriver(wrapDriver(BundledSQLiteDriver()))
155153
.setQueryCoroutineContext(Dispatchers.IO)
156154
.build()
157155
.also { driverRoom3Db = it }
@@ -171,9 +169,7 @@ object SampleDatabases {
171169
"openhelper_room.db",
172170
)
173171
.openHelperFactory { configuration ->
174-
SentrySupportSQLiteOpenHelper.create(
175-
FrameworkSQLiteOpenHelperFactory().create(configuration)
176-
)
172+
wrapOpenHelper(FrameworkSQLiteOpenHelperFactory().create(configuration))
177173
}
178174
.fallbackToDestructiveMigration(true)
179175
.build()
@@ -189,9 +185,7 @@ object SampleDatabases {
189185
name = "openhelper_sqldelight.db",
190186
factory =
191187
SupportSQLiteOpenHelper.Factory { configuration ->
192-
SentrySupportSQLiteOpenHelper.create(
193-
FrameworkSQLiteOpenHelperFactory().create(configuration)
194-
)
188+
wrapOpenHelper(FrameworkSQLiteOpenHelperFactory().create(configuration))
195189
},
196190
)
197191
.also { sqlDelightDriver = it }
@@ -232,9 +226,7 @@ object SampleDatabases {
232226
}
233227
)
234228
.build()
235-
return SentrySupportSQLiteOpenHelper.create(
236-
FrameworkSQLiteOpenHelperFactory().create(configuration)
237-
)
229+
return wrapOpenHelper(FrameworkSQLiteOpenHelperFactory().create(configuration))
238230
}
239231

240232
private fun buildSentryHelper(context: Context, dbName: String): SupportSQLiteOpenHelper {
@@ -252,17 +244,22 @@ object SampleDatabases {
252244
}
253245
)
254246
.build()
255-
return SentrySupportSQLiteOpenHelper.create(
256-
FrameworkSQLiteOpenHelperFactory().create(configuration)
257-
)
247+
return wrapOpenHelper(FrameworkSQLiteOpenHelperFactory().create(configuration))
258248
}
259249

250+
private fun wrapDriver(driver: SQLiteDriver): SQLiteDriver =
251+
if (BuildConfig.USE_SAGP) driver else SentrySQLiteDriver.create(driver)
252+
253+
private fun wrapOpenHelper(delegate: SupportSQLiteOpenHelper): SupportSQLiteOpenHelper =
254+
if (BuildConfig.USE_SAGP) delegate else SentrySupportSQLiteOpenHelper.create(delegate)
255+
260256
/** Opens every database on a background thread, forcing the one-time open + bootstrap to run. */
261257
fun warmUp(context: Context) {
262258
val appContext = context.applicationContext
263259
val generation = ++warmUpGeneration
264260
warmUpComplete = false
265261
warmUpErrors = ""
262+
Log.i(TAG, "Warm-up starting (USE_SAGP=${BuildConfig.USE_SAGP})")
266263
// Fire-and-forget: the warm-up outlives no particular screen, so a bare scope is fine here.
267264
warmUpJob =
268265
CoroutineScope(Dispatchers.IO).launch {

0 commit comments

Comments
 (0)