fix: Missing required view (#1)

* misc: Renamed `app` to `sample`

* misc: Renamed crashhandler to library

* fix: Missing required view
This commit is contained in:
2022-08-09 18:09:30 +02:00
committed by GitHub
parent 7dfb454e72
commit ef361a4eaa
33 changed files with 233 additions and 21 deletions

1
library/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

81
library/build.gradle.kts Normal file
View File

@ -0,0 +1,81 @@
plugins {
id("com.android.library")
`maven-publish`
kotlin("android")
}
val artifact = "crashhandler"
group = "com.dzeio"
val projectVersion = project.findProperty("version") as String? ?: "1.0.0"
version = projectVersion
publishing {
publications {
register<MavenPublication>("release") {
groupId = group as String?
artifactId = artifact
version = projectVersion
afterEvaluate {
from(components["release"])
}
}
}
}
android {
namespace = "com.dzeio.crashhandler"
compileSdk = 33
buildToolsVersion = "33.0.0"
defaultConfig {
minSdk = 21
targetSdk = 33
aarMetadata {
minCompileSdk = 21
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
testFixtures {
enable = true
}
publishing {
singleVariant("release") {
withSourcesJar()
withJavadocJar()
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
// Enable View Binding and Data Binding
buildFeatures {
viewBinding = true
}
}
dependencies {
// Necessary for the Activity (well to make it pretty :D)
implementation("com.google.android.material:material:1.6.1")
}

View File

21
library/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Add activity to main manifest -->
<activity android:name=".ui.ErrorActivity"
android:theme="@style/Theme.Material3.DynamicColors.DayNight"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,256 @@
package com.dzeio.crashhandler
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Process
import android.util.Log
import android.widget.Toast
import androidx.annotation.StringRes
import com.dzeio.crashhandler.CrashHandler.Builder
import com.dzeio.crashhandler.ui.ErrorActivity
import java.util.Date
import kotlin.system.exitProcess
/**
* the Crash Handler class, you can get an instance by using it's [Builder]
*/
class CrashHandler private constructor(
private val application: Application?,
private val activity: Class<*>,
private val prefs: SharedPreferences?,
private val prefsKey: String?,
@StringRes
private val errorReporterCrashKey: Int?,
private val prefix: String? = null,
private val suffix: String? = null
) {
private companion object {
private const val TAG = "CrashHandler"
}
/**
* Builder for the crash handler
*/
class Builder() {
private var application: Application? = null
private var prefs: SharedPreferences? = null
private var prefsKey: String? = null
private var errorReporterCrashKey: Int? = null
private var activity: Class<*>? = ErrorActivity::class.java
private var prefix: String? = null
private var suffix: String? = null
/**
* 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 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
return this
}
/**
* [SharedPreferences] of your app to be able to handle ErrorActivity crashes
*
* note: you also need to use [withPrefsKey]
*
* @param prefs instance of [SharedPreferences] to use
*/
fun withPrefs(prefs: SharedPreferences?): Builder {
this.prefs = prefs
return this
}
/**
* the key of the [SharedPreferences] you want to let the library handle
*
* note: you also need to use [withPrefs]
*
* @param prefsKey the key to use
*/
fun withPrefsKey(prefsKey: String?): Builder {
this.prefsKey = prefsKey
return this
}
/**
* the resource key to use for the [Toast] if ErrorActivity crashed
*
* @param errorReporterCrashKey the string key to use
*/
fun witheErrorReporterCrashKey(@StringRes errorReporterCrashKey: Int): Builder {
this.errorReporterCrashKey = errorReporterCrashKey
return this
}
/**
* text to add after the "Crash report:" text and before the rest
*
* ex: "${BuildConfig.APPLICATION_ID} v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
*
* @param prefix the text you add
*/
fun withPrefix(prefix: String): Builder {
this.prefix = prefix
return this
}
/**
* text to add after the content generated by the handler
*
* @param suffix the text
*/
fun withSuffix(suffix: String): Builder {
this.suffix = suffix
return this
}
/**
* build the Crash Handler
*/
fun build(): CrashHandler {
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)
}
}
/**
* Setup the crash handler, after this method is executed crashes should be handled through your
* activity
*
* @param application the application instance to make sure everything is setup right
*/
fun setup(application: Application) {
// Application Error Handling
oldHandler = Thread.getDefaultUncaughtExceptionHandler()
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.e(TAG, "En error was detected", paramThrowable)
// mostly unusable data but also log Thread Stacktrace
Log.i(TAG, "Thread StackTrace:")
for (item in paramThread.stackTrace) {
Log.i(TAG, item.toString())
}
// get current time an date
val now = Date().time
// prepare to build debug string
var data = "Crash report:\n\n"
data += prefix ?: ""
// add device informations
val deviceToReport =
if (Build.DEVICE.contains(Build.MANUFACTURER)) Build.DEVICE else "${Build.MANUFACTURER} ${Build.DEVICE}"
data += "\n\non $deviceToReport (${Build.MODEL}) running Android ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})"
// add the current time to it
data += "\n\nCrash happened at ${Date(now)}"
// if lib as access to the preferences store
if (prefs != null && prefsKey != null) {
// get the last Crash
val lastCrash = prefs.getLong(prefsKey, 0L)
// then add it to the logs :D
data += "\nLast crash happened at ${Date(lastCrash)}"
// if a crash already happened just before it means the Error Activity crashed lul
if (lastCrash >= now - 1000) {
// log it :D
Log.e(
TAG,
"Seems like the ErrorActivity also crashed, letting the OS handle it"
)
// try to send a toast indicating it
Toast.makeText(
application,
errorReporterCrashKey ?: R.string.error_reporter_crash,
Toast.LENGTH_LONG
).show()
// Use the default exception handler
oldHandler?.uncaughtException(paramThread, paramThrowable)
return@setDefaultUncaughtExceptionHandler
}
// update the store
prefs.edit().putLong(prefsKey, now).apply()
}
Log.i(TAG, "Collecting Error")
// get Thread name and ID
data += "\n\nHappened on Thread \"${paramThread.name}\" (${paramThread.id})"
// print exception backtrace
data += "\n\nException:\n${paramThrowable.stackTraceToString()}\n\n"
data += suffix ?: ""
Log.i(TAG, "Starting ${activity.name}")
// prepare the activity
val intent = Intent(application, activity)
// add flags so that it don't use the current Application context
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
intent.putExtra("error", data)
// Start the activity
application.startActivity(intent)
Log.i(TAG, "Activity should have started")
// Kill self
Process.killProcess(Process.myPid())
exitProcess(10)
}
}
}

View File

@ -0,0 +1,43 @@
package com.dzeio.crashhandler.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.dzeio.crashhandler.databinding.CrashHandlerActivityErrorBinding
class ErrorActivity : AppCompatActivity() {
private lateinit var binding: CrashHandlerActivityErrorBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CrashHandlerActivityErrorBinding.inflate(layoutInflater)
setContentView(binding.root)
val data = intent.getStringExtra("error")
// put it in the textView
binding.errorText.apply {
text = data
setTextIsSelectable(true)
}
// Handle the Quit button
binding.errorQuit.setOnClickListener {
finishAffinity()
}
// handle copy to clipboard button
binding.copyToClipboard.setOnClickListener {
val clipboard =
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = ClipData.newPlainText("error backtrace", data)
clipboard.setPrimaryClip(clip)
}
}
}

View File

@ -0,0 +1,77 @@
<?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"
tools:context=".ui.ErrorActivity"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
style="?textAppearanceHeadline5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/error_app_crash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/please_report"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="@string/error_app_report"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/copy_to_clipboard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/please_report">
<TextView
android:id="@+id/error_text"
style="?textAppearanceCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<Button
android:id="@+id/copy_to_clipboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:text="@string/copy_to_clipboard"
app:layout_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="@+id/error_quit"
app:layout_constraintStart_toStartOf="@+id/error_quit" />
<Button
android:id="@+id/error_quit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/quit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error Activity Translations -->
<string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string>
<string name="error_app_report">Merci d\'envoyer le rapport d\'erreur afin que nous puissions améliorer votre experience</string>
<string name="copy_to_clipboard">Copier dans le presse papier</string>
<string name="quit">Quitter</string>
<string name="error_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</string>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error Activity Translations -->
<string name="error_app_crash">An Error Occurred in the application forcing it to close down</string>
<string name="error_app_report">Please report it so we can enhance you\'re Android OpenHealth experience</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="quit">Quit</string>
<string name="error_reporter_crash">An error occurred while making the error report</string>
</resources>