Android Quick Start
Integrate Traceway into your native Android application with the com.tracewayapp:traceway (opens in a new tab) library from Maven Central.
Installation
Add the dependency to your app module's build.gradle.kts:
dependencies {
implementation("com.tracewayapp:traceway:1.0.1")
}Maven Central is enabled by default in modern Android projects, so no extra repositories { ... } entry is required.
Setup
Initialize the SDK from your Application.onCreate(). This wraps the whole process. Every uncaught exception, on every thread, is captured automatically:
import android.app.Application
import com.tracewayapp.traceway.Traceway
import com.tracewayapp.traceway.TracewayOptions
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Traceway.init(
application = this,
connectionString = "your-token@https://traceway.example.com/api/report",
options = TracewayOptions(version = "1.0.0"),
)
}
}Register the application class in AndroidManifest.xml and add the INTERNET permission so reports can leave the device:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application android:name=".MyApp" ...>
...
</application>
</manifest>Capture Errors Manually
import com.tracewayapp.traceway.Traceway
// Capture a caught exception
try {
riskyOperation()
} catch (e: Throwable) {
Traceway.captureException(e)
}
// Capture a message
Traceway.captureMessage("User completed onboarding")
// Force send pending events
Traceway.flush()With Options
TracewayOptions accepts the following parameters. Field names mirror the Flutter SDK so configuration can be ported directly.
| Option | Type | Default | Description |
|---|---|---|---|
sampleRate | Double | 1.0 | Event sampling rate (0.0 to 1.0) |
debug | Boolean | false | Print debug info to logcat |
version | String | "" | App version string |
proguardUuid | String? | null | R8/ProGuard build UUID for deobfuscating release crashes; set to BuildConfig.TRACEWAY_PROGUARD_UUID from the symbols plugin |
debounceMs | Long | 1500 | Milliseconds before sending batched events |
retryDelayMs | Long | 10000 | Retry delay in ms on failed uploads |
maxPendingExceptions | Int | 5 | Max exceptions held in memory before oldest is dropped |
persistToDisk | Boolean | true | Persist pending exceptions to disk so they survive app restarts |
maxLocalFiles | Int | 5 | Max exception files stored on disk |
localFileMaxAgeHours | Int | 12 | Hours after which unsynced local files are deleted |
captureLogs | Boolean | true | Mirror every println / System.out / System.err line into the rolling log buffer |
captureNetwork | Boolean | true | Record network events sent through TracewayOkHttpInterceptor |
captureNavigation | Boolean | true | Record activity push / pop transitions |
eventsWindowMs | Long | 10000 | Rolling window kept in the log/action buffers (ms) |
eventsMaxCount | Int | 200 | Hard cap applied independently to logs and actions |
screenCapture | Boolean | false | No effect on Android, accepted for API parity |
capturePixelRatio | Double | 0.75 | No effect on Android |
maxBufferFrames | Int | 150 | No effect on Android |
fps | Int | 15 | No effect on Android |
Traceway.init(
application = this,
connectionString = "your-token@https://traceway.example.com/api/report",
options = TracewayOptions(
sampleRate = 0.5,
debug = true,
version = "2.1.0",
debounceMs = 2000,
),
)No screen recording. Unlike the Flutter SDK, the Android library does not record video. The
screenCapture,capturePixelRatio,maxBufferFrames, andfpsoptions are accepted for API parity but have no effect.
Logs & Actions
Every captured exception ships with the last ~10 seconds of session context, attached to a sessionRecordings entry on the wire, the same shape the Flutter SDK uses (minus the video chunks). Two independent rolling buffers, logs and actions, are kept in memory, each capped at 200 entries by default.
Logs
Every println and any other write to System.out / System.err is mirrored into the log buffer. Console output behaves normally; it just gets a copy.
Calls that go straight to android.util.Log bypass System.out and are not captured automatically. To pick up Logcat lines, install a Timber tree (or any other logger) that calls TracewayClient.instance?.recordLog(message, level):
import android.util.Log
import com.tracewayapp.traceway.TracewayClient
import timber.log.Timber
Timber.plant(object : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
val level = when (priority) {
Log.VERBOSE, Log.DEBUG -> "debug"
Log.WARN -> "warn"
Log.ERROR, Log.ASSERT -> "error"
else -> "info"
}
TracewayClient.instance?.recordLog(message, level = level)
}
})Actions
Three kinds of actions are collected:
-
Network: every HTTP request routed through an OkHttp client with
TracewayOkHttpInterceptorinstalled (method, URL, status, duration, request/response byte counts). Catches Retrofit, Coil, Glide-via-OkHttp, and your own usage:val client = OkHttpClient.Builder() .addInterceptor(TracewayOkHttpInterceptor()) .build()For HTTP clients that aren't OkHttp, record events manually:
TracewayClient.instance?.recordNetworkEvent( NetworkEvent(method = "GET", url = "https://...", durationMs = 42L, statusCode = 200) ) -
Navigation: activity
push/poptransitions, recorded automatically onceTraceway.init(...)runs (the SDK registers anApplication.ActivityLifecycleCallbacksfor you). No wiring needed on your side. -
Custom: anything your app records explicitly:
Traceway.recordAction( category = "cart", name = "add_item", data = mapOf("sku" to "SKU-123", "qty" to 2), )
Wire shape
Logs and actions are sent to the backend inside the same sessionRecordings entry that the Flutter SDK uses for video. They're stored under separate logs and actions arrays. Each recording carries startedAt and endedAt ISO 8601 timestamps so the backend can align events on a timeline (offsetIntoVideoMs = event.timestamp − recording.startedAt):
{
"sessionRecordings": [
{
"exceptionId": "...",
"startedAt": "2026-04-28T14:30:53.011Z",
"endedAt": "2026-04-28T14:31:02.314Z",
"logs": [
{"type": "log", "timestamp": "2026-04-28T14:30:54.508Z", "level": "info", "message": "user tapped pay"}
],
"actions": [
{"type": "navigation", "action": "push", "from": "/", "to": "/cart", "timestamp": "2026-04-28T14:30:53.011Z"},
{"type": "network", "method": "GET", "url": "...", "statusCode": 200, "durationMs": 86, "timestamp": "2026-04-28T14:30:54.422Z"},
{"type": "custom", "category": "cart", "name": "add_item", "data": {"sku": "SKU-1"}, "timestamp": "2026-04-28T14:30:54.605Z"}
]
}
]
}startedAt and endedAt come from the timestamp range of the buffered events. A session recording entry is created for every captured exception that has any timeline data.
Disabling channels
Each channel can be turned off individually via TracewayOptions:
Traceway.init(
application = this,
connectionString = "your-token@https://traceway.example.com/api/report",
options = TracewayOptions(
captureLogs = false, // println / System.out / System.err
captureNetwork = false, // OkHttp interceptor events
captureNavigation = false, // Activity push / pop
),
)What Gets Captured Automatically
Traceway.init(...) sets up capture for the following sources with no additional configuration:
- Uncaught Java/Kotlin exceptions on every thread, via
Thread.setDefaultUncaughtExceptionHandler - View click handler throws: uncaught exceptions inside
View.OnClickListener,AlertDialogbutton handlers, and similar UI callbacks - Background thread throws: anything that escapes a
Thread'srun()body - Main
Handler.postthrows: async exceptions on the main looper, caught by the default uncaught-exception handler - Activity lifecycle transitions:
push/poprecorded as navigation actions
Deobfuscate Release Crashes
Release builds run R8, so crash traces arrive with renamed classes and rewritten line numbers. To get readable traces, apply the com.tracewayapp.symbols Gradle plugin. It injects a per-build UUID into BuildConfig.TRACEWAY_PROGUARD_UUID and uploads the build's mapping.txt to your Traceway instance. Pass the injected UUID to the SDK so each report can be matched to its mapping:
Traceway.init(
application = this,
connectionString = BuildConfig.TRACEWAY_DSN,
options = TracewayOptions(
version = "1.0.0",
proguardUuid = BuildConfig.TRACEWAY_PROGUARD_UUID,
),
)See the Android symbolicator docs for the full plugin configuration, the upload token, and the manual upload path.
Test Your Integration
Add a button that throws an exception to verify everything is wired up:
Button(this).apply {
text = "Send Test Error"
setOnClickListener {
throw RuntimeException("Test error from Traceway")
}
}Tap the button and check your Traceway dashboard to verify the error appears with its full stack trace.
Platform Support
| Platform | Error Tracking | Screen Recording |
|---|---|---|
| Android (API 21+) | Yes | No |
For Flutter apps that need cross-platform error tracking (and screen recording on iOS / Android / macOS), use the Flutter SDK instead. It ships the same wire format, so a single Traceway backend handles both.