I found this useful when debugging crashes in my app. It doesn't rely on any existing context or intent; it sets itself up from scratch.
In android.manifest, add an ErrorReportActivity:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="ch.calvert.gauges.ErrorReportActivity"
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
Create ErrorHandler.kt:
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import java.io.PrintWriter
import java.io.StringWriter
private var oldHandler: Thread.UncaughtExceptionHandler? = null
class MyUncaughtExceptionHandler(context: Context) : Thread.UncaughtExceptionHandler {
private val context: Context
private val defaultHandler: Thread.UncaughtExceptionHandler?
init {
this.context = context.applicationContext
defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
}
override fun uncaughtException(thread: Thread, exception: Throwable) {
try {
val sw = StringWriter()
exception.printStackTrace(PrintWriter(sw))
val intent = Intent(context, ErrorReportActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra("error_message", "Woops. An unexpected error occurred")
intent.putExtra("stack_trace", sw.toString())
// Start the new activity directly without using startActivity()
if (context is Activity) {
context.startActivity(intent)
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
System.exit(1)
}
}
}
class ErrorReportActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleUncaughtException()
}
private fun handleUncaughtException() {
val error: String = intent.getStringExtra("error_message")!!
val stackTrace:String = intent.getStringExtra("stack_trace")!!
showDialogWithStackTrace(error, stackTrace)
}
private fun showDialogWithStackTrace(error: String, stackTrace: String) {
val dialog = Dialog(this)
dialog.setCancelable(true)
val dialogLayout = LinearLayout(this)
dialogLayout.orientation = LinearLayout.VERTICAL
dialogLayout.setBackgroundColor(Color.WHITE)
dialog.setContentView(dialogLayout)
val errorTextView = createTextView(error,10f,Color.RED)
val stackTraceTextView = createTextView(stackTrace, 8f, Color.BLACK)
dialogLayout.addView(errorTextView)
dialogLayout.addView(stackTraceTextView)
dialog.show()
}
private fun createTextView(text: String, textSize: Float, textColor: Int): TextView {
val textView = TextView(this)
textView.text = text
textView.textSize = textSize
textView.setTextColor(textColor)
textView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
return textView
}
}
In your main activity, instantiate the handler:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.setDefaultUncaughtExceptionHandler(MyUncaughtExceptionHandler(applicationContext));
setContentView(R.layout.activity_main)
}
Way later in your application:
override fun onDraw(canvas: Canvas) {
throw Exception("SNAFU")
...
and you'll see this:

Please spare me the lecture that this is sacrilege. Some might find it useful and you're welcome to ignore it.
Oh, and to add to my sins, ChatGPT was a great help >;-).