diff --git a/Makefile b/Makefile index 81cf1e51..81c7860f 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,9 @@ compile: ./gradlew build sudo xcode-select --switch /Applications/Xcode.app && /usr/bin/xcodebuild -version cd ./sentry-samples/kmp-app/iosApp; pod install + cd ./sentry-samples/kmp-app-mvvm-di/iosApp; pod install xcodebuild -workspace ./sentry-samples/kmp-app/iosApp/iosApp.xcworkspace -scheme iosApp -configuration Debug -sdk iphonesimulator -arch arm64 + xcodebuild -workspace ./sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace -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/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..88c03a11 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + id("com.android.application") + kotlin("android") +} + +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 + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + 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.2.0") + implementation("io.insert-koin:koin-core:3.2.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") +} diff --git a/sentry-samples/kmp-app-mvvm-di/androidApp/proguard-rules.pro b/sentry-samples/kmp-app-mvvm-di/androidApp/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile 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..719a6e06 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/androidApp/src/main/kotlin/sentry/kmp/demo/android/MainApp.kt @@ -0,0 +1,26 @@ +package sentry.kmp.demo.android + +import android.app.Application +import android.content.Context +import android.util.Log +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 } + single { + { Log.i("Startup", "Hello from Android/Kotlin!") } + } + } + ) + } +} 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/Podfile b/sentry-samples/kmp-app-mvvm-di/iosApp/Podfile new file mode 100644 index 00000000..aff9c517 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Podfile @@ -0,0 +1,5 @@ +target 'iosApp' do + use_frameworks! + platform :ios, '14.1' + pod 'shared', :path => '../shared' +end \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/Podfile.lock b/sentry-samples/kmp-app-mvvm-di/iosApp/Podfile.lock new file mode 100644 index 00000000..b65af76a --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/Podfile.lock @@ -0,0 +1,30 @@ +PODS: + - Sentry (8.2.0): + - Sentry/Core (= 8.2.0) + - SentryPrivate (= 8.2.0) + - Sentry/Core (8.2.0): + - SentryPrivate (= 8.2.0) + - SentryPrivate (8.2.0) + - shared (1.0): + - Sentry (~> 8.2.0) + +DEPENDENCIES: + - shared (from `../shared`) + +SPEC REPOS: + trunk: + - Sentry + - SentryPrivate + +EXTERNAL SOURCES: + shared: + :path: "../shared" + +SPEC CHECKSUMS: + Sentry: cf1d35c866266da58964fe7b62526bda93ffcb38 + SentryPrivate: 2909bcc7b19a827b49e9bde0e56116b08d40dfdf + shared: 2d28ef1f501f48e94cfddbaa9dfff0435cc81b97 + +PODFILE CHECKSUM: f282da88f39e69507b0a255187c8a6b644477756 + +COCOAPODS: 1.12.0 diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcodeproj/project.pbxproj b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2d95045f --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,426 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + 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 */; }; + 244CD82229E0579200F8451F /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244CD81E29E0579200F8451F /* LoginScreen.swift */; }; + 244CD82329E0579200F8451F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244CD81F29E0579200F8451F /* AppDelegate.swift */; }; + 244CD82429E0579200F8451F /* Koin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244CD82029E0579200F8451F /* Koin.swift */; }; + 244CD82529E0579200F8451F /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 244CD82129E0579200F8451F /* HomeScreen.swift */; }; + FFC0DCE8EFC8D0EEA1D14C99 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8242FA7EEBC7E8A55743811E /* 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 = ""; }; + 244CD81E29E0579200F8451F /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; + 244CD81F29E0579200F8451F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 244CD82029E0579200F8451F /* Koin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Koin.swift; sourceTree = ""; }; + 244CD82129E0579200F8451F /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 45A0F05C445532171C74E8C3 /* 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; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8242FA7EEBC7E8A55743811E /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D39B5469A872DAA2059A5A5D /* 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 */ + DAA6816987C1F96EB698F1CE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FFC0DCE8EFC8D0EEA1D14C99 /* Pods_iosApp.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 0D5D06B1E380733306423282 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8242FA7EEBC7E8A55743811E /* Pods_iosApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 17C2E505E0856C78D3F3AE93 /* Pods */ = { + isa = PBXGroup; + children = ( + D39B5469A872DAA2059A5A5D /* Pods-iosApp.debug.xcconfig */, + 45A0F05C445532171C74E8C3 /* Pods-iosApp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 17C2E505E0856C78D3F3AE93 /* Pods */, + 0D5D06B1E380733306423282 /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 244CD81F29E0579200F8451F /* AppDelegate.swift */, + 244CD82129E0579200F8451F /* HomeScreen.swift */, + 244CD82029E0579200F8451F /* Koin.swift */, + 244CD81E29E0579200F8451F /* LoginScreen.swift */, + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF8C242A565B00829871 /* Info.plist */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + 4E7E0840DFEC454A512192BD /* [CP] Check Pods Manifest.lock */, + 7555FF77242A565900829871 /* Sources */, + 7555FF79242A565900829871 /* Resources */, + DAA6816987C1F96EB698F1CE /* Frameworks */, + 80B824D75AE06EF6630365EC /* [CP] Embed Pods Frameworks */, + 244CD82729E54BA500F8451F /* Sentry dsyms upload */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + 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; + 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 */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 244CD82729E54BA500F8451F /* Sentry dsyms upload */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + ); + name = "Sentry dsyms upload"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which sentry-cli >/dev/null; then\nexport SENTRY_ORG=\nexport SENTRY_PROJECT=\nexport SENTRY_AUTH_TOKEN=\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"; + }; + 4E7E0840DFEC454A512192BD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + 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"; + showEnvVarsInLog = 0; + }; + 80B824D75AE06EF6630365EC /* [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 */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 244CD82229E0579200F8451F /* LoginScreen.swift in Sources */, + 244CD82329E0579200F8451F /* AppDelegate.swift in Sources */, + 244CD82429E0579200F8451F /* Koin.swift in Sources */, + 244CD82529E0579200F8451F /* HomeScreen.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + 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; + 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; + baseConfigurationReference = D39B5469A872DAA2059A5A5D /* Pods-iosApp.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iosApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 45A0F05C445532171C74E8C3 /* Pods-iosApp.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = iosApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; + 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 */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..c009e7d7 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/AppDelegate.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/AppDelegate.swift new file mode 100644 index 00000000..f863295a --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..ee7e3ca0 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..fb88a396 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/Assets.xcassets/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/HomeScreen.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/HomeScreen.swift new file mode 100644 index 00000000..aa71784f --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/Info.plist b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..8044709c --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Info.plist @@ -0,0 +1,48 @@ + + + + + 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 + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UILaunchScreen + + + \ No newline at end of file diff --git a/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Koin.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/Koin.swift new file mode 100644 index 00000000..5c01cc17 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/LoginScreen.swift b/sentry-samples/kmp-app-mvvm-di/iosApp/iosApp/LoginScreen.swift new file mode 100644 index 00000000..f2699fe7 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/iosApp/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/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/sentry-samples/kmp-app-mvvm-di/iosApp/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/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/shared/build.gradle.kts b/sentry-samples/kmp-app-mvvm-di/shared/build.gradle.kts new file mode 100644 index 00000000..fe18820d --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + kotlin("multiplatform") + kotlin("native.cocoapods") + id("com.android.library") +} + +kotlin { + android() + iosX64() + iosArm64() + iosSimulatorArm64() + + 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("Sentry", "~> 8.2.0") + + framework { + baseName = "shared" + export(project(":sentry-kotlin-multiplatform")) + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":sentry-kotlin-multiplatform")) + implementation("io.insert-koin:koin-core:3.2.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 androidUnitTest by getting + 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 = 31 + defaultConfig { + minSdk = 21 + targetSdk = 31 + } + namespace = "sentry.kmp.demo" +} 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..4079bc34 --- /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 = '1.0' + spec.homepage = 'Link to the Shared Module homepage' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = 'Some description for the Shared Module' + spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework' + spec.libraries = 'c++' + spec.ios.deployment_target = '14.1' + spec.dependency 'Sentry', '~> 8.2.0' + + 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/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..f8aabda1 --- /dev/null +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt @@ -0,0 +1,55 @@ +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.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/androidApp/build.gradle.kts b/sentry-samples/kmp-app/androidApp/build.gradle.kts index a1081d5d..9ea95fc7 100644 --- a/sentry-samples/kmp-app/androidApp/build.gradle.kts +++ b/sentry-samples/kmp-app/androidApp/build.gradle.kts @@ -6,7 +6,7 @@ plugins { android { compileSdk = Config.Android.compileSdkVersion defaultConfig { - applicationId = "sample.kpm_app.android" + applicationId = "sample.kmp.app.android" minSdk = Config.Android.minSdkVersion targetSdk = Config.Android.targetSdkVersion versionCode = 1 diff --git a/sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml b/sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml index bdfc10f4..7a818feb 100644 --- a/sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml +++ b/sentry-samples/kmp-app/androidApp/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="sample.kmp.app.android"> &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"; + }; 3620F85FA2EFF718C6F7366B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/settings.gradle.kts b/settings.gradle.kts index 05b5c78d..24cf6c07 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,7 @@ pluginManagement { gradlePluginPortal() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/") } } @@ -12,6 +13,8 @@ dependencyResolutionManagement { mavenCentral() mavenLocal() google() + maven("https://androidx.dev/storage/compose-compiler/repository/") + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/") } } @@ -28,3 +31,14 @@ KMP App with targets: include("sentry-samples:kmp-app:shared") include("sentry-samples:kmp-app:androidApp") include("sentry-samples:kmp-app:desktopApp") + +/* +KMP App with targets: + - Android with Jetpack Compose + - iOS with SwiftUI + - Dependency Injection with Koin + - MVVM Architecture + */ +include("sentry-samples:kmp-app-mvvm-di:iosApp") +include("sentry-samples:kmp-app-mvvm-di:androidApp") +include("sentry-samples:kmp-app-mvvm-di:shared")