Skip to content

Commit

Permalink
feat: Add sentry-android-navigation module (#2116)
Browse files Browse the repository at this point in the history
* feat: Add compose module (navigation) (#2121)
* feat: Add compose sample (#2122)
  • Loading branch information
romtsn authored Jun 24, 2022
1 parent cedc62e commit b141252
Show file tree
Hide file tree
Showing 28 changed files with 771 additions and 4 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ buildscript {
// classpath("io.sentry:sentry-android-gradle-plugin:{version}")

classpath(Config.QualityPlugins.binaryCompatibilityValidatorPlugin)
classpath(Config.BuildPlugins.composeGradlePlugin)
}
}

Expand Down
18 changes: 16 additions & 2 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import java.math.BigDecimal

object Config {
val kotlinVersion = "1.5.31"
val kotlinVersion = "1.6.10"
val kotlinStdLib = "stdlib-jdk8"

val springBootVersion = "2.6.8"
val kotlinCompatibleLanguageVersion = "1.4"

val composeVersion = "1.1.1"

object BuildPlugins {
val androidGradle = "com.android.tools.build:gradle:7.2.0"
val kotlinGradlePlugin = "gradle-plugin"
Expand All @@ -19,14 +21,16 @@ object Config {
val grettyVersion = "4.0.0"
val gradleMavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.18.0"
val dokkaPlugin = "org.jetbrains.dokka:dokka-gradle-plugin:$kotlinVersion"
val composeGradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$composeVersion"
}

object Android {
private val sdkVersion = 31
private val sdkVersion = 32

val minSdkVersion = 14
val minSdkVersionOkHttp = 21
val minSdkVersionNdk = 16
val minSdkVersionCompose = 21
val targetSdkVersion = sdkVersion
val compileSdkVersion = sdkVersion

Expand Down Expand Up @@ -104,6 +108,16 @@ object Config {
val graphQlJava = "com.graphql-java:graphql-java:17.3"

val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect"
val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib"

private val navigationVersion = "2.4.2"
val navigationRuntime = "androidx.navigation:navigation-runtime:$navigationVersion"
// compose deps
val composeNavigation = "androidx.navigation:navigation-compose:$navigationVersion"
val composeActivity = "androidx.activity:activity-compose:1.4.0"
val composeFoundation = "androidx.compose.foundation:foundation:$composeVersion"
val composeFoundationLayout = "androidx.compose.foundation:foundation-layout:$composeVersion"
val composeMaterial = "androidx.compose.material3:material3:1.0.0-alpha13"
}

object AnnotationProcessors {
Expand Down
1 change: 1 addition & 0 deletions sentry-android-navigation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
15 changes: 15 additions & 0 deletions sentry-android-navigation/api/sentry-android-navigation.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public final class io/sentry/android/navigation/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
public fun <init> ()V
}

public final class io/sentry/android/navigation/SentryNavigationListener : androidx/navigation/NavController$OnDestinationChangedListener {
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;)V
public synthetic fun <init> (Lio/sentry/IHub;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onDestinationChanged (Landroidx/navigation/NavController;Landroidx/navigation/NavDestination;Landroid/os/Bundle;)V
}

95 changes: 95 additions & 0 deletions sentry-android-navigation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.extensions.DetektExtension

plugins {
id("com.android.library")
kotlin("android")
jacoco
id(Config.QualityPlugins.gradleVersions)
id(Config.QualityPlugins.detektPlugin)
}

android {
compileSdk = Config.Android.compileSdkVersion

defaultConfig {
targetSdk = Config.Android.targetSdkVersion
minSdk = Config.Android.minSdkVersion

// for AGP 4.1
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

buildTypes {
getByName("debug")
getByName("release") {
consumerProguardFiles("proguard-rules.pro")
}
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
kotlinOptions.languageVersion = Config.kotlinCompatibleLanguageVersion
}

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

lint {
warningsAsErrors = true
checkDependencies = true

// We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks.
checkReleaseBuilds = false
}

variantFilter {
if (Config.Android.shouldSkipDebugVariant(buildType.name)) {
ignore = true
}
}
}

tasks.withType<Test> {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = false
}
}

kotlin {
explicitApi()
}

dependencies {
api(projects.sentry)

compileOnly(Config.Libs.navigationRuntime)

// tests
testImplementation(Config.Libs.navigationRuntime)

testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.mockitoInline)

testImplementation(Config.TestLibs.robolectric)
testImplementation(Config.TestLibs.androidxCore)
testImplementation(Config.TestLibs.androidxRunner)
testImplementation(Config.TestLibs.androidxJunit)
testImplementation(Config.TestLibs.androidxCoreKtx)
}

tasks.withType<Detekt> {
// Target version of the generated JVM bytecode. It is used for type resolution.
jvmTarget = JavaVersion.VERSION_1_8.toString()
}

configure<DetektExtension> {
buildUponDefaultConfig = true
allRules = true
}
7 changes: 7 additions & 0 deletions sentry-android-navigation/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
##---------------Begin: proguard configuration for Compose ----------

# To ensure that stack traces is unambiguous
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
-keepattributes LineNumberTable,SourceFile

##---------------End: proguard configuration for Compose ----------
2 changes: 2 additions & 0 deletions sentry-android-navigation/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="io.sentry.android.navigation"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.sentry.android.navigation

import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import io.sentry.Breadcrumb
import io.sentry.Hint
import io.sentry.HubAdapter
import io.sentry.IHub
import io.sentry.SentryLevel.INFO
import io.sentry.TypeCheckHint
import java.lang.ref.WeakReference

class SentryNavigationListener @JvmOverloads constructor(
private val hub: IHub = HubAdapter.getInstance()
) : NavController.OnDestinationChangedListener {

private var previousDestinationRef: WeakReference<NavDestination>? = null
private var previousArgs: Bundle? = null

override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
addBreadcrumb(destination, arguments)
previousDestinationRef = WeakReference(destination)
previousArgs = arguments
}

private fun addBreadcrumb(destination: NavDestination, arguments: Bundle?) {
val breadcrumb = Breadcrumb().apply {
type = "navigation"
category = "navigation"

val from = previousDestinationRef?.get()?.route
from?.let { data["from"] = it }
previousArgs?.let { args ->
val fromArguments = args.keySet().filter {
it != NavController.KEY_DEEP_LINK_INTENT // there's a lot of unrelated stuff
}.associateWith { args[it] }
if (fromArguments.isNotEmpty()) {
data["from_arguments"] = fromArguments
}
}

val to = destination.route
to?.let { data["to"] = it }
arguments?.let { args ->
val toArguments = args.keySet().filter {
it != NavController.KEY_DEEP_LINK_INTENT // there's a lot of unrelated stuff
}.associateWith { args[it] }
if (toArguments.isNotEmpty()) {
data["to_arguments"] = toArguments
}
}

level = INFO
}
val hint = Hint()
hint.set(TypeCheckHint.ANDROID_NAV_DESTINATION, destination)
hub.addBreadcrumb(breadcrumb)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.sentry.android.navigation

import androidx.core.os.bundleOf
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nhaarman.mockitokotlin2.check
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.Breadcrumb
import io.sentry.IHub
import io.sentry.SentryLevel
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

@RunWith(AndroidJUnit4::class)
@Config(sdk = [31])
class SentryNavigationListenerTest {

class Fixture {
val hub = mock<IHub>()
val destination = mock<NavDestination>()
val navController = mock<NavController>()

fun getSut(toRoute: String = "route"): SentryNavigationListener {
whenever(destination.route).thenReturn(toRoute)
return SentryNavigationListener(hub)
}
}

private val fixture = Fixture()

@Test
fun `onDestinationChanged captures a breadcrumb`() {
val sut = fixture.getSut()

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

verify(fixture.hub).addBreadcrumb(
check<Breadcrumb> {
assertEquals("navigation", it.type)
assertEquals("navigation", it.category)
assertEquals("route", it.data["to"])
assertEquals(SentryLevel.INFO, it.level)
}
)
}

@Test
fun `onDestinationChanged captures a breadcrumb with arguments`() {
val sut = fixture.getSut()

sut.onDestinationChanged(
fixture.navController,
fixture.destination,
bundleOf("arg1" to "foo", "arg2" to "bar")
)

verify(fixture.hub).addBreadcrumb(
check<Breadcrumb> {
assertEquals("route", it.data["to"])
assertEquals(mapOf("arg1" to "foo", "arg2" to "bar"), it.data["to_arguments"])
}
)
}

@Test
fun `onDestinationChanged does not send empty args map`() {
val sut = fixture.getSut()

sut.onDestinationChanged(
fixture.navController,
fixture.destination,
bundleOf()
)

verify(fixture.hub).addBreadcrumb(
check<Breadcrumb> {
assertEquals("route", it.data["to"])
assertNull(it.data["to_arguments"])
}
)
}

@Test
fun `onDestinationChanged captures a breadcrumb with from and to destinations`() {
val sut = fixture.getSut(toRoute = "route_from")

sut.onDestinationChanged(
fixture.navController,
fixture.destination,
bundleOf("from_arg1" to "from_foo")
)
reset(fixture.hub)

val toDestination = mock<NavDestination> {
whenever(mock.route).thenReturn("route_to")
}
sut.onDestinationChanged(
fixture.navController,
toDestination,
bundleOf("to_arg1" to "to_foo")
)
verify(fixture.hub).addBreadcrumb(
check<Breadcrumb> {
assertEquals("route_from", it.data["from"])
assertEquals(mapOf("from_arg1" to "from_foo"), it.data["from_arguments"])

assertEquals("route_to", it.data["to"])
assertEquals(mapOf("to_arg1" to "to_foo"), it.data["to_arguments"])
}
)
}
}
1 change: 1 addition & 0 deletions sentry-compose/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
12 changes: 12 additions & 0 deletions sentry-compose/api/android/sentry-compose.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
public final class io/sentry/compose/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
public fun <init> ()V
}

public final class io/sentry/compose/SentryNavigationIntegrationKt {
public static final fun withSentryObservableEffect (Landroidx/navigation/NavHostController;Landroidx/compose/runtime/Composer;I)Landroidx/navigation/NavHostController;
}

Empty file.
Loading

0 comments on commit b141252

Please sign in to comment.