Skip to content
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
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
20 changes: 16 additions & 4 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"
Comment thread
romtsn marked this conversation as resolved.
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
Comment thread
romtsn marked this conversation as resolved.

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

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

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

val navigationRuntime = "androidx.navigation:navigation-runtime:2.4.2"
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ 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 {
Expand Down
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.
121 changes: 121 additions & 0 deletions sentry-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.extensions.DetektExtension

plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.compose")
jacoco
id(Config.QualityPlugins.gradleVersions)
id(Config.QualityPlugins.detektPlugin)
`maven-publish` // necessary for publishMavenLocal task to publish correct artifacts
}

kotlin {
explicitApi()

android {
publishLibraryVariants("release")
}
jvm("desktop") {
compilations.all {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}

sourceSets.all {
// Allow all experimental APIs, since MPP projects are themselves experimental
languageSettings.apply {
optIn("kotlin.Experimental")
optIn("kotlin.ExperimentalMultiplatform")
}
}

sourceSets {
val commonMain by getting {
dependencies {
api(compose.runtime)
api(compose.ui)

implementation(Config.Libs.kotlinStdLib)
}
}
val androidMain by getting {
dependencies {
api(projects.sentry)
api(projects.sentryAndroidNavigation)

api(Config.Libs.composeNavigation)
implementation(Config.Libs.lifecycleCommonJava8)
}
}
val androidTest by getting {
dependencies {
implementation(Config.TestLibs.kotlinTestJunit)
implementation(Config.TestLibs.mockitoKotlin)
implementation(Config.TestLibs.mockitoInline)
}
}
}
}

android {
compileSdk = Config.Android.compileSdkVersion

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

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

sourceSets["main"].apply {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
}

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

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
}
}

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
}
1 change: 1 addition & 0 deletions sentry-compose/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kotlin.mpp.stability.nowarn=true
7 changes: 7 additions & 0 deletions sentry-compose/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-compose/src/androidMain/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.compose"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.sentry.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import io.sentry.HubAdapter
import io.sentry.IHub
import io.sentry.android.navigation.SentryNavigationListener

internal class SentryLifecycleObserver(
private val navController: NavController,
private val hub: IHub = HubAdapter.getInstance(),
private val navListener: NavController.OnDestinationChangedListener =
SentryNavigationListener(hub)
Comment thread
marandaneto marked this conversation as resolved.
) : LifecycleEventObserver {

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
navController.addOnDestinationChangedListener(navListener)
} else if (event == Lifecycle.Event.ON_PAUSE) {
navController.removeOnDestinationChangedListener(navListener)
}
}

fun dispose() {
navController.removeOnDestinationChangedListener(navListener)
}
}

// As described in https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects#6
@Composable
@NonRestartableComposable
public fun NavHostController.withSentryObservableEffect(): NavHostController {
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle, this) {
val observer = SentryLifecycleObserver(this@withSentryObservableEffect)

lifecycle.addObserver(observer)

onDispose {
observer.dispose()
lifecycle.removeObserver(observer)
}
}
return this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.sentry.compose

import androidx.lifecycle.Lifecycle
import androidx.navigation.NavController
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import io.sentry.IHub
import io.sentry.android.navigation.SentryNavigationListener
import kotlin.test.Test

internal class SentryLifecycleObserverTest {

class Fixture {
val navListener = mock<SentryNavigationListener>()
val hub = mock<IHub>()
val navController = mock<NavController>()

fun getSut(): SentryLifecycleObserver {
return SentryLifecycleObserver(navController, hub, navListener)
}
}

private val fixture = Fixture()

@Test
fun `onResume adds navigation listener`() {
val sut = fixture.getSut()

sut.onStateChanged(mock(), Lifecycle.Event.ON_RESUME)

verify(fixture.navController).addOnDestinationChangedListener(fixture.navListener)
}

@Test
fun `onPause removes navigation listener`() {
val sut = fixture.getSut()

sut.onStateChanged(mock(), Lifecycle.Event.ON_PAUSE)

verify(fixture.navController).removeOnDestinationChangedListener(fixture.navListener)
}

@Test
fun `dispose removes navigation listener`() {
val sut = fixture.getSut()

sut.dispose()

verify(fixture.navController).removeOnDestinationChangedListener(fixture.navListener)
}
}
14 changes: 13 additions & 1 deletion sentry-samples/sentry-samples-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {

defaultConfig {
applicationId = "io.sentry.samples.android"
minSdk = Config.Android.minSdkVersionOkHttp
minSdk = Config.Android.minSdkVersionCompose
targetSdk = Config.Android.targetSdkVersion
versionCode = 2
versionName = "1.1.0"
Expand Down Expand Up @@ -36,6 +36,11 @@ android {
// Determines whether to support View Binding.
// Note that the viewBinding.enabled property is now deprecated.
viewBinding = true
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = Config.composeVersion
}

dependenciesInfo {
Expand Down Expand Up @@ -104,6 +109,7 @@ dependencies {
implementation(projects.sentryAndroidOkhttp)
implementation(projects.sentryAndroidFragment)
implementation(projects.sentryAndroidTimber)
implementation(projects.sentryCompose)
implementation(Config.Libs.fragment)

// how to exclude androidx if release health feature is disabled
Expand All @@ -118,5 +124,11 @@ dependencies {
implementation(Config.Libs.retrofit2)
implementation(Config.Libs.retrofit2Gson)

implementation(Config.Libs.composeActivity)
implementation(Config.Libs.composeFoundation)
implementation(Config.Libs.composeFoundationLayout)
implementation(Config.Libs.composeNavigation)
implementation(Config.Libs.composeMaterial)

debugImplementation(Config.Libs.leakCanary)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
android:name=".PermissionsActivity"
android:exported="false" />

<activity
android:name=".compose.ComposeActivity"
android:exported="false" />

<!-- NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard-->
<meta-data android:name="io.sentry.dsn" android:value="https://1053864c67cc410aa1ffc9701bd6f93d@o447951.ingest.sentry.io/5428559" />

Expand Down
Loading