Skip to content

Commit

Permalink
Merge pull request #23 from Q42/crash-logging
Browse files Browse the repository at this point in the history
ADD Crashlytics
  • Loading branch information
ninovanhooff authored Apr 5, 2024
2 parents 0776a26 + 191ba61 commit 766c705
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 1 deletion.
15 changes: 15 additions & 0 deletions .idea/git_toolbox_prj.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Using Bitrise instead? Then you can delete:
1. Run `python ./scripts/rename-project.py` or `python3 ./scripts/rename-project.py` from the
project root, to change the project name and the package names. The script will ask for your new
project name and update all references.
1. Replace google-services.json with your own Firebase config file.
The template Firebase project can be found here: [Firebase project](https://console.firebase.google.com/u/0/project/template-android-799ab/overview)
Google Services currently in use:
- Crashlytics

## Contributing

Expand Down Expand Up @@ -238,6 +242,12 @@ It also has multiplatform support, so we can use it in our KMP projects as well.

We use Napier because it's usage is close to Timber/Tolbaaken, but Napier supports KMM.

We use Crashlytics for crash reporting. Note that Google Analytics is not added. Google [recommends]
(https://firebase.google.com/docs/crashlytics/get-started?platform=android#before-you-begin)
to enable it for more insight such as breadcrumbs and crash-free percentage.
If you don't want to use Google Analytics, you can remove it by simply removing the dependency.


### Image loading

We did not include an image loading library is this template, because not every app might need it,
Expand Down
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
plugins {
id "com.android.application"
alias libs.plugins.googleServices
alias libs.plugins.firebaseCrashlyticsPlugin
}

apply from: "$rootDir/build.module.feature-and-app.gradle"
Expand Down Expand Up @@ -63,4 +65,7 @@ dependencies {
implementation(project(":data:user")) // needed for di
implementation(project(":core:network")) // needed for di
implementation(libs.composeDestinations)

api platform(libs.firebaseBoM)
implementation(libs.firebaseCrashlytics)
}
48 changes: 48 additions & 0 deletions app/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"project_info": {
"project_number": "1076113435843",
"project_id": "template-android-799ab",
"storage_bucket": "template-android-799ab.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1076113435843:android:e2916720b9c98936dc8676",
"android_client_info": {
"package_name": "nl.q42.template"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDzfsJ_ycKoyci--w_M2hHwvsjk8-zKmzA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1076113435843:android:72065788817f0133dc8676",
"android_client_info": {
"package_name": "nl.q42.template.dev"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDzfsJ_ycKoyci--w_M2hHwvsjk8-zKmzA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
6 changes: 6 additions & 0 deletions app/src/main/kotlin/nl/q42/template/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package nl.q42.template

import android.app.Application
import android.os.StrictMode
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.HiltAndroidApp
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import nl.q42.template.logging.CrashlyticsLogger

@HiltAndroidApp
class MainApplication : Application() {
Expand All @@ -13,6 +15,7 @@ class MainApplication : Application() {
super.onCreate()

if (BuildConfig.DEBUG) {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
Napier.base(DebugAntilog())

StrictMode.setThreadPolicy(
Expand All @@ -23,6 +26,9 @@ class MainApplication : Application() {
.penaltyLog()
.build()
)
} else {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
Napier.base(CrashlyticsLogger())
}
}
}
49 changes: 49 additions & 0 deletions app/src/main/kotlin/nl/q42/template/logging/CrashlyticsLogger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package nl.q42.template.logging

import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import io.github.aakira.napier.Antilog
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.LogLevel
import nl.q42.template.BuildConfig

/** A value suitable for Crashlytics
* This value is used to truncate the user-defined message and the exception message
* In theory, the message sent to Crashlytics could therefore be 2x this value
*/
private const val MAX_CHARS_IN_LOG = 1200

/** A Crashlytics logger. The name Antilog might be an unfortunate choice by the Napier library;
* this is not a stub
*/
class CrashlyticsLogger : Antilog() {

private val logcatLogger = DebugAntilog()

override fun performLog(
priority: LogLevel,
tag: String?,
throwable: Throwable?,
message: String?
) {
if (message == null && throwable == null) return

if (BuildConfig.DEBUG || priority > LogLevel.DEBUG) {
// also send to logcat
logcatLogger.log(priority, tag, throwable, message)
}

val limitedMessage = message?.take(MAX_CHARS_IN_LOG) ?: "(no message)" // to avoid OutOfMemoryError's

if (priority < LogLevel.ERROR) {
// at least one of message or throwable is not null
val errorMessage = throwable?.let {
" with error: ${throwable}: ${throwable.message}".take(MAX_CHARS_IN_LOG)
} ?: ""
Firebase.crashlytics.log(limitedMessage + errorMessage)
} else {
Firebase.crashlytics.log("recordException with message: $limitedMessage")
Firebase.crashlytics.recordException(throwable ?: Exception(message))
}
}
}
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ plugins { // sets class paths only (because of 'apply false')
alias libs.plugins.kotlinSerialization apply false
alias libs.plugins.hilt apply false
alias libs.plugins.ksp apply false
alias libs.plugins.googleServices apply false
alias libs.plugins.firebaseCrashlyticsPlugin apply false
}

allprojects {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nl.q42.template.navigation.viewmodel

import androidx.annotation.VisibleForTesting
import com.ramcosta.composedestinations.spec.Direction
import io.github.aakira.napier.Napier
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

Expand Down Expand Up @@ -43,6 +44,7 @@ class MyRouteNavigator : RouteNavigator {

@VisibleForTesting
fun navigate(state: NavigationState) {
Napier.i { state.toString() }
navigationState.value = state
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package nl.q42.template.presentation.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.aakira.napier.Napier
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -15,6 +17,7 @@ import nl.q42.template.navigation.AppGraphRoutes
import nl.q42.template.navigation.viewmodel.RouteNavigator
import nl.q42.template.ui.home.destinations.HomeSecondScreenDestination
import nl.q42.template.ui.presentation.ViewStateString
import java.lang.RuntimeException
import javax.inject.Inject

@HiltViewModel
Expand Down Expand Up @@ -57,6 +60,9 @@ class HomeViewModel @Inject constructor(
}

fun onOpenSecondScreenClicked() {
Napier.e(RuntimeException("Open Second Screen tapped. This will be shown as the non-fatal title")) {
"Open Second Screen Tapped. This will be shown in the Crashlytics breadcrumbs"
}
navigateTo(HomeSecondScreenDestination(title = "Hello world!"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package nl.q42.template.ui.home

import androidx.compose.foundation.layout.Arrangement.Center
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import nl.q42.template.presentation.home.HomeViewState
import nl.q42.template.ui.compose.get
import nl.q42.template.ui.presentation.toViewStateString
Expand Down Expand Up @@ -52,6 +55,10 @@ internal fun HomeContent(
Button(onClick = onOpenOnboardingClicked) {
Text("Open onboarding")
}

Spacer(modifier = Modifier.height(32.dp))

Text(text = "NOTE: when cloning this template, set up your own Firebase project and replace google-services.json")
}
}

Expand Down
7 changes: 7 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ jvmTarget = "17"
kotlin = "1.9.22"
ksp = "1.9.22-1.0.18"
gradlePlugin = "8.2.1"
googleServices = "4.4.0"
crashlyticsPlugin = "2.9.9"
firebaseBOM = "32.7.0"
manesVersions = "0.44.0"
littleRobotsCatalogUpdates = "0.8.1"
hilt = "2.50"
Expand Down Expand Up @@ -49,6 +52,8 @@ composeUITooling = { module = "androidx.compose.ui:ui-tooling" }
composeUIToolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" }
composeMaterial3 = { module = "androidx.compose.material3:material3" }
composePlatform = { module = "androidx.compose:compose-bom", version.ref = "composePlatform" }
firebaseBoM = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBOM" }
firebaseCrashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
hiltNavigationCompose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
composeLifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "composeLifecycle" }
Expand All @@ -64,3 +69,5 @@ manesVersions = { id = "com-github-ben-manes-versions", version.ref = "manesVers
littleRobotsCatalogUpdates = { id = "nl.littlerobots.version-catalog-update", version.ref = "littleRobotsCatalogUpdates" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
googleServices = { id = "com.google.gms.google-services", version.ref = "googleServices" }
firebaseCrashlyticsPlugin = { id = "com.google.firebase.crashlytics", version.ref = "crashlyticsPlugin" }

0 comments on commit 766c705

Please sign in to comment.