diff --git a/examples/ExampleApp/.gitignore b/examples/ExampleApp/.gitignore new file mode 100644 index 00000000..e510fa99 --- /dev/null +++ b/examples/ExampleApp/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +.idea +.DS_Store +build +captures +.externalNativeBuild +.cxx +local.properties +xcuserdata \ No newline at end of file diff --git a/examples/ExampleApp/README.md b/examples/ExampleApp/README.md new file mode 100644 index 00000000..ee21d5e3 --- /dev/null +++ b/examples/ExampleApp/README.md @@ -0,0 +1,93 @@ +# Bugsnag Kotlin Multiplatform - ExampleApp + +This repository demonstrates how to integrate [Bugsnag KMP](https://github.com/bugsnag/bugsnag-kotlin-multiplatform) into a Kotlin Multiplatform (KMP) application across **Android**, **iOS**, and **JavaScript Web** platforms. + +## Project Structure +```plaintext +examples/ExampleApp/ +├── androidApp/ # Android app with Jetpack Compose UI +├── iosExampleApp/ # iOS app with SwiftUI +├── webApp/ # Web app using Kotlin/JS and HTML +├── shared/ # Shared KMP module with Bugsnag integration +└── build.gradle.kts # Root build configuration +``` +--- + +## Android App + +### Build & Run + +```bash +cd examples/ExampleApp +./gradlew clean build +Open in Android Studio +Set your Bugsnag API Key at `androidApp/src/main/java/com/example/exampleapps/android/MainActivity.kt` +``` + +### Features +Notify Bugsnag with a manual error + +Trigger a fatal crash + +Set custom metadata + +Leave a breadcrumb + +Simulate an ANR + +## iOS App + +### Build & Run + +```bash +open iosExampleApp/iosExampleApp.xcworkspace +Select an ios sim or device +Set your Bugsnag API Key at `iosApp/iOSApp.swift` +Run the app +``` + +### Features + +Manual Bugsnag notification + +Use shared KMP code + +Trigger crash via button + +## Web App + +### Build & Run + +```bash +cd examples/ExampleApp +./gradlew :webApp:jsBrowserDevelopmentRun +open http://localhost:8080 +``` + +### Features +Display UI with HTML/JS + +Buttons call shared Bugsnag methods: + +Manual notify + +Trigger crash + +Set metadata + +Leave breadcrumb + +## Shared KMP Module +The shared module contains the Bugsnag integration code used across all platforms. It includes: +- Bugsnag initialization +- Manual error reporting +- Custom metadata handling +- Breadcrumbs management + +## Troubleshooting + +Can't find shared module in iOS project? +``` bash +run ./gradlew syncFramework +link the generated framework in Xcode +``` \ No newline at end of file diff --git a/examples/ExampleApp/androidApp/build.gradle.kts b/examples/ExampleApp/androidApp/build.gradle.kts new file mode 100644 index 00000000..f1c73557 --- /dev/null +++ b/examples/ExampleApp/androidApp/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.compose.compiler) +} + +android { + namespace = "com.example.bugsnag.kmp.android" + compileSdk = 35 + defaultConfig { + applicationId = "com.example.bugsnag.kmp.android" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + buildFeatures { + compose = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.shared) + implementation(libs.compose.ui) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.androidx.activity.compose) + debugImplementation(libs.compose.ui.tooling) + implementation(project(":shared")) + implementation(libs.bugsnag.kmp) +} \ No newline at end of file diff --git a/examples/ExampleApp/androidApp/src/main/AndroidManifest.xml b/examples/ExampleApp/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..845096d7 --- /dev/null +++ b/examples/ExampleApp/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MainActivity.kt b/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MainActivity.kt new file mode 100644 index 00000000..5da070c0 --- /dev/null +++ b/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MainActivity.kt @@ -0,0 +1,87 @@ +package com.example.bugsnag.kmp.android + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.bugsnag.kmp.Configuration +import com.example.ExampleApps.SharedBugsnag + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val config = Configuration(applicationContext) + SharedBugsnag().startBugsnag(config) + + setContent { + MyApplicationTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + MainScreen() + } + } + } + } +} + +@Composable +fun MainScreen() { + val bugsnag = SharedBugsnag() + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text("Bugsnag KMP - Android example app") + + Button( + onClick = { bugsnag.manualNotify("Manual notify from Android") }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Notify Bugsnag") + } + + Button( + onClick = { bugsnag.triggerFatalCrash("Crash from Android button") }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Trigger Fatal Crash") + } + + Button( + onClick = { bugsnag.setMetadata("device", "sentFromAndroid") }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Set Metadata") + } + + Button( + onClick = { bugsnag.leaveBreadcrumbs("User tapped breadcrumb button") }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Leave Breadcrumb") + } + + Button( + onClick = { + // Manual ANR trigger: keep platform-specific code where needed + android.os.Handler(android.os.Looper.getMainLooper()).post { + Thread.sleep(10_000) + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Trigger ANR") + } + } +} diff --git a/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MyApplicationTheme.kt b/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MyApplicationTheme.kt new file mode 100644 index 00000000..abe1350d --- /dev/null +++ b/examples/ExampleApp/androidApp/src/main/java/com/example/exampleapps/android/MyApplicationTheme.kt @@ -0,0 +1,55 @@ +package com.example.bugsnag.kmp.android + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.Typography +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +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.dp +import androidx.compose.ui.unit.sp + +@Composable +fun MyApplicationTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + darkColorScheme( + primary = Color(0xFFBB86FC), + secondary = Color(0xFF03DAC5), + tertiary = Color(0xFF3700B3) + ) + } else { + lightColorScheme( + primary = Color(0xFF6200EE), + secondary = Color(0xFF03DAC5), + tertiary = Color(0xFF3700B3) + ) + } + val typography = Typography( + bodyMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + ) + val shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) + ) + + MaterialTheme( + colorScheme = colors, + typography = typography, + shapes = shapes, + content = content + ) +} diff --git a/examples/ExampleApp/androidApp/src/main/res/values/styles.xml b/examples/ExampleApp/androidApp/src/main/res/values/styles.xml new file mode 100644 index 00000000..6b4fa3d0 --- /dev/null +++ b/examples/ExampleApp/androidApp/src/main/res/values/styles.xml @@ -0,0 +1,3 @@ + +