fix: Missing required view (#1)
* misc: Renamed `app` to `sample` * misc: Renamed crashhandler to library * fix: Missing required view
0
app/.gitignore → library/.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
package com.dzeio.crashhandler
|
package com.dzeio.crashhandler
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -17,13 +18,14 @@ import kotlin.system.exitProcess
|
|||||||
* the Crash Handler class, you can get an instance by using it's [Builder]
|
* the Crash Handler class, you can get an instance by using it's [Builder]
|
||||||
*/
|
*/
|
||||||
class CrashHandler private constructor(
|
class CrashHandler private constructor(
|
||||||
private val activity: Any,
|
private val application: Application?,
|
||||||
|
private val activity: Class<*>,
|
||||||
private val prefs: SharedPreferences?,
|
private val prefs: SharedPreferences?,
|
||||||
private val prefsKey: String?,
|
private val prefsKey: String?,
|
||||||
@StringRes
|
@StringRes
|
||||||
private val errorReporterCrashKey: Int?,
|
private val errorReporterCrashKey: Int?,
|
||||||
private var prefix: String? = null,
|
private val prefix: String? = null,
|
||||||
private var suffix: String? = null
|
private val suffix: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
@ -34,10 +36,11 @@ class CrashHandler private constructor(
|
|||||||
* Builder for the crash handler
|
* Builder for the crash handler
|
||||||
*/
|
*/
|
||||||
class Builder() {
|
class Builder() {
|
||||||
|
private var application: Application? = null
|
||||||
private var prefs: SharedPreferences? = null
|
private var prefs: SharedPreferences? = null
|
||||||
private var prefsKey: String? = null
|
private var prefsKey: String? = null
|
||||||
private var errorReporterCrashKey: Int? = null
|
private var errorReporterCrashKey: Int? = null
|
||||||
private var activity: Any? = ErrorActivity::class.java
|
private var activity: Class<*>? = ErrorActivity::class.java
|
||||||
private var prefix: String? = null
|
private var prefix: String? = null
|
||||||
private var suffix: String? = null
|
private var suffix: String? = null
|
||||||
|
|
||||||
@ -48,7 +51,19 @@ class CrashHandler private constructor(
|
|||||||
*
|
*
|
||||||
* @param activity the activity class to use
|
* @param activity the activity class to use
|
||||||
*/
|
*/
|
||||||
fun withActivity(activity: Any): Builder {
|
fun withContext(context: Context): Builder {
|
||||||
|
this.application = context.applicationContext as Application?
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the Crash activity to with your own
|
||||||
|
*
|
||||||
|
* note: you can get the backtrace text by using `intent.getStringExtra("error")`
|
||||||
|
*
|
||||||
|
* @param activity the activity class to use
|
||||||
|
*/
|
||||||
|
fun withActivity(activity: Class<*>): Builder {
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -114,7 +129,24 @@ class CrashHandler private constructor(
|
|||||||
* build the Crash Handler
|
* build the Crash Handler
|
||||||
*/
|
*/
|
||||||
fun build(): CrashHandler {
|
fun build(): CrashHandler {
|
||||||
return CrashHandler(activity!!, prefs, prefsKey, errorReporterCrashKey, prefix, suffix)
|
return CrashHandler(application, activity!!, prefs, prefsKey, errorReporterCrashKey, prefix, suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var oldHandler: Thread.UncaughtExceptionHandler? = null
|
||||||
|
|
||||||
|
fun setup() {
|
||||||
|
if (application != null) {
|
||||||
|
this.setup(application)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the handler
|
||||||
|
*/
|
||||||
|
fun destroy() {
|
||||||
|
if (oldHandler != null) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(oldHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +158,7 @@ class CrashHandler private constructor(
|
|||||||
*/
|
*/
|
||||||
fun setup(application: Application) {
|
fun setup(application: Application) {
|
||||||
// Application Error Handling
|
// Application Error Handling
|
||||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
|
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
|
||||||
|
|
||||||
// Log error to logcat if it wasn't done before has it can not be logged depending on the version
|
// Log error to logcat if it wasn't done before has it can not be logged depending on the version
|
||||||
@ -199,13 +231,13 @@ class CrashHandler private constructor(
|
|||||||
|
|
||||||
data += suffix ?: ""
|
data += suffix ?: ""
|
||||||
|
|
||||||
Log.i(TAG, "Starting ${(activity as Class<*>).name}")
|
Log.i(TAG, "Starting ${activity.name}")
|
||||||
|
|
||||||
// prepare the activity
|
// prepare the activity
|
||||||
val intent = Intent(application.applicationContext, activity)
|
val intent = Intent(application, activity)
|
||||||
|
|
||||||
// add flags so that it don't use the current Application context
|
// add flags so that it don't use the current Application context
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||||
|
|
||||||
// add the Data String
|
// add the Data String
|
||||||
intent.putExtra("error", data)
|
intent.putExtra("error", data)
|
@ -5,24 +5,26 @@ import android.content.ClipboardManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.dzeio.crashhandler.databinding.ActivityErrorBinding
|
import com.dzeio.crashhandler.databinding.CrashHandlerActivityErrorBinding
|
||||||
|
|
||||||
class ErrorActivity : AppCompatActivity() {
|
class ErrorActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityErrorBinding
|
private lateinit var binding: CrashHandlerActivityErrorBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
binding = ActivityErrorBinding.inflate(layoutInflater)
|
binding = CrashHandlerActivityErrorBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val data = intent.getStringExtra("error")
|
val data = intent.getStringExtra("error")
|
||||||
|
|
||||||
// put it in the textView
|
// put it in the textView
|
||||||
binding.errorText.text = data
|
binding.errorText.apply {
|
||||||
binding.errorText.setTextIsSelectable(true)
|
text = data
|
||||||
|
setTextIsSelectable(true)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the Quit button
|
// Handle the Quit button
|
||||||
binding.errorQuit.setOnClickListener {
|
binding.errorQuit.setOnClickListener {
|
@ -2,7 +2,8 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".ui.ErrorActivity"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
@ -39,7 +39,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(project(":crashhandler"))
|
implementation(project(":library"))
|
||||||
|
|
||||||
// Material Design
|
// Material Design
|
||||||
implementation("com.google.android.material:material:1.6.1")
|
implementation("com.google.android.material:material:1.6.1")
|
@ -15,6 +15,10 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".ui.ErrorActivity"
|
||||||
|
android:theme="@style/Theme.CrashHandler"
|
||||||
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,14 +1,21 @@
|
|||||||
package com.dzeio.crashhandlertest
|
package com.dzeio.crashhandlertest
|
||||||
|
|
||||||
import com.dzeio.crashhandler.CrashHandler
|
import com.dzeio.crashhandler.CrashHandler
|
||||||
|
import com.dzeio.crashhandlertest.ui.ErrorActivity
|
||||||
|
|
||||||
class Application : android.app.Application() {
|
class Application : android.app.Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
CrashHandler.Builder()
|
CrashHandler.Builder()
|
||||||
|
.withActivity(ErrorActivity::class.java)
|
||||||
|
.withContext(this)
|
||||||
.withPrefix("Pouet :D")
|
.withPrefix("Pouet :D")
|
||||||
.withSuffix("WHYYYYY")
|
.withSuffix("WHYYYYY")
|
||||||
.build().setup(this)
|
.build().setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "CrashHandlerTest"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package com.dzeio.crashhandlertest.ui
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Process
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import com.dzeio.crashhandlertest.Application
|
||||||
|
import com.dzeio.crashhandlertest.databinding.ActivityErrorBinding
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class ErrorActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "${Application.TAG}/ErrorActivity"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityErrorBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityErrorBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
val data = intent.getStringExtra("error")
|
||||||
|
|
||||||
|
// Get Application datas
|
||||||
|
val deviceToReport = if (Build.DEVICE.contains(Build.MANUFACTURER)) Build.DEVICE else "${Build.MANUFACTURER} ${Build.DEVICE}"
|
||||||
|
|
||||||
|
val reportText = """
|
||||||
|
Crash Report (Thread: ${intent?.getLongExtra("threadId", -1) ?: "unknown"})
|
||||||
|
on $deviceToReport (${Build.MODEL}) running Android ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})
|
||||||
|
|
||||||
|
backtrace:
|
||||||
|
|
||||||
|
""".trimIndent() + data
|
||||||
|
|
||||||
|
// put it in the textView
|
||||||
|
binding.errorText.text = reportText
|
||||||
|
|
||||||
|
// Handle the Quit button
|
||||||
|
binding.errorQuit.setOnClickListener {
|
||||||
|
Process.killProcess(Process.myPid())
|
||||||
|
exitProcess(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the Email Button
|
||||||
|
binding.errorSubmitEmail.setOnClickListener {
|
||||||
|
|
||||||
|
// Create Intent
|
||||||
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
|
intent.data = Uri.parse("mailto:")
|
||||||
|
intent.type = "text/plain"
|
||||||
|
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("report.openhealth@dzeio.com"))
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, "Error report for application crash")
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, "Send Report Email\n$reportText")
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(Intent.createChooser(intent, "Send Report Email..."))
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(this, "Not Email client found :(", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the GitHub Button
|
||||||
|
binding.errorSubmitGithub.setOnClickListener {
|
||||||
|
|
||||||
|
// Build URL
|
||||||
|
val url = "https://github.com/dzeiocom/OpenHealth/issues/new?title=Application Error&body=$reportText"
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(
|
||||||
|
Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||||
|
)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(this, "No Web Browser found :(", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
sample/src/main/res/layout/activity_error.xml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:id="@+id/linearLayout2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView3"
|
||||||
|
style="?textAppearanceHeadline5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="blablabla"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView4"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="blablabla2"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView3" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/error_submit_email"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView4">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/error_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
style="?textAppearanceCaption"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/error_submit_github"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Github"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@+id/error_submit_email"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/error_submit_email"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="E-Mail"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/error_quit"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/error_quit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="Quit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
@ -15,5 +15,5 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
rootProject.name = "Crash Handler"
|
rootProject.name = "Crash Handler"
|
||||||
|
|
||||||
include ':app'
|
include ':sample'
|
||||||
include ':crashhandler'
|
include ':library'
|
||||||
|