diff --git a/.gitignore b/.gitignore index 9c81b2ea..aa6d8bd8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ Pods/ .DS_Store tmp +# Samples +iosApp.xcconfig +sentry.properties +release/ # Xcode diff --git a/.run/androidApp-cocoapods.run.xml b/.run/androidApp-cocoapods.run.xml new file mode 100644 index 00000000..5ca5075e --- /dev/null +++ b/.run/androidApp-cocoapods.run.xml @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/.run/androidApp-mvvm-di-spm.run.xml b/.run/androidApp-mvvm-di-spm.run.xml new file mode 100644 index 00000000..d93e4b81 --- /dev/null +++ b/.run/androidApp-mvvm-di-spm.run.xml @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/.run/androidApp-spm.run.xml b/.run/androidApp-spm.run.xml new file mode 100644 index 00000000..b0221433 --- /dev/null +++ b/.run/androidApp-spm.run.xml @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/.run/iosApp-cocoapods.run.xml b/.run/iosApp-cocoapods.run.xml new file mode 100644 index 00000000..8ce66786 --- /dev/null +++ b/.run/iosApp-cocoapods.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.run/iosApp-mvvm-di-spm.run.xml b/.run/iosApp-mvvm-di-spm.run.xml new file mode 100644 index 00000000..c449975c --- /dev/null +++ b/.run/iosApp-mvvm-di-spm.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.run/iosApp-spm.run.xml b/.run/iosApp-spm.run.xml new file mode 100644 index 00000000..55a25237 --- /dev/null +++ b/.run/iosApp-spm.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 04137e9d..8819e947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Improvements + +- ref: improve samples & add SPM docs ([#82](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/82)) + ## 0.1.1 ### Fixes diff --git a/Makefile b/Makefile index 81cf1e51..2e78c223 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,17 @@ format: # build and run tests compile: ./gradlew build + make buildAppleSamples + +buildAppleSamples: + cd ./sentry-samples/kmp-app-cocoapods/iosApp/iosApp && touch iosApp.xcconfig + cd ./sentry-samples/kmp-app-spm/iosApp && touch iosApp.xcconfig + cd ./sentry-samples/kmp-app-mvvm-di/iosApp && touch iosApp.xcconfig sudo xcode-select --switch /Applications/Xcode.app && /usr/bin/xcodebuild -version - cd ./sentry-samples/kmp-app/iosApp; pod install - xcodebuild -workspace ./sentry-samples/kmp-app/iosApp/iosApp.xcworkspace -scheme iosApp -configuration Debug -sdk iphonesimulator -arch arm64 + cd ./sentry-samples/kmp-app-cocoapods/iosApp; pod install + xcodebuild -workspace ./sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcworkspace -scheme iosApp -configuration Debug -sdk iphonesimulator -arch arm64 + xcodebuild -project ./sentry-samples/kmp-app-spm/iosApp.xcodeproj -scheme iosApp -configuration Debug -sdk iphonesimulator -arch arm64 + xcodebuild -project ./sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj -scheme iosApp -configuration Debug -sdk iphonesimulator -arch arm64 # We stop gradle at the end to make sure the cache folders # don't contain any lock files and are free to be cached. diff --git a/README.md b/README.md index 54873013..2e9d99e8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,31 @@ cocoapods { } ``` +### Swift Package Manager + +Alternatively you can use the Swift Package Manager to include the Sentry Cocoa SDK into this SDK. +Open your iOS app in Xcode and open File > Add Packages. Then add the SDK by entering the git repo url in the top right search field: +`https://github.com/getsentry/sentry-cocoa.git` + +After adding the package, you need to add the following to your shared `build.gradle.kts`: + +```gradle +listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + // ... other Apple targets +).forEach { + it.binaries.framework { + baseName = "shared" + isStatic = true + + // Export the SDK in order to be able to access it directly in the iOS project + export("io.sentry:sentry-kotlin-multiplatform:") + } +} +``` + ## Initialization There are two main strategies for initializing the SDK: diff --git a/sentry-kotlin-multiplatform/src/commonAppleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt b/sentry-kotlin-multiplatform/src/commonAppleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt index 9c6d0cf8..e6257944 100644 --- a/sentry-kotlin-multiplatform/src/commonAppleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt +++ b/sentry-kotlin-multiplatform/src/commonAppleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt @@ -79,10 +79,12 @@ internal actual object SentryBridge { } } -public fun captureError(error: NSError) { +@Suppress("unused") +public fun Sentry.captureError(error: NSError) { SentrySDK.captureError(error) } -public fun captureException(exception: NSException) { +@Suppress("unused") +public fun Sentry.captureException(exception: NSException) { SentrySDK.captureException(exception) } diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BeforeSendIntegrationTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BeforeSendIntegrationTest.kt index b9e71e3d..b143d8df 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BeforeSendIntegrationTest.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BeforeSendIntegrationTest.kt @@ -2,6 +2,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.Message +import io.sentry.kotlin.multiplatform.utils.fakeDsn import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -12,13 +13,16 @@ class BeforeSendIntegrationTest { @Test fun `event is not null if KMP beforeSend option is null`() { - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) } @Test fun `event is null if KMP beforeSend callback config returns null`() { val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { null } @@ -29,6 +33,7 @@ class BeforeSendIntegrationTest { @Test fun `event is not null if KMP beforeSend callback config returns not null`() { val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event } @@ -40,6 +45,7 @@ class BeforeSendIntegrationTest { fun `event logger is modified if KMP beforeSend callback config modifies it`() { val expected = "test" val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.logger = expected event @@ -53,6 +59,7 @@ class BeforeSendIntegrationTest { fun `event level is modified if KMP beforeSend callback config modifies it`() { val expected = SentryLevel.DEBUG val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.level = expected event @@ -66,6 +73,7 @@ class BeforeSendIntegrationTest { fun `event message is modified if KMP beforeSend callback config modifies it`() { val expected = Message("test") val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.message = expected event @@ -79,6 +87,7 @@ class BeforeSendIntegrationTest { fun `event release is modified if KMP beforeSend callback config modifies it`() { val expected = "test" val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.release = expected event @@ -92,6 +101,7 @@ class BeforeSendIntegrationTest { fun `event environment is modified if KMP beforeSend callback config modifies it`() { val expected = "test" val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.environment = expected event @@ -105,6 +115,7 @@ class BeforeSendIntegrationTest { fun `event serverName is modified if KMP beforeSend callback config modifies it`() { val expected = "test" val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.serverName = expected event @@ -118,6 +129,7 @@ class BeforeSendIntegrationTest { fun `event dist is modified if KMP beforeSend callback config modifies it`() { val expected = "test" val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.dist = expected event @@ -131,6 +143,7 @@ class BeforeSendIntegrationTest { fun `event fingerprint is modified if KMP beforeSend callback config modifies it`() { val expected = mutableListOf("test") val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.fingerprint = expected event @@ -144,6 +157,7 @@ class BeforeSendIntegrationTest { fun `event tags are modified if KMP beforeSend callback config modifies it`() { val expected = mutableMapOf("test" to "test") val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.tags = expected event @@ -157,6 +171,7 @@ class BeforeSendIntegrationTest { fun `event breadcrumbs are modified if KMP beforeSend callback config modifies it`() { val expected = mutableListOf(Breadcrumb.debug("test")) val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn it.beforeSend = { event -> event.breadcrumbs = expected event @@ -171,7 +186,9 @@ class BeforeSendIntegrationTest { @Test fun `event logger is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.logger, event.logger) } @@ -187,7 +204,9 @@ class BeforeSendIntegrationTest { @Test fun `event message is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.message, event.message) } @@ -195,7 +214,9 @@ class BeforeSendIntegrationTest { @Test fun `event release is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.release, event.release) } @@ -203,7 +224,9 @@ class BeforeSendIntegrationTest { @Test fun `event environment is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.environment, event.environment) } @@ -211,7 +234,9 @@ class BeforeSendIntegrationTest { @Test fun `event serverName is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.serverName, event.serverName) } @@ -219,7 +244,9 @@ class BeforeSendIntegrationTest { @Test fun `event dist is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.dist, event.dist) } @@ -227,7 +254,9 @@ class BeforeSendIntegrationTest { @Test fun `event fingerprint is not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.fingerprint, event.fingerprint) } @@ -235,7 +264,9 @@ class BeforeSendIntegrationTest { @Test fun `event tags are not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.tags, event.tags) } @@ -243,7 +274,9 @@ class BeforeSendIntegrationTest { @Test fun `event breadcrumbs are not modified if KMP beforeSend callback config is not modified`() { val originalEvent = sentryEventConfigurator.originalEvent - val event = sentryEventConfigurator.applyOptions() + val event = sentryEventConfigurator.applyOptions { + it.dsn = fakeDsn + } assertNotNull(event) assertEquals(originalEvent.breadcrumbs, event.breadcrumbs) } diff --git a/sentry-samples/README.md b/sentry-samples/README.md new file mode 100644 index 00000000..8b3a547d --- /dev/null +++ b/sentry-samples/README.md @@ -0,0 +1,53 @@ + +# Sentry Kotlin Multiplatform Samples + +This contains three samples of Kotlin Multiplatform projects showcasing the Sentry Kotlin Multiplatform SDK usage. +- Sample 1: Android, iOS with Cocoapods, Desktop with Jetpack Compose +- Sample 2: Android, iOS with Swift Package Manager, Desktop with Jetpack Compose +- Sample 3: Android with Jetpack Compose, iOS with Jetpack Compose, MVVM and Dependency Injection with [Koin](https://insert-koin.io/) + +## Getting Started +> All samples are configured as sub-projects. You need to open the root project in Android Studio and sync the gradle files. + +### Requirements +- Xcode (for iOS) +- Android Studio [KMM plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) +- [sentry-cli](https://docs.sentry.io/product/cli/installation/) +- Cocoapods + +### Configuration +Modify the `common-sentry.properties` file according to your needs. + +```bash +# common-sentry.properties +org.slug=YOUR_ORG_SLUG +project.slug=YOUR_PROJECT_SLUG +auth.token=YOUR_AUTH_TOKEN +``` +#### Android +Before running the Android app, execute the `./prepare-android-build.sh` script in the `.../sentry-samples/scripts` directory. +You might need to re-sync your gradle files afterwards. +By default the Android application is using the `release` build and will automatically upload proguard mapping files for deobfuscation after the setup. + +#### iOS +Before running the iOS app, execute the `./prepare-apple-build.sh` script in the `.../sentry-samples/scripts` directory. +By default the iOS application will automatically upload debug symbol files for deobfuscation after the setup. + +### SDK configuration +In the shared code of each sample you will find a `SentrySetup.kt` file where you can configure the SDK to your needs. + +## Running the Samples +Run configurations are automatically set through Android Studio. Further configurations are generally not needed. + +## Troubleshooting +> iOS app run configuration in Android Studio is not working / shows an error + +Reimporting the `.xcodeproj` or `.xcworkspace` in run configurations depending on whether you run the SPM or Cocoapods sample usually fixes the problem. +It should automatically reload the project scheme and configuration. + +> WARNING: CocoaPods requires your terminal to be using UTF-8 encoding. Consider adding the following to ~/.profile: + +Run `export LANG=en_US.UTF-8` to avoid encoding issues. + +## Further information +For more information on the Sentry Kotlin Multiplatform, please refer to the [official Sentry documentation](https://docs.sentry.io/platforms/kotlin-multiplatform/). diff --git a/sentry-samples/common-sentry.properties b/sentry-samples/common-sentry.properties new file mode 100644 index 00000000..eb942b7f --- /dev/null +++ b/sentry-samples/common-sentry.properties @@ -0,0 +1,3 @@ +org.slug=YOUR_ORG_SLUG +project.slug=YOUR_PROJECT_SLUG +auth.token=YOUR_AUTH_TOKEN diff --git a/sentry-samples/kmp-app-cocoapods/androidApp/build.gradle.kts b/sentry-samples/kmp-app-cocoapods/androidApp/build.gradle.kts new file mode 100644 index 00000000..3e70dcaf --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/androidApp/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("com.android.application") + kotlin("android") + id("io.sentry.android.gradle") version "3.5.0" +} + +android { + compileSdk = Config.Android.compileSdkVersion + defaultConfig { + applicationId = "sample.kmp.app.android" + minSdk = Config.Android.minSdkVersion + targetSdk = Config.Android.targetSdkVersion + versionCode = 1 + versionName = "1.0" + } + signingConfigs { + create("release") { + storeFile = file("sentry.keystore") + storePassword = "sentry" + keyAlias = "Sentry Android Key" + keyPassword = "sentry" + } + } + buildTypes { + getByName("release") { + isDefault = true + isMinifyEnabled = true + proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) + signingConfig = signingConfigs.getByName("release") + } + } +} + +dependencies { + implementation(rootProject.project(":sentry-samples:kmp-app-spm:shared")) + implementation("com.google.android.material:material:1.6.1") + implementation("androidx.appcompat:appcompat:1.4.2") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") +} + +// Prevent Sentry from being included in the Android app through the AGP. +configurations { + compileOnly { + exclude(group = "io.sentry", module = "sentry") + exclude(group = "io.sentry", module = "sentry-android") + } +} + +sentry { + autoUploadProguardMapping.set(false) +} diff --git a/sentry-samples/kmp-app-cocoapods/androidApp/sentry.keystore b/sentry-samples/kmp-app-cocoapods/androidApp/sentry.keystore new file mode 100644 index 00000000..3d6ddc7c Binary files /dev/null and b/sentry-samples/kmp-app-cocoapods/androidApp/sentry.keystore differ diff --git a/sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/AndroidManifest.xml similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/AndroidManifest.xml diff --git a/sentry-samples/kmp-app/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt diff --git a/sentry-samples/kmp-app/androidApp/src/main/res/layout/activity_main.xml b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/layout/activity_main.xml similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/res/layout/activity_main.xml rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/layout/activity_main.xml diff --git a/sentry-samples/kmp-app/androidApp/src/main/res/raw/sentry.png b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/raw/sentry.png similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/res/raw/sentry.png rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/raw/sentry.png diff --git a/sentry-samples/kmp-app/androidApp/src/main/res/values/colors.xml b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/values/colors.xml similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/res/values/colors.xml rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/values/colors.xml diff --git a/sentry-samples/kmp-app/androidApp/src/main/res/values/styles.xml b/sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/values/styles.xml similarity index 100% rename from sentry-samples/kmp-app/androidApp/src/main/res/values/styles.xml rename to sentry-samples/kmp-app-cocoapods/androidApp/src/main/res/values/styles.xml diff --git a/sentry-samples/kmp-app/desktopApp/build.gradle.kts b/sentry-samples/kmp-app-cocoapods/desktopApp/build.gradle.kts similarity index 94% rename from sentry-samples/kmp-app/desktopApp/build.gradle.kts rename to sentry-samples/kmp-app-cocoapods/desktopApp/build.gradle.kts index 95fe47b1..0e52becb 100644 --- a/sentry-samples/kmp-app/desktopApp/build.gradle.kts +++ b/sentry-samples/kmp-app-cocoapods/desktopApp/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.compose.compose import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { @@ -25,7 +24,7 @@ kotlin { sourceSets { val jvmMain by getting { dependencies { - implementation(rootProject.project(":sentry-samples:kmp-app:shared")) + implementation(rootProject.project(":sentry-samples:kmp-app-cocoapods:shared")) implementation(compose.desktop.currentOs) } } diff --git a/sentry-samples/kmp-app-cocoapods/desktopApp/src/jvmMain/kotlin/sample.kmp.app.desktop/Main.kt b/sentry-samples/kmp-app-cocoapods/desktopApp/src/jvmMain/kotlin/sample.kmp.app.desktop/Main.kt new file mode 100644 index 00000000..0f82c667 --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/desktopApp/src/jvmMain/kotlin/sample.kmp.app.desktop/Main.kt @@ -0,0 +1,67 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package sample.kmp.app.desktop + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import io.sentry.kotlin.multiplatform.Sentry +import io.sentry.kotlin.multiplatform.protocol.Breadcrumb +import sample.kmp.app.LoginImpl +import sample.kmp.app.Platform +import sample.kmp.app.configureSentryScope +import sample.kmp.app.initializeSentry + +@Composable +@Preview +fun App() { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + val btnBackgroundColor = Color(56, 31, 67) + Button({ + Sentry.captureMessage("From KMP Sample App: " + Platform().platform) + }, colors = ButtonDefaults.buttonColors(backgroundColor = btnBackgroundColor)) { + Text("Capture Message", color = Color.White) + } + Button({ + LoginImpl.login("MyUsername") + }, colors = ButtonDefaults.buttonColors(backgroundColor = btnBackgroundColor)) { + Text("Capture Exception", color = Color.White) + } + Button({ + LoginImpl.login() + }, colors = ButtonDefaults.buttonColors(backgroundColor = btnBackgroundColor)) { + Text("Crash", color = Color.White) + } + } +} + +fun main() = application { + Window(onCloseRequest = ::exitApplication, title = "Jetpack Compose Desktop App (Cocoapods Sample Version)") { + // Initialize Sentry using shared code + initializeSentry() + + // Shared scope across all platforms + configureSentryScope() + + // Add platform specific scope in addition to the shared scope + Sentry.configureScope { + it.setContext("JVM Desktop Context", mapOf("context1" to 12, "context2" to false)) + it.addBreadcrumb(Breadcrumb.debug("initialized Sentry on JVM Desktop")) + } + + App() + } +} diff --git a/sentry-samples/kmp-app/iosApp/Podfile b/sentry-samples/kmp-app-cocoapods/iosApp/Podfile similarity index 100% rename from sentry-samples/kmp-app/iosApp/Podfile rename to sentry-samples/kmp-app-cocoapods/iosApp/Podfile diff --git a/sentry-samples/kmp-app/iosApp/Podfile.lock b/sentry-samples/kmp-app-cocoapods/iosApp/Podfile.lock similarity index 91% rename from sentry-samples/kmp-app/iosApp/Podfile.lock rename to sentry-samples/kmp-app-cocoapods/iosApp/Podfile.lock index bfe0d33e..c32432f8 100644 --- a/sentry-samples/kmp-app/iosApp/Podfile.lock +++ b/sentry-samples/kmp-app-cocoapods/iosApp/Podfile.lock @@ -23,7 +23,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Sentry: 16d46dd5ca10e7f4469a2611805a3de123188add SentryPrivate: 2bb4f8d9ff558b25ac70b66c1dedc58a7c43630b - shared: 0dca2e824df822c0e0186869c320e4ba675f23ed + shared: d37be71558369434a00872610da90714f79a1fb3 PODFILE CHECKSUM: f282da88f39e69507b0a255187c8a6b644477756 diff --git a/sentry-samples/kmp-app/iosApp/iosApp.xcodeproj/project.pbxproj b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcodeproj/project.pbxproj similarity index 83% rename from sentry-samples/kmp-app/iosApp/iosApp.xcodeproj/project.pbxproj rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcodeproj/project.pbxproj index ca21bc09..8c4ca0d0 100644 --- a/sentry-samples/kmp-app/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcodeproj/project.pbxproj @@ -9,31 +9,32 @@ /* Begin PBXBuildFile section */ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; - 0E5B2A1A28A27E06000489BA /* sentry.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E5B2A1928A27E06000489BA /* sentry.png */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 243652F129F3500B00FD902A /* sentry.png in Resources */ = {isa = PBXBuildFile; fileRef = 243652F029F3500B00FD902A /* sentry.png */; }; + 3D1664DC8EBDF25BA5271797 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EEA8758FDDDA81262859B40 /* Pods_iosApp.framework */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; - D5B08EC7C23D3FC698ACBEB5 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD4480B062D7800C5B353B6F /* Pods_iosApp.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 0E5B2A1928A27E06000489BA /* sentry.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sentry.png; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 25CA49B0A5ED1BE92174FF45 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + 243652F029F3500B00FD902A /* sentry.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sentry.png; sourceTree = ""; }; + 24B0FC5D29F8273700434F1C /* iosApp.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iosApp.xcconfig; sourceTree = ""; }; + 27764284A12D67D77B0D8962 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 85E7ED6A895F840ADEBA6EA9 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; - FD4480B062D7800C5B353B6F /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9EEA8758FDDDA81262859B40 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D2E6F99EDC5C3622F933AA2F /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 870B838CCCF9D96E0B41F9F4 /* Frameworks */ = { + 15ED548C5D994C7F345A5039 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D5B08EC7C23D3FC698ACBEB5 /* Pods_iosApp.framework in Frameworks */, + 3D1664DC8EBDF25BA5271797 /* Pods_iosApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -48,11 +49,11 @@ path = "Preview Content"; sourceTree = ""; }; - 12A4D8E5051784B06944ACD4 /* Pods */ = { + 1E09B7524956916C5D9D7D40 /* Pods */ = { isa = PBXGroup; children = ( - 25CA49B0A5ED1BE92174FF45 /* Pods-iosApp.debug.xcconfig */, - 85E7ED6A895F840ADEBA6EA9 /* Pods-iosApp.release.xcconfig */, + D2E6F99EDC5C3622F933AA2F /* Pods-iosApp.debug.xcconfig */, + 27764284A12D67D77B0D8962 /* Pods-iosApp.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -62,8 +63,8 @@ children = ( 7555FF7D242A565900829871 /* iosApp */, 7555FF7C242A565900829871 /* Products */, - 12A4D8E5051784B06944ACD4 /* Pods */, - E43E59E03F8D83D9E75F05C0 /* Frameworks */, + 1E09B7524956916C5D9D7D40 /* Pods */, + C3B0B54580AA0124B27DF2B7 /* Frameworks */, ); sourceTree = ""; }; @@ -78,20 +79,21 @@ 7555FF7D242A565900829871 /* iosApp */ = { isa = PBXGroup; children = ( - 0E5B2A1928A27E06000489BA /* sentry.png */, + 243652F029F3500B00FD902A /* sentry.png */, 058557BA273AAA24004C7B11 /* Assets.xcassets */, 7555FF82242A565900829871 /* ContentView.swift */, 7555FF8C242A565B00829871 /* Info.plist */, 2152FB032600AC8F00CF470E /* iOSApp.swift */, 058557D7273AAEEB004C7B11 /* Preview Content */, + 24B0FC5D29F8273700434F1C /* iosApp.xcconfig */, ); path = iosApp; sourceTree = ""; }; - E43E59E03F8D83D9E75F05C0 /* Frameworks */ = { + C3B0B54580AA0124B27DF2B7 /* Frameworks */ = { isa = PBXGroup; children = ( - FD4480B062D7800C5B353B6F /* Pods_iosApp.framework */, + 9EEA8758FDDDA81262859B40 /* Pods_iosApp.framework */, ); name = Frameworks; sourceTree = ""; @@ -103,11 +105,12 @@ isa = PBXNativeTarget; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( - E80F4795196947BF23867272 /* [CP] Check Pods Manifest.lock */, + 0BFD73D9FC258ED49378CBD3 /* [CP] Check Pods Manifest.lock */, 7555FF77242A565900829871 /* Sources */, 7555FF79242A565900829871 /* Resources */, - 870B838CCCF9D96E0B41F9F4 /* Frameworks */, - 3620F85FA2EFF718C6F7366B /* [CP] Embed Pods Frameworks */, + 15ED548C5D994C7F345A5039 /* Frameworks */, + B9409BDB3577907681ECC50B /* [CP] Embed Pods Frameworks */, + 24B0FC5C29F8270F00434F1C /* Upload Debug Symbols to Sentry */, ); buildRules = ( ); @@ -157,7 +160,7 @@ buildActionMask = 2147483647; files = ( 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, - 0E5B2A1A28A27E06000489BA /* sentry.png in Resources */, + 243652F129F3500B00FD902A /* sentry.png in Resources */, 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -165,24 +168,29 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3620F85FA2EFF718C6F7366B /* [CP] Embed Pods Frameworks */ = { + 0BFD73D9FC258ED49378CBD3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E80F4795196947BF23867272 /* [CP] Check Pods Manifest.lock */ = { + 24B0FC5C29F8270F00434F1C /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -190,18 +198,32 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Upload Debug Symbols to Sentry"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "if which sentry-cli >/dev/null; then\nERROR=$(sentry-cli upload-dif \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null)\nif [ ! $? -eq 0 ]; then\necho \"warning: sentry-cli - $ERROR\"\nfi\nelse\necho \"warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\n"; + }; + B9409BDB3577907681ECC50B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -221,6 +243,7 @@ /* Begin XCBuildConfiguration section */ 7555FFA3242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 24B0FC5D29F8273700434F1C /* iosApp.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -282,6 +305,7 @@ }; 7555FFA4242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 24B0FC5D29F8273700434F1C /* iosApp.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -337,7 +361,7 @@ }; 7555FFA6242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 25CA49B0A5ED1BE92174FF45 /* Pods-iosApp.debug.xcconfig */; + baseConfigurationReference = D2E6F99EDC5C3622F933AA2F /* Pods-iosApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -357,7 +381,7 @@ }; 7555FFA7242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 85E7ED6A895F840ADEBA6EA9 /* Pods-iosApp.release.xcconfig */; + baseConfigurationReference = 27764284A12D67D77B0D8962 /* Pods-iosApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; diff --git a/sentry-samples/kmp-app/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcworkspace/contents.xcworkspacedata similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp.xcworkspace/contents.xcworkspacedata rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcworkspace/contents.xcworkspacedata diff --git a/sentry-samples/kmp-app/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/sentry-samples/kmp-app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/Contents.json similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Assets.xcassets/Contents.json diff --git a/sentry-samples/kmp-app/iosApp/iosApp/ContentView.swift b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/ContentView.swift similarity index 86% rename from sentry-samples/kmp-app/iosApp/iosApp/ContentView.swift rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/ContentView.swift index ec6ad541..7e383a7a 100644 --- a/sentry-samples/kmp-app/iosApp/iosApp/ContentView.swift +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/ContentView.swift @@ -6,7 +6,7 @@ struct ContentView: View { Text("KMP Sample App " + Platform().platform) VStack() { Button("Capture Message") { - Sentry().captureMessage(message: "From KMP Sample App " + Platform().platform) + Sentry.shared.captureMessage(message: "From KMP Sample App " + Platform().platform) } Button("Capture Exception") { LoginImpl().login(username: "MyUsername") diff --git a/sentry-samples/kmp-app/iosApp/iosApp/Info.plist b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Info.plist similarity index 98% rename from sentry-samples/kmp-app/iosApp/iosApp/Info.plist rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Info.plist index 40fa6dc1..8044709c 100644 --- a/sentry-samples/kmp-app/iosApp/iosApp/Info.plist +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Info.plist @@ -43,6 +43,6 @@ UIInterfaceOrientationLandscapeRight UILaunchScreen - + - + \ No newline at end of file diff --git a/sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/Contents.json b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json similarity index 96% rename from sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/Contents.json rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json index 73c00596..4aa7c535 100644 --- a/sentry-samples/kmp-app/iosApp/iosApp/Assets.xcassets/Contents.json +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000..3c03f0d4 --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,35 @@ +import SwiftUI +import shared + +@main +struct iOSApp: App { + let sentry = Sentry.shared + + init() { + // Initialize Sentry using shared code + SentrySetupKt.initializeSentry() + + // Shared scope across all platforms + SentrySetupKt.configureSentryScope() + + // Add platform specific scope in addition to the shared scope + sentry.configureScope { scope in + scope.setContext(key: "iOS Context", value: [ + "context1": 20, + "context2": true + ]) + let breadcrumb = Breadcrumb.companion.debug(message: "initialized Sentry on iOS") + scope.addBreadcrumb(breadcrumb: breadcrumb) + if let path = Bundle.main.path(forResource: "sentry", ofType: "png") { + let imageAttachment = Attachment(pathname: path, filename: "sentry.png") + scope.addAttachment(attachment: imageAttachment) + } + } + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/sentry-samples/kmp-app/iosApp/iosApp/sentry.png b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/sentry.png similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp/sentry.png rename to sentry-samples/kmp-app-cocoapods/iosApp/iosApp/sentry.png diff --git a/sentry-samples/kmp-app/shared/build.gradle.kts b/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts similarity index 82% rename from sentry-samples/kmp-app/shared/build.gradle.kts rename to sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts index 32f9922a..3f4394e1 100644 --- a/sentry-samples/kmp-app/shared/build.gradle.kts +++ b/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts @@ -4,10 +4,14 @@ plugins { id("com.android.library") } -version = "1.0" - kotlin { - android() + android { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } jvm() iosX64() iosArm64() @@ -16,10 +20,11 @@ kotlin { cocoapods { summary = "Some description for the Shared Module" homepage = "Link to the Shared Module homepage" + version = "1.0" ios.deploymentTarget = "14.1" podfile = project.file("../iosApp/Podfile") - pod(Config.Libs.sentryCocoa, "~> ${Config.Libs.sentryCocoaVersion}") + pod("Sentry", "~> 8.4.0") framework { baseName = "shared" @@ -38,16 +43,9 @@ kotlin { implementation(kotlin("test")) } } - - val jvmMain by getting { - dependsOn(commonMain) - } - - val androidMain by getting { - dependsOn(commonMain) - } + val jvmMain by getting + val androidMain by getting val androidUnitTest by getting - val iosX64Main by getting val iosArm64Main by getting val iosSimulatorArm64Main by getting @@ -70,9 +68,10 @@ kotlin { } android { - compileSdk = Config.Android.compileSdkVersion + compileSdk = 32 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { - minSdk = Config.Android.minSdkVersion + minSdk = 27 + targetSdk = 32 } } diff --git a/sentry-samples/kmp-app/shared/shared.podspec b/sentry-samples/kmp-app-cocoapods/shared/shared.podspec similarity index 95% rename from sentry-samples/kmp-app/shared/shared.podspec rename to sentry-samples/kmp-app-cocoapods/shared/shared.podspec index 14bc67a4..1fa7c843 100644 --- a/sentry-samples/kmp-app/shared/shared.podspec +++ b/sentry-samples/kmp-app-cocoapods/shared/shared.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |spec| spec.dependency 'Sentry', '~> 8.4.0' spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => ':sentry-samples:kmp-app:shared', + 'KOTLIN_PROJECT_PATH' => ':sentry-samples:kmp-app-cocoapods:shared', 'PRODUCT_MODULE_NAME' => 'shared', } diff --git a/sentry-samples/kmp-app/shared/src/androidMain/AndroidManifest.xml b/sentry-samples/kmp-app-cocoapods/shared/src/androidMain/AndroidManifest.xml similarity index 100% rename from sentry-samples/kmp-app/shared/src/androidMain/AndroidManifest.xml rename to sentry-samples/kmp-app-cocoapods/shared/src/androidMain/AndroidManifest.xml diff --git a/sentry-samples/kmp-app/shared/src/androidMain/kotlin/sample/kmp/app/Platform.kt b/sentry-samples/kmp-app-cocoapods/shared/src/androidMain/kotlin/sample/kmp/app/Platform.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/androidMain/kotlin/sample/kmp/app/Platform.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/androidMain/kotlin/sample/kmp/app/Platform.kt diff --git a/sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt diff --git a/sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/Platform.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/Platform.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/Platform.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/Platform.kt diff --git a/sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/AppSetup.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/commonMain/kotlin/sample.kmp.app/AppSetup.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt diff --git a/sentry-samples/kmp-app/shared/src/iosMain/kotlin/sample.kmp.app/HttpClient.kt b/sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/HttpClient.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/iosMain/kotlin/sample.kmp.app/HttpClient.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/HttpClient.kt diff --git a/sentry-samples/kmp-app/shared/src/iosMain/kotlin/sample.kmp.app/Platform.kt b/sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/Platform.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/iosMain/kotlin/sample.kmp.app/Platform.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/Platform.kt diff --git a/sentry-samples/kmp-app/shared/src/jvmMain/kotlin/sample/kmp/app/Platform.kt b/sentry-samples/kmp-app-cocoapods/shared/src/jvmMain/kotlin/sample/kmp/app/Platform.kt similarity index 100% rename from sentry-samples/kmp-app/shared/src/jvmMain/kotlin/sample/kmp/app/Platform.kt rename to sentry-samples/kmp-app-cocoapods/shared/src/jvmMain/kotlin/sample/kmp/app/Platform.kt diff --git a/sentry-samples/kmp-app-mvvm-di/README.md b/sentry-samples/kmp-app-mvvm-di/README.md new file mode 100644 index 00000000..36969957 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/README.md @@ -0,0 +1,57 @@ +# Sentry KMP Demo App + +This is a demo app for the Sentry Kotlin Multiplatform SDK that includes a native iOS app and a native Android app with shared code. + +## Shared Features + - Dependency Injection with Koin + - ViewModels + - Sentry Setup + +## Getting Started + +### IDE + +Install the [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) for Android Studio. + +You can use Android Studio to run both the Android and iOS sample apps. + +The android target is available as `sentry-samples.kmp-app-mvvm-di.androidApp` automatically. +The iOS target needs to be configured: add a new run configuration and select an iOS application as a new target. +You can then select the `iosApp.xcworkspace` file for the required `Xcode project file`. + +### Android +- Export your `ANDROID_HOME` environment variable if you haven't done already. +- You can run `./gradlew :sentry-samples:kmp-app:androidApp:assembleDebug` to compile the Android app. + +### iOS + +#### Cocoapods +You need Cocoapods installed on your machine. + +Run `export LANG=en_US.UTF-8` to avoid encoding issues. + +`pod install` will automatically run through gradle if you run the iOS app through Android Studio. +However, you can still run `pod install` on the iOS folder manually if you want to make sure the pods are up to date. + +#### DSYMS +First you need to have `sentry-cli` installed. + +Then add the following script to your `Build Phases` in Xcode and change the `org`, `project`, `auth_token` slug placeholders accordingly: +Make sure to change the placeholders correctly, otherwise the iOS app will not run. + +```shell +if which sentry-cli >/dev/null; then +export SENTRY_ORG= +export SENTRY_PROJECT= +export SENTRY_AUTH_TOKEN= +ERROR=$(sentry-cli upload-dif "$DWARF_DSYM_FOLDER_PATH" 2>&1 >/dev/null) +if [ ! $? -eq 0 ]; then +echo "warning: sentry-cli - $ERROR" +fi +else +echo "warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases" +fi +``` + +### Sentry Setup +If you need to change the `DSN` or any options you can do so in the `SentrySetup.kt` file in the `shared` module. diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/build.gradle.kts b/sentry-samples/kmp-app-mvvm-di/androidApp/build.gradle.kts new file mode 100644 index 00000000..7e62c3c7 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + id("com.android.application") + kotlin("android") + id("io.sentry.android.gradle") version "3.5.0" +} + +android { + namespace = "sentry.kmp.demo.android" + compileSdk = 33 + defaultConfig { + applicationId = "sentry.kmp.demo" + minSdk = 21 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + } + buildFeatures { + compose = true + } + signingConfigs { + create("release") { + storeFile = file("sentry.keystore") + storePassword = "sentry" + keyAlias = "Sentry Android Key" + keyPassword = "sentry" + } + } + buildTypes { + getByName("release") { + isDefault = true + isMinifyEnabled = true + proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) + signingConfig = signingConfigs.getByName("release") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.0-dev-k1.8.0-33c0ad36f83" + } +} + +dependencies { + implementation(rootProject.project(":sentry-samples:kmp-app-mvvm-di:shared")) + implementation("androidx.core:core-ktx:1.10.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + implementation("androidx.activity:activity-compose:1.7.0") + implementation("androidx.lifecycle:lifecycle-runtime:2.6.1") + implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1") + implementation("androidx.navigation:navigation-compose:2.5.3") + implementation("androidx.navigation:navigation-runtime:2.5.3") + implementation("io.insert-koin:koin-android:3.4.0") + implementation("androidx.compose.compiler:compiler:1.4.0-dev-k1.8.0-33c0ad36f83") + implementation("androidx.compose.ui:ui:1.5.0-alpha02") + implementation("androidx.compose.ui:ui-tooling:1.5.0-alpha02") + implementation("androidx.compose.foundation:foundation:1.5.0-alpha02") + implementation("androidx.compose.material:material:1.5.0-alpha02") +} + +// Prevent Sentry from being included in the Android app through the AGP. +configurations { + compileOnly { + exclude(group = "io.sentry", module = "sentry") + exclude(group = "io.sentry", module = "sentry-android") + } +} + +sentry { + autoUploadProguardMapping.set(false) +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/sentry.keystore b/sentry-samples/kmp-app-mvvm-di/androidApp/sentry.keystore new file mode 100644 index 00000000..3d6ddc7c Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/sentry.keystore differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/AndroidManifest.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f6802f44 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainActivity.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainActivity.kt new file mode 100644 index 00000000..d66aaab1 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainActivity.kt @@ -0,0 +1,26 @@ +package sentry.kmp.demo.android + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.component.KoinComponent +import sentry.kmp.demo.android.theme.Theme +import sentry.kmp.demo.android.ui.MyApp +import sentry.kmp.demo.models.AuthenticationViewModel +import sentry.kmp.demo.models.HomeViewModel + +class MainActivity : ComponentActivity(), KoinComponent { + + private val authenticationViewModel: AuthenticationViewModel by viewModel() + private val homeViewModel: HomeViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Theme { + MyApp(authenticationViewModel, homeViewModel) + } + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt new file mode 100644 index 00000000..1d60baaa --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt @@ -0,0 +1,22 @@ +package sentry.kmp.demo.android + +import android.app.Application +import android.content.Context +import org.koin.dsl.module +import sentry.kmp.demo.initKoin +import sentry.kmp.demo.sentry.initSentry + +class MainApp : Application() { + + override fun onCreate() { + super.onCreate() + + initSentry(this) + + initKoin( + module { + single { this@MainApp } + } + ) + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Color.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Color.kt new file mode 100644 index 00000000..81b515ff --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Color.kt @@ -0,0 +1,8 @@ +package sentry.kmp.demo.android.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Shapes.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Shapes.kt new file mode 100644 index 00000000..ee7ee816 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Shapes.kt @@ -0,0 +1,11 @@ +package sentry.kmp.demo.android.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Theme.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Theme.kt new file mode 100644 index 00000000..a1c77b63 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Theme.kt @@ -0,0 +1,38 @@ +package sentry.kmp.demo.android.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 +) + +@Composable +fun Theme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Typography.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Typography.kt new file mode 100644 index 00000000..327d3dde --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/theme/Typography.kt @@ -0,0 +1,15 @@ +package sentry.kmp.demo.android.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) +) diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/Composables.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/Composables.kt new file mode 100644 index 00000000..810ac516 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/Composables.kt @@ -0,0 +1,25 @@ +package sentry.kmp.demo.android.ui + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import sentry.kmp.demo.models.AuthenticationViewModel +import sentry.kmp.demo.models.HomeViewModel + +@Composable +fun MyApp(authenticationViewModel: AuthenticationViewModel, homeViewModel: HomeViewModel) { + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = "login" + ) { + composable("login") { + LoginScreen(navController, authenticationViewModel) + } + composable("home") { + HomeScreen(navController, homeViewModel) + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/HomeScreen.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/HomeScreen.kt new file mode 100644 index 00000000..dd20aafb --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/HomeScreen.kt @@ -0,0 +1,83 @@ +package sentry.kmp.demo.android.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.AlertDialog +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import sentry.kmp.demo.models.HomeViewModel + +@Composable +fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { + var showDialog by remember { mutableStateOf(false) } + var dialogMessage by remember { mutableStateOf("") } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Welcome!", + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 16.dp) + ) + Text( + text = homeViewModel.homeText, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Normal, + modifier = Modifier.padding(bottom = 16.dp) + ) + Button( + onClick = { + homeViewModel.updateProfileWithErr() + dialogMessage = "An error occurred during profile update" + showDialog = true + }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) { + Text("Update Profile (error)") + } + Button( + onClick = { navController.popBackStack() }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) { + Text("Log Out") + } + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text("Error") }, + text = { Text(dialogMessage) }, + confirmButton = { + Button( + onClick = { showDialog = false } + ) { + Text("OK") + } + } + ) + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/LoginScreen.kt b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/LoginScreen.kt new file mode 100644 index 00000000..c7c668c8 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/ui/LoginScreen.kt @@ -0,0 +1,125 @@ +package sentry.kmp.demo.android.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.AlertDialog +import androidx.compose.material.Button +import androidx.compose.material.Checkbox +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import sentry.kmp.demo.models.AuthenticationViewModel + +@Composable +fun LoginScreen(navController: NavController, authenticationViewModel: AuthenticationViewModel) { + val email = remember { mutableStateOf("user@sentrydemo.com") } + val password = remember { mutableStateOf("randompassword") } + val enableLoginError = remember { mutableStateOf(true) } + var showDialog by remember { mutableStateOf(false) } + var dialogMessage by remember { mutableStateOf("") } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Sentry Demo", + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 16.dp) + ) + OutlinedTextField( + value = email.value, + onValueChange = { email.value = it }, + label = { Text("Email") }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email + ), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = password.value, + onValueChange = { password.value = it }, + label = { Text("Password") }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password + ), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) + Button( + onClick = { + val succeeded = authenticationViewModel.login(enableLoginError.value) + if (succeeded) { + navController.navigate("home") + } else { + dialogMessage = "An error occurred during login" + showDialog = true + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) { + Text("Log In ${if (enableLoginError.value) "(error)" else ""}") + } + Button( + onClick = { + authenticationViewModel.signUp() + }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) { + Text("Sign up (crash)") + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + Text("Enable login error") + Checkbox( + checked = enableLoginError.value, + onCheckedChange = { enableLoginError.value = it }, + modifier = Modifier.padding(end = 8.dp) + ) + } + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text("Error") }, + text = { Text(dialogMessage) }, + confirmButton = { + Button( + onClick = { showDialog = false } + ) { + Text("OK") + } + } + ) + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_24px.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_24px.xml new file mode 100644 index 00000000..ce351f43 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_border_24px.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_border_24px.xml new file mode 100644 index 00000000..e6646709 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_favorite_border_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_launcher_background.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..898f3ed5 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..dffca360 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..64ba76f7 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..dae5e082 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..e5ed4659 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..14ed0af3 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..b0907cac Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d8ae0315 Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..2c18de9e Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..beed3cdd Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/colors.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/colors.xml new file mode 100644 index 00000000..69b22338 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/strings.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/strings.xml new file mode 100644 index 00000000..f83b2779 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Sentry KMP Demo + diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/styles.xml b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/styles.xml new file mode 100644 index 00000000..5885930d --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.pbxproj b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..38876913 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,447 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 1DBB948028897D4700E79663 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 1DBB947F28897D4700E79663 /* Sentry */; }; + 243652ED29F34FBF00FD902A /* sentry.png in Resources */ = {isa = PBXBuildFile; fileRef = 243652EC29F34FBF00FD902A /* sentry.png */; }; + 243652F629F3516400FD902A /* Koin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243652F229F3516400FD902A /* Koin.swift */; }; + 243652F729F3516400FD902A /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243652F329F3516400FD902A /* LoginScreen.swift */; }; + 243652F829F3516400FD902A /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243652F429F3516400FD902A /* HomeScreen.swift */; }; + 243652F929F3516400FD902A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243652F529F3516400FD902A /* AppDelegate.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 7555FFB4242A642300829871 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 243652EC29F34FBF00FD902A /* sentry.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sentry.png; sourceTree = ""; }; + 243652F229F3516400FD902A /* Koin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Koin.swift; sourceTree = ""; }; + 243652F329F3516400FD902A /* LoginScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; + 243652F429F3516400FD902A /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 243652F529F3516400FD902A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 24B0FC5A29F826B300434F1C /* iosApp.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iosApp.xcconfig; sourceTree = ""; }; + 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7555FF78242A565900829871 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DBB948028897D4700E79663 /* Sentry in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 7555FFB0242A642200829871 /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 243652EC29F34FBF00FD902A /* sentry.png */, + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 243652F529F3516400FD902A /* AppDelegate.swift */, + 243652F429F3516400FD902A /* HomeScreen.swift */, + 243652F229F3516400FD902A /* Koin.swift */, + 243652F329F3516400FD902A /* LoginScreen.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + 24B0FC5A29F826B300434F1C /* iosApp.xcconfig */, + ); + path = iosApp; + sourceTree = ""; + }; + 7555FFB0242A642200829871 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + 7555FFB5242A651A00829871 /* Run Script */, + 7555FF77242A565900829871 /* Sources */, + 7555FF78242A565900829871 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + 7555FFB4242A642300829871 /* Embed Frameworks */, + 24B0FC5B29F826D700434F1C /* Upload Debug Symbols to Sentry */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + packageProductDependencies = ( + 1DBB947F28897D4700E79663 /* Sentry */, + ); + productName = NSExceptionKtSample; + productReference = 7555FF7B242A565900829871 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + packageReferences = ( + 1DBB947E28897D4700E79663 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, + ); + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 243652ED29F34FBF00FD902A /* sentry.png in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 24B0FC5B29F826D700434F1C /* Upload Debug Symbols to Sentry */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + ); + name = "Upload Debug Symbols to Sentry"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which sentry-cli >/dev/null; then\nERROR=$(sentry-cli upload-dif \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null)\nif [ ! $? -eq 0 ]; then\necho \"warning: sentry-cli - $ERROR\"\nfi\nelse\necho \"warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\n"; + }; + 7555FFB5242A651A00829871 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../../gradlew :sentry-samples:kmp-app-mvvm-di:shared:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 243652F929F3516400FD902A /* AppDelegate.swift in Sources */, + 243652F629F3516400FD902A /* Koin.swift in Sources */, + 243652F829F3516400FD902A /* HomeScreen.swift in Sources */, + 243652F729F3516400FD902A /* LoginScreen.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24B0FC5A29F826B300434F1C /* iosApp.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24B0FC5A29F826B300434F1C /* iosApp.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; + INFOPLIST_FILE = iosApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + shared, + ); + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.kotlin.multiplatform.samples; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; + INFOPLIST_FILE = iosApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + shared, + ); + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.kotlin.multiplatform.samples; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 1DBB947E28897D4700E79663 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/getsentry/sentry-cocoa.git"; + requirement = { + kind = exactVersion; + version = 8.4.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 1DBB947F28897D4700E79663 /* Sentry */ = { + isa = XCSwiftPackageProductDependency; + package = 1DBB947E28897D4700E79663 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = Sentry; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/sentry-samples/kmp-app/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from sentry-samples/kmp-app/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..11d4b194 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "sentry-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/getsentry/sentry-cocoa.git", + "state" : { + "revision" : "92a6472efc750a4e18bdee21c204942ab0bc4dcd", + "version" : "8.4.0" + } + } + ], + "version" : 2 +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme new file mode 100644 index 00000000..cfbcecae --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift new file mode 100644 index 00000000..f863295a --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/AppDelegate.swift @@ -0,0 +1,24 @@ +import SwiftUI +import shared + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions + launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + startKoin() + + SentrySetupKt.start() + + let viewController = UIHostingController(rootView: HomeScreen()) + + self.window = UIWindow(frame: UIScreen.main.bounds) + self.window?.rootViewController = viewController + self.window?.makeKeyAndVisible() + + return true + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..ee7e3ca0 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..fb88a396 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/HomeScreen.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/HomeScreen.swift new file mode 100644 index 00000000..aa71784f --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/HomeScreen.swift @@ -0,0 +1,50 @@ +import SwiftUI +import shared + +struct HomeScreen: View { + @State private var presentLoginScreen = true + @State private var showDialog = false + @State private var dialogMessage = "" + + @Environment(\.presentationMode) var presentationMode + private var viewModel = KotlinDependencies.shared.getHomeViewModel() + + var body: some View { + VStack(alignment: .center, spacing: 16) { + Text("Welcome!") + .font(.title) + .fontWeight(.bold) + .padding(.bottom, 16) + Text(viewModel.homeText) + .font(.body) + .fontWeight(.regular) + .padding(.bottom, 16) + Button(action: { + viewModel.updateProfileWithErr() + showDialog = true + dialogMessage = "An error occurred during profile update" + }) { + Text("Update Profile (error)") + } + Button(action: { presentLoginScreen = true }) { + Text("Log Out") + } + .sheet(isPresented: $presentLoginScreen) { + LoginScreen() + } + .frame(maxWidth: .infinity) + .padding(.top, 16) + } + .padding(16) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .alert(isPresented: $showDialog) { + Alert( + title: Text("Error"), + message: Text(dialogMessage), + dismissButton: .default(Text("OK")) { + showDialog = false + } + ) + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Info.plist b/sentry-samples/kmp-app-mvvm-di/iosApp/Info.plist new file mode 100644 index 00000000..b9760e8d --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + SENTRY_DSN + $(SENTRY_DSN) + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + bugsnag + + apiKey + $(BUGSNAG_API_KEY) + + + diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Koin.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/Koin.swift new file mode 100644 index 00000000..5c01cc17 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Koin.swift @@ -0,0 +1,12 @@ +import Foundation +import shared + +func startKoin() { + let koinApplication = KoinIOSKt.doInitKoinIos() + _koin = koinApplication.koin +} + +private var _koin: Koin_coreKoin? +var koin: Koin_coreKoin { + return _koin! +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/LoginScreen.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/LoginScreen.swift new file mode 100644 index 00000000..f2699fe7 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/LoginScreen.swift @@ -0,0 +1,70 @@ +import SwiftUI +import shared + +struct LoginScreen: View { + @State private var email = "user@sentrydemo.com" + @State private var password = "randompassword" + @State private var enableLoginError = true + @State private var showDialog = false + @State private var dialogMessage = "" + @State private var isLoggedIn = false + @Environment(\.presentationMode) var presentationMode + + private var viewModel = KotlinDependencies.shared.getAuthenticationViewModel() + + var body: some View { + VStack(alignment: .center, spacing: 16) { + Text("Sentry Demo") + .font(.title) + .fontWeight(.bold) + .padding(.bottom, 16) + TextField("Email", text: $email) + .keyboardType(.emailAddress) + .textFieldStyle(RoundedBorderTextFieldStyle()) + SecureField("Password", text: $password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding(.top, 16) + Button(action: { + let succeded = viewModel.login(withError: enableLoginError) + if succeded { + // present home screen + presentationMode.wrappedValue.dismiss() + } else { + dialogMessage = "An error occurred during login" + showDialog = true + } + }) { + if (enableLoginError) { + Text("Log In (error)") + } else { + Text("Log In") + } + } + .frame(maxWidth: .infinity) + .padding(.top, 16) + Button(action: { + viewModel.signUp() + }) { + Text("Sign up (crash)") + } + .frame(maxWidth: .infinity) + .padding(.top, 16) + HStack { + Text("Enable login error") + Toggle("", isOn: $enableLoginError) + .padding(.trailing, 8) + } + } + .padding(16) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .alert(isPresented: $showDialog) { + Alert( + title: Text("Error"), + message: Text(dialogMessage), + dismissButton: .default(Text("OK")) { + showDialog = false + } + ) + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/sentry.png b/sentry-samples/kmp-app-mvvm-di/iosApp/sentry.png new file mode 100644 index 00000000..8595392b Binary files /dev/null and b/sentry-samples/kmp-app-mvvm-di/iosApp/sentry.png differ diff --git a/sentry-samples/kmp-app-mvvm-di/shared/build.gradle.kts b/sentry-samples/kmp-app-mvvm-di/shared/build.gradle.kts new file mode 100644 index 00000000..f906eba3 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/build.gradle.kts @@ -0,0 +1,80 @@ +plugins { + kotlin("multiplatform") + id("com.android.library") +} + +kotlin { + android() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + baseName = "shared" + isStatic = true + export(project(":sentry-kotlin-multiplatform")) + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":sentry-kotlin-multiplatform")) + implementation("io.insert-koin:koin-core:3.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + val androidMain by getting { + dependencies { + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") + } + } + val iosX64Main by getting + val iosArm64Main by getting + val iosSimulatorArm64Main by getting + val iosMain by creating { + dependsOn(commonMain) + iosX64Main.dependsOn(this) + iosArm64Main.dependsOn(this) + iosSimulatorArm64Main.dependsOn(this) + } + val iosX64Test by getting + val iosArm64Test by getting + val iosSimulatorArm64Test by getting + val iosTest by creating { + dependsOn(commonTest) + iosX64Test.dependsOn(this) + iosArm64Test.dependsOn(this) + iosSimulatorArm64Test.dependsOn(this) + } + } +} + +android { + compileSdk = Config.Android.compileSdkVersion + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + defaultConfig { + minSdk = Config.Android.minSdkVersion + } +} + +// Workaround for KotlinMetadata tasks failing when using ./gradlew build. +// However, running this sample on iOS and Android simulators remains unaffected. +afterEvaluate { + afterEvaluate { + tasks.configureEach { + if ( + name.startsWith("compile") && + name.endsWith("KotlinMetadata") + ) { + enabled = false + } + } + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/shared.podspec b/sentry-samples/kmp-app-mvvm-di/shared/shared.podspec new file mode 100644 index 00000000..97306a57 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/shared.podspec @@ -0,0 +1,39 @@ +Pod::Spec.new do |spec| + spec.name = 'shared' + spec.version = '0.1.0' + spec.homepage = '' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = '' + spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework' + spec.libraries = 'c++' + + + + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':sentry-samples:kmp-app-mvvm-di:shared', + 'PRODUCT_MODULE_NAME' => 'shared', + } + + spec.script_phases = [ + { + :name => 'Build shared', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../../../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] + +end \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/AndroidManifest.xml b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..a723fcb0 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/KoinAndroid.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/KoinAndroid.kt new file mode 100644 index 00000000..d91c9842 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/KoinAndroid.kt @@ -0,0 +1,10 @@ +package sentry.kmp.demo + +import org.koin.dsl.module +import sentry.kmp.demo.models.AuthenticationViewModel +import sentry.kmp.demo.models.HomeViewModel + +actual val platformModule = module { + single { AuthenticationViewModel() } + single { HomeViewModel() } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/models/ViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/models/ViewModel.kt new file mode 100644 index 00000000..bbb35589 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/androidMain/kotlin/sentry/kmp/demo/models/ViewModel.kt @@ -0,0 +1,13 @@ +package sentry.kmp.demo.models + +import kotlinx.coroutines.CoroutineScope +import androidx.lifecycle.ViewModel as AndroidXViewModel +import androidx.lifecycle.viewModelScope as androidXViewModelScope + +actual abstract class ViewModel actual constructor() : AndroidXViewModel() { + actual val viewModelScope: CoroutineScope = androidXViewModelScope + + actual override fun onCleared() { + super.onCleared() + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/Koin.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/Koin.kt new file mode 100644 index 00000000..4b30583d --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/Koin.kt @@ -0,0 +1,17 @@ +package sentry.kmp.demo + +import org.koin.core.KoinApplication +import org.koin.core.context.startKoin +import org.koin.core.module.Module + +fun initKoin(appModule: Module): KoinApplication { + val koinApplication = startKoin { + modules( + appModule, + platformModule + ) + } + return koinApplication +} + +expect val platformModule: Module diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt new file mode 100644 index 00000000..b0d3f153 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt @@ -0,0 +1,35 @@ +package sentry.kmp.demo.models + +import io.sentry.kotlin.multiplatform.Sentry +import io.sentry.kotlin.multiplatform.SentryLevel +import io.sentry.kotlin.multiplatform.protocol.Breadcrumb + +class LoginException(message: String) : Exception(message) + +class AuthenticationViewModel : ViewModel() { + + fun login(withError: Boolean): Boolean { + return if (withError) { + try { + throw LoginException("Error logging in") + } catch (exception: Exception) { + Sentry.captureException(exception) { + val breadcrumb = Breadcrumb.error("Error during login").apply { + setData("touch event", "on login") + } + it.addBreadcrumb(breadcrumb) + it.setContext("Login", "Failed due to ...") + it.setTag("login", "failed") + it.level = SentryLevel.ERROR + } + false + } + } else { + true + } + } + + fun signUp() { + Sentry.crash() + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/HomeViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/HomeViewModel.kt new file mode 100644 index 00000000..712a4a9d --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/HomeViewModel.kt @@ -0,0 +1,20 @@ +package sentry.kmp.demo.models + +import io.sentry.kotlin.multiplatform.ScopeCallback +import io.sentry.kotlin.multiplatform.Sentry + +class ProfileUpdateException(message: String) : Exception(message) + +class HomeViewModel : ViewModel() { + + val homeText = + "This screen will show you how we can change the scope of each Sentry event via captureException!" + + private val scopeConfig: ScopeCallback = { + it.setContext("home", "logged in") + } + + fun updateProfileWithErr() { + Sentry.captureException(ProfileUpdateException("Error updating profile"), scopeConfig) + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/ViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/ViewModel.kt new file mode 100644 index 00000000..82b5aa83 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/ViewModel.kt @@ -0,0 +1,8 @@ +package sentry.kmp.demo.models + +import kotlinx.coroutines.CoroutineScope + +expect abstract class ViewModel() { + val viewModelScope: CoroutineScope + protected open fun onCleared() +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt new file mode 100644 index 00000000..920aa3a2 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt @@ -0,0 +1,62 @@ +package sentry.kmp.demo.sentry + +import io.sentry.kotlin.multiplatform.Attachment +import io.sentry.kotlin.multiplatform.Context +import io.sentry.kotlin.multiplatform.OptionsConfiguration +import io.sentry.kotlin.multiplatform.Sentry +import kotlin.experimental.ExperimentalObjCRefinement +import kotlin.native.HiddenFromObjC + +/** Shared options configuration */ +private val optionsConfiguration: OptionsConfiguration = { + it.dsn = "https://83f281ded2844eda83a8a413b080dbb9@o447951.ingest.sentry.io/5903800" + it.attachStackTrace = true + it.attachThreads = true + it.attachScreenshot = true + it.release = "kmp-release@0.0.1" + it.beforeSend = { event -> + if (event.environment == "test") { + null + } else { + event + } + } + it.beforeBreadcrumb = { breadcrumb -> + breadcrumb.message = "Add message before every breadcrumb" + breadcrumb + } +} + +/** + * Initializes Sentry with given options. + * Make sure to hook this into your native platforms as early as possible + */ +@OptIn(ExperimentalObjCRefinement::class) +@HiddenFromObjC +fun initSentry(context: Context) { + Sentry.init(context, optionsConfiguration) + configureSentryScope() +} + +/** + * Convenience initializer for Cocoa targets. + * Kotlin -> ObjC doesn't support default parameters (yet). + */ +fun start() { + Sentry.init(optionsConfiguration) + configureSentryScope() +} + +/** Configure scope applicable to all platforms */ +private fun configureSentryScope() { + Sentry.configureScope { + it.setContext("Custom Context", "Shared Context") + it.setTag("custom-tag", "from shared code") + it.addAttachment( + Attachment( + "This is a shared text attachment".encodeToByteArray(), + "shared.log" + ) + ) + } +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/KoinIOS.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/KoinIOS.kt new file mode 100644 index 00000000..7cd50c18 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/KoinIOS.kt @@ -0,0 +1,22 @@ +package sentry.kmp.demo + +import org.koin.core.KoinApplication +import org.koin.core.component.KoinComponent +import org.koin.dsl.module +import sentry.kmp.demo.models.AuthenticationViewModel +import sentry.kmp.demo.models.HomeViewModel + +fun initKoinIos(): KoinApplication = initKoin( + module { } +) + +actual val platformModule = module { + single { AuthenticationViewModel() } + single { HomeViewModel() } +} + +@Suppress("unused") // Called from Swift +object KotlinDependencies : KoinComponent { + fun getAuthenticationViewModel() = getKoin().get() + fun getHomeViewModel() = getKoin().get() +} diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/models/ViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/models/ViewModel.kt new file mode 100644 index 00000000..e157e877 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/iosMain/kotlin/sentry.kmp.demo/models/ViewModel.kt @@ -0,0 +1,30 @@ +package sentry.kmp.demo.models + +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + +/** + * Base class that provides a Kotlin/Native equivalent to the AndroidX `ViewModel`. In particular, this provides + * a [CoroutineScope][kotlinx.coroutines.CoroutineScope] which uses [Dispatchers.Main][kotlinx.coroutines.Dispatchers.Main] + * and can be tied into an arbitrary lifecycle by calling [clear] at the appropriate time. + */ +actual abstract class ViewModel { + + actual val viewModelScope = MainScope() + + /** + * Override this to do any cleanup immediately before the internal [CoroutineScope][kotlinx.coroutines.CoroutineScope] + * is cancelled in [clear] + */ + protected actual open fun onCleared() { + } + + /** + * Cancels the internal [CoroutineScope][kotlinx.coroutines.CoroutineScope]. After this is called, the ViewModel should + * no longer be used. + */ + fun clear() { + onCleared() + viewModelScope.cancel() + } +} diff --git a/sentry-samples/kmp-app-spm/androidApp/build.gradle.kts b/sentry-samples/kmp-app-spm/androidApp/build.gradle.kts new file mode 100644 index 00000000..3e70dcaf --- /dev/null +++ b/sentry-samples/kmp-app-spm/androidApp/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("com.android.application") + kotlin("android") + id("io.sentry.android.gradle") version "3.5.0" +} + +android { + compileSdk = Config.Android.compileSdkVersion + defaultConfig { + applicationId = "sample.kmp.app.android" + minSdk = Config.Android.minSdkVersion + targetSdk = Config.Android.targetSdkVersion + versionCode = 1 + versionName = "1.0" + } + signingConfigs { + create("release") { + storeFile = file("sentry.keystore") + storePassword = "sentry" + keyAlias = "Sentry Android Key" + keyPassword = "sentry" + } + } + buildTypes { + getByName("release") { + isDefault = true + isMinifyEnabled = true + proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) + signingConfig = signingConfigs.getByName("release") + } + } +} + +dependencies { + implementation(rootProject.project(":sentry-samples:kmp-app-spm:shared")) + implementation("com.google.android.material:material:1.6.1") + implementation("androidx.appcompat:appcompat:1.4.2") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") +} + +// Prevent Sentry from being included in the Android app through the AGP. +configurations { + compileOnly { + exclude(group = "io.sentry", module = "sentry") + exclude(group = "io.sentry", module = "sentry-android") + } +} + +sentry { + autoUploadProguardMapping.set(false) +} diff --git a/sentry-samples/kmp-app-spm/androidApp/sentry.keystore b/sentry-samples/kmp-app-spm/androidApp/sentry.keystore new file mode 100644 index 00000000..3d6ddc7c Binary files /dev/null and b/sentry-samples/kmp-app-spm/androidApp/sentry.keystore differ diff --git a/sentry-samples/kmp-app-spm/androidApp/src/main/AndroidManifest.xml b/sentry-samples/kmp-app-spm/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bdfc10f4 --- /dev/null +++ b/sentry-samples/kmp-app-spm/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt b/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt new file mode 100644 index 00000000..1b693381 --- /dev/null +++ b/sentry-samples/kmp-app-spm/androidApp/src/main/java/sample/kmp/app/android/MainActivity.kt @@ -0,0 +1,76 @@ +package sample.kmp.app.android + +import android.app.Application +import android.os.Bundle +import android.widget.Button +import androidx.appcompat.app.AppCompatActivity +import io.sentry.kotlin.multiplatform.Attachment +import io.sentry.kotlin.multiplatform.Sentry +import io.sentry.kotlin.multiplatform.protocol.Breadcrumb +import sample.kmp.app.LoginImpl +import sample.kmp.app.Platform +import sample.kmp.app.configureSentryScope +import sample.kmp.app.initializeSentry +import java.io.FileOutputStream +import java.io.IOException + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val captureMessageBtn: Button = findViewById(R.id.captureMessageBtn) + val captureExceptionBtn: Button = findViewById(R.id.captureExceptionBtn) + val captureHardCrashBtn: Button = findViewById(R.id.captureHardCrash) + + captureMessageBtn.setOnClickListener { + Sentry.captureMessage("From KMP Sample App: " + Platform().platform) + } + + captureExceptionBtn.setOnClickListener { + LoginImpl.login("MyUsername") + } + + captureHardCrashBtn.setOnClickListener { + LoginImpl.login() + } + } +} + +class SentryApplication : Application() { + override fun onCreate() { + super.onCreate() + + // Initialize Sentry using shared code + initializeSentry(this) + + // Shared scope across all platforms + configureSentryScope() + + val imageFile = applicationContext.getFileStreamPath("sentry.png") + try { + applicationContext.resources.openRawResource(R.raw.sentry).use { inputStream -> + FileOutputStream(imageFile).use { outputStream -> + val bytes = ByteArray(1024) + while (inputStream.read(bytes) !== -1) { + // To keep the sample code simple this happens on the main thread. Don't do this in a + // real app. + outputStream.write(bytes) + } + outputStream.flush() + } + } + } catch (e: IOException) { + Sentry.captureException(e) + } + + val imageAttachment = Attachment(imageFile.getAbsolutePath(), "sentry.png", "image/png") + + // Add platform specific scope in addition to the shared scope + Sentry.configureScope { + it.setContext("Android Context", mapOf("context1" to 12, "context2" to false)) + it.addBreadcrumb(Breadcrumb.debug("initialized Sentry on Android")) + it.addAttachment(imageAttachment) + } + } +} diff --git a/sentry-samples/kmp-app-spm/androidApp/src/main/res/layout/activity_main.xml b/sentry-samples/kmp-app-spm/androidApp/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..024e7343 --- /dev/null +++ b/sentry-samples/kmp-app-spm/androidApp/src/main/res/layout/activity_main.xml @@ -0,0 +1,41 @@ + + + +