Skip to content

Commit

Permalink
Add auto-instrumentation for compose navigation (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi committed Nov 8, 2022
1 parent 5bd7243 commit ec6cf61
Show file tree
Hide file tree
Showing 27 changed files with 627 additions and 103 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Populate events with dependencies metadata ([#396](https://github.com/getsentry/sentry-android-gradle-plugin/pull/396))
- Add auto-instrumentation for compose navigation ([#392](https://github.com/getsentry/sentry-android-gradle-plugin/pull/392))

### Dependencies

Expand Down
8 changes: 7 additions & 1 deletion buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object LibsVersion {
const val JUNIT = "4.13.2"
const val ASM = "7.0" // compatibility matrix -> https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/instrumentation/InstrumentationContext#apiversion
const val SQLITE = "2.1.0"
const val SENTRY = "5.5.0"
const val SENTRY = "6.6.0"
}

object Libs {
Expand Down Expand Up @@ -62,6 +62,12 @@ object Samples {
const val recyclerView = "androidx.recyclerview:recyclerview:1.2.0"
const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
const val appcompat = "androidx.appcompat:appcompat:1.2.0"

const val composeRuntime = "androidx.compose.runtime:runtime:1.1.1"
const val composeNavigation = "androidx.navigation:navigation-compose:2.5.2"
const val composeActivity = "androidx.activity:activity-compose:1.4.0"
const val composeFoundation = "androidx.compose.foundation:foundation:1.2.1"
const val composeFoundationLayout = "androidx.compose.foundation:foundation-layout:1.2.1"
}

object Coroutines {
Expand Down
14 changes: 14 additions & 0 deletions examples/android-instrumentation-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ android {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
namespace = "io.sentry.samples.instrumentation"

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.1.1"
}
}

// useful, when we want to modify room-generated classes, and then compile them into .class files
Expand All @@ -55,6 +63,12 @@ dependencies {
implementation(Samples.AndroidX.lifecycle)
implementation(Samples.AndroidX.appcompat)

implementation(Samples.AndroidX.composeRuntime)
implementation(Samples.AndroidX.composeActivity)
implementation(Samples.AndroidX.composeFoundation)
implementation(Samples.AndroidX.composeFoundationLayout)
implementation(Samples.AndroidX.composeNavigation)

implementation(Samples.Coroutines.core)
implementation(Samples.Coroutines.android)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
</intent-filter>
</activity>

<activity
android:name=".ui.ComposeActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat" />

<activity
android:name=".ui.EditActivity"
android:theme="@style/Theme.AppCompat.NoActionBar" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.sentry.samples.instrumentation.ui

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

class ComposeActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()

NavHost(
navController = navController,
startDestination = Destination.Home.route
) {
val pillShape = RoundedCornerShape(50)

composable(Destination.Home.route) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
BasicText(
modifier = Modifier
.border(2.dp, Color.Gray, pillShape)
.clip(pillShape)
.clickable {
navController.navigate(Destination.Details.route)
}
.padding(24.dp),
text = "Home. Tap to go to Details."
)
}
}
composable(Destination.Details.route) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
BasicText(
modifier = Modifier
.border(2.dp, Color.Gray, pillShape)
.clip(pillShape)
.clickable {
navController.popBackStack()
}
.padding(24.dp),
text = "Details. Tap or press back to return."
)
}
}
}
}
}

sealed class Destination(
val route: String
) {
object Home : Destination("home")
object Details : Destination("details")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class MainActivity : ComponentActivity() {
startActivity(Intent(this, EditActivity::class.java))
return@setOnMenuItemClickListener true
}
if (it.itemId == R.id.action_compose) {
startActivity(Intent(this, ComposeActivity::class.java))
return@setOnMenuItemClickListener true
}
return@setOnMenuItemClickListener false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@
android:title="Add"
app:showAsAction="ifRoom"
tools:ignore="HardcodedText" />

<item
android:id="@+id/action_compose"
android:icon="@android:drawable/ic_menu_gallery"
android:title="Compose"
app:showAsAction="always"
tools:ignore="HardcodedText" />
</menu>
5 changes: 2 additions & 3 deletions plugin-build/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ dependencies {
testImplementationAar(Libs.SQLITE)
testImplementationAar(Libs.SQLITE_FRAMEWORK)
testRuntimeOnly(files(androidSdkPath))
testRuntimeOnly(Libs.SENTRY_ANDROID)
testImplementationAar(Libs.SENTRY_ANDROID)

testRuntimeOnly(
files(
Expand All @@ -75,14 +75,13 @@ tasks.withType<GroovyCompile>().configureEach {
}

tasks.withType<KotlinCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_11.toString()
targetCompatibility = JavaVersion.VERSION_11.toString()
classpath += files(sourceSets["main"].groovy.classesDirectory)

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=enable")
languageVersion = "1.4"
apiVersion = "1.4"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,59 @@ abstract class AbstractInstallStrategy : ComponentMetadataRule {

protected lateinit var logger: Logger

protected abstract val moduleId: String
protected abstract val sentryModuleId: String

protected abstract val shouldInstallModule: Boolean

protected open val minSupportedVersion: SemVer = SemVer(0, 0, 0)
protected open val minSupportedThirdPartyVersion: SemVer = SemVer(0, 0, 0)

protected open val minSupportedSentryVersion: SemVer = SemVer(0, 0, 0)

override fun execute(context: ComponentMetadataContext) {
val autoInstallState = AutoInstallState.getInstance()
if (!shouldInstallModule) {
logger.info {
"$moduleId won't be installed because it was already installed directly"
"$sentryModuleId won't be installed because it was already installed directly"
}
return
}
val semVer = SemVer.parse(context.details.id.version)
if (semVer < minSupportedVersion) {
val thirdPartySemVersion = SemVer.parse(context.details.id.version)
if (thirdPartySemVersion < minSupportedThirdPartyVersion) {
logger.warn {
"$moduleId won't be installed because the current version is " +
"lower than the minimum supported version ($minSupportedVersion)"
"$sentryModuleId won't be installed because the current version is " +
"lower than the minimum supported version ($minSupportedThirdPartyVersion)"
}
return
}

if (minSupportedSentryVersion.major > 0) {
try {
val sentrySemVersion = SemVer.parse(autoInstallState.sentryVersion)
if (sentrySemVersion < minSupportedSentryVersion) {
logger.warn {
"$sentryModuleId won't be installed because the current version is " +
"lower than the minimum supported sentry version " +
"($autoInstallState.sentryVersion)"
}
return
}
} catch (ex: IllegalArgumentException) {
logger.warn {
"$sentryModuleId won't be installed because the provided " +
"sentry version($autoInstallState.sentryVersion) could not be processed " +
"as a semantic version."
}
return
}
}

context.details.allVariants { metadata ->
metadata.withDependencies { dependencies ->
val sentryVersion = autoInstallState.sentryVersion
dependencies.add("$SENTRY_GROUP:$moduleId:$sentryVersion")
dependencies.add("$SENTRY_GROUP:$sentryModuleId:$sentryVersion")

logger.info {
"$moduleId was successfully installed with version: $sentryVersion"
"$sentryModuleId was successfully installed with version: $sentryVersion"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.sentry.android.gradle.autoinstall

import io.sentry.android.gradle.autoinstall.compose.ComposeInstallStrategy
import io.sentry.android.gradle.autoinstall.compose.ComposeInstallStrategy.Registrar.SENTRY_COMPOSE_ID
import io.sentry.android.gradle.autoinstall.fragment.FragmentInstallStrategy
import io.sentry.android.gradle.autoinstall.fragment.FragmentInstallStrategy.Registrar.SENTRY_FRAGMENT_ID
import io.sentry.android.gradle.autoinstall.okhttp.OkHttpInstallStrategy
Expand All @@ -18,7 +20,8 @@ private const val SENTRY_ANDROID_CORE_ID = "sentry-android-core"
private val strategies = listOf(
OkHttpInstallStrategy.Registrar,
TimberInstallStrategy.Registrar,
FragmentInstallStrategy.Registrar
FragmentInstallStrategy.Registrar,
ComposeInstallStrategy.Registrar
)

fun Project.installDependencies(extension: SentryPluginExtension) {
Expand All @@ -34,6 +37,7 @@ fun Project.installDependencies(extension: SentryPluginExtension) {
installOkHttp = !dependencies.isModuleAvailable(SENTRY_OKHTTP_ID)
installTimber = !dependencies.isModuleAvailable(SENTRY_TIMBER_ID)
installFragment = !dependencies.isModuleAvailable(SENTRY_FRAGMENT_ID)
installCompose = !dependencies.isModuleAvailable(SENTRY_COMPOSE_ID)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ class AutoInstallState private constructor() : Serializable {
@set:Synchronized
var installTimber: Boolean = false

@get:Synchronized
@set:Synchronized
var installCompose: Boolean = false

override fun toString(): String {
return "AutoInstallState(sentryVersion='$sentryVersion', " +
"installOkHttp=$installOkHttp, " +
"installFragment=$installFragment, " +
"installTimber=$installTimber)"
"installTimber=$installTimber," +
"installCompose=$installCompose)"
}

override fun equals(other: Any?): Boolean {
Expand All @@ -39,6 +44,7 @@ class AutoInstallState private constructor() : Serializable {
if (installOkHttp != other.installOkHttp) return false
if (installFragment != other.installFragment) return false
if (installTimber != other.installTimber) return false
if (installCompose != other.installCompose) return false

return true
}
Expand All @@ -48,6 +54,7 @@ class AutoInstallState private constructor() : Serializable {
result = 31 * result + installOkHttp.hashCode()
result = 31 * result + installFragment.hashCode()
result = 31 * result + installTimber.hashCode()
result = 31 * result + installCompose.hashCode()
return result
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.sentry.android.gradle.autoinstall.compose

import io.sentry.android.gradle.SentryPlugin
import io.sentry.android.gradle.autoinstall.AbstractInstallStrategy
import io.sentry.android.gradle.autoinstall.AutoInstallState
import io.sentry.android.gradle.autoinstall.InstallStrategyRegistrar
import io.sentry.android.gradle.util.SemVer
import javax.inject.Inject
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
import org.slf4j.Logger

abstract class ComposeInstallStrategy : AbstractInstallStrategy {

constructor(logger: Logger) : super() {
this.logger = logger
}

@Suppress("unused") // used by Gradle
@Inject // inject is needed to avoid Gradle error
constructor() : this(SentryPlugin.logger)

override val sentryModuleId: String get() = SENTRY_COMPOSE_ID

override val shouldInstallModule: Boolean get() = AutoInstallState.getInstance().installCompose

override val minSupportedSentryVersion: SemVer
get() = SemVer(6, 7, 0)

override val minSupportedThirdPartyVersion: SemVer
get() = SemVer(1, 0, 0)

companion object Registrar : InstallStrategyRegistrar {

internal const val SENTRY_COMPOSE_ID = "sentry-compose-android"

override fun register(component: ComponentMetadataHandler) {
component.withModule(
"androidx.compose.runtime:runtime",
ComposeInstallStrategy::class.java
) {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.sentry.android.gradle.SentryPlugin
import io.sentry.android.gradle.autoinstall.AbstractInstallStrategy
import io.sentry.android.gradle.autoinstall.AutoInstallState
import io.sentry.android.gradle.autoinstall.InstallStrategyRegistrar
import io.sentry.android.gradle.util.SemVer
import javax.inject.Inject
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler
import org.slf4j.Logger
Expand All @@ -19,10 +20,12 @@ abstract class FragmentInstallStrategy : AbstractInstallStrategy {
@Inject // inject is needed to avoid Gradle error
constructor() : this(SentryPlugin.logger)

override val moduleId: String get() = SENTRY_FRAGMENT_ID
override val sentryModuleId: String get() = SENTRY_FRAGMENT_ID

override val shouldInstallModule: Boolean get() = AutoInstallState.getInstance().installFragment

override val minSupportedSentryVersion: SemVer get() = SemVer(5, 1, 0)

companion object Registrar : InstallStrategyRegistrar {
private const val FRAGMENT_GROUP = "androidx.fragment"
private const val FRAGMENT_ID = "fragment"
Expand Down
Loading

0 comments on commit ec6cf61

Please sign in to comment.