Skip to content

Commit 933be04

Browse files
authored
Chore/crashlytics (#46)
1 parent 8c797d3 commit 933be04

File tree

15 files changed

+252
-39
lines changed

15 files changed

+252
-39
lines changed

app/build.gradle

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ plugins {
66
id "com.google.devtools.ksp"
77
id "org.sonarqube" version "4.2.1.3168"
88
id 'jacoco'
9+
id 'com.google.firebase.crashlytics'
910
}
1011

1112
def getPropertiesFile = { path ->
@@ -70,6 +71,10 @@ android {
7071
getPropertiesFile('app/config/uat.properties').each { p ->
7172
buildConfigField 'String', p.key, p.value
7273
}
74+
// Disable mapping file upload in debug to avoid unnecessary uploads
75+
firebaseCrashlytics {
76+
mappingFileUploadEnabled false
77+
}
7378
}
7479
release {
7580
minifyEnabled false
@@ -78,6 +83,11 @@ android {
7883
getPropertiesFile('app/config/prod.properties').each { p ->
7984
buildConfigField 'String', p.key, p.value
8085
}
86+
// Unable to upload mapping files without this in release
87+
firebaseCrashlytics {
88+
mappingFileUploadEnabled true
89+
nativeSymbolUploadEnabled true
90+
}
8191
}
8292
}
8393
compileOptions {
@@ -155,7 +165,8 @@ sonar {
155165
**/DatastorePref.kt,\
156166
**/FirebaseService.kt,\
157167
**/WordpressService.kt,\
158-
**/*Interceptor.kt,"
168+
**/*Interceptor.kt,\
169+
**/CrashlyticsUtils.kt,"
159170
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml"
160171
}
161172
}
@@ -191,14 +202,15 @@ dependencies {
191202
debugImplementation "androidx.compose.ui:ui-test-manifest:1.9.2"
192203

193204
// Import the BoM for the Firebase platform
194-
implementation platform('com.google.firebase:firebase-bom:34.3.0')
205+
implementation platform('com.google.firebase:firebase-bom:34.5.0')
195206

196207
// Firebase
197208
implementation("com.google.firebase:firebase-auth-ktx:22.3.0")
198209
implementation 'com.google.firebase:firebase-firestore-ktx:25.1.4'
199210
implementation 'com.google.firebase:firebase-messaging:25.0.1'
200211
implementation 'com.google.firebase:firebase-storage-ktx:21.0.2'
201212
implementation "com.google.firebase:firebase-analytics"
213+
implementation 'com.google.firebase:firebase-crashlytics-ndk'
202214

203215
// Navigation
204216
implementation 'androidx.navigation:navigation-compose:2.9.5'

app/src/main/java/com/xpeho/xpeapp/MainActivity.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.xpeho.xpeapp.ui.Home
2121
import com.xpeho.xpeapp.ui.notifications.AlarmScheduler
2222
import com.xpeho.xpeapp.ui.theme.XpeAppTheme
2323
import kotlinx.coroutines.CoroutineScope
24-
import kotlinx.coroutines.Dispatchers
24+
import com.xpeho.xpeapp.utils.DefaultDispatcherProvider
2525
import kotlinx.coroutines.flow.MutableStateFlow
2626
import kotlinx.coroutines.launch
2727
import kotlinx.coroutines.runBlocking
@@ -71,30 +71,30 @@ class MainActivity : ComponentActivity() {
7171
MutableStateFlow(if (connectedLastTime) Screens.Home else Screens.Login)
7272

7373
// Periodic check for token expiration (every 8 hours)
74-
CoroutineScope(Dispatchers.IO).launch {
74+
CoroutineScope(DefaultDispatcherProvider.io).launch {
7575
while (true) {
7676
kotlinx.coroutines.delay(TOKEN_CHECK_INTERVAL.inWholeMilliseconds)
7777

7878
// Check if we are connected and if the token has expired
7979
val authState = XpeApp.appModule.authenticationManager.authState.value
80-
if (authState is com.xpeho.xpeapp.domain.AuthState.Authenticated) {
81-
if (!XpeApp.appModule.authenticationManager.isAuthValid()) {
82-
XpeApp.appModule.authenticationManager.logout()
83-
withContext(Dispatchers.Main) {
84-
startScreenFlow.value = Screens.Login
85-
}
80+
if (authState is com.xpeho.xpeapp.domain.AuthState.Authenticated &&
81+
!XpeApp.appModule.authenticationManager.isAuthValid()
82+
) {
83+
XpeApp.appModule.authenticationManager.logout()
84+
withContext(DefaultDispatcherProvider.main) {
85+
startScreenFlow.value = Screens.Login
8686
}
8787
}
8888
}
8989
}
9090

9191
// If the user was connected last time, try to restore the authentication state.
9292
if (connectedLastTime) {
93-
CoroutineScope(Dispatchers.IO).launch {
93+
CoroutineScope(DefaultDispatcherProvider.io).launch {
9494
XpeApp.appModule.authenticationManager.restoreAuthStateFromStorage()
9595
if (!XpeApp.appModule.authenticationManager.isAuthValid()) {
9696
XpeApp.appModule.authenticationManager.logout()
97-
withContext(Dispatchers.Main) {
97+
withContext(DefaultDispatcherProvider.main) {
9898
startScreenFlow.value = Screens.Login
9999
}
100100
}
@@ -142,13 +142,13 @@ class MainActivity : ComponentActivity() {
142142
private fun checkForUpdate() {
143143
// Check for updates only in release mode
144144
if (!BuildConfig.DEBUG) {
145-
CoroutineScope(Dispatchers.IO).launch {
145+
CoroutineScope(DefaultDispatcherProvider.io).launch {
146146
val latestVersion = getLatestReleaseTag()
147147
val currentVersion = getCurrentAppVersion()
148148

149149
// If the latest version is not null and is greater than the current version,
150150
if (latestVersion != null && isVersionLessThan(currentVersion, latestVersion)) {
151-
withContext(Dispatchers.Main) {
151+
withContext(DefaultDispatcherProvider.main) {
152152
showUpdateDialog(latestVersion)
153153
}
154154
}
@@ -181,7 +181,7 @@ class MainActivity : ComponentActivity() {
181181
}
182182

183183
private suspend fun getLatestReleaseTag(): String? {
184-
return withContext(Dispatchers.IO) {
184+
return withContext(DefaultDispatcherProvider.io) {
185185
val client = OkHttpClient()
186186
val request = Request.Builder()
187187
.url("https://api.github.com/repos/XPEHO/xpeapp_android/releases/latest")

app/src/main/java/com/xpeho/xpeapp/XpeApp.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.xpeho.xpeapp
22

33
import android.app.Application
4+
import com.google.firebase.crashlytics.FirebaseCrashlytics
45
import com.xpeho.xpeapp.di.AppModule
56
import com.xpeho.xpeapp.di.MainAppModule
67

@@ -11,6 +12,10 @@ class XpeApp : Application() {
1112

1213
override fun onCreate() {
1314
super.onCreate()
15+
16+
// Initialize Firebase Crashlytics
17+
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = true
18+
1419
appModule = MainAppModule(appContext = this)
1520
}
1621
}

app/src/main/java/com/xpeho/xpeapp/data/service/FirebaseService.kt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,38 @@ import com.xpeho.xpeapp.data.NEWSLETTERS_COLLECTION
99
import com.xpeho.xpeapp.data.model.FeatureFlipping
1010
import com.xpeho.xpeapp.data.model.Newsletter
1111
import com.xpeho.xpeapp.data.model.toFeatureFlipping
12+
import com.xpeho.xpeapp.utils.CrashlyticsUtils
1213
import kotlinx.coroutines.tasks.await
1314
import java.time.ZoneId
1415

1516
class FirebaseService {
1617
suspend fun authenticate() {
17-
FirebaseAuth.getInstance().signInAnonymously().await()
18+
try {
19+
CrashlyticsUtils.logEvent("Firebase: Tentative d'authentification anonyme")
20+
FirebaseAuth.getInstance().signInAnonymously().await()
21+
CrashlyticsUtils.logEvent("Firebase: Authentification anonyme réussie")
22+
} catch (e: FirebaseException) {
23+
CrashlyticsUtils.logEvent("Firebase: Erreur d'authentification anonyme")
24+
CrashlyticsUtils.recordException(e)
25+
throw e
26+
}
1827
}
1928

2029
fun isAuthenticated() = FirebaseAuth.getInstance().currentUser != null
2130

22-
fun signOut() = FirebaseAuth.getInstance().signOut()
31+
fun signOut() {
32+
try {
33+
CrashlyticsUtils.logEvent("Firebase: Déconnexion")
34+
FirebaseAuth.getInstance().signOut()
35+
} catch (e: FirebaseException) {
36+
CrashlyticsUtils.recordException(e)
37+
throw e
38+
}
39+
}
2340

2441
suspend fun fetchFeatureFlipping(): List<FeatureFlipping> {
2542
try {
43+
CrashlyticsUtils.logEvent("Firebase: Récupération des feature flags")
2644
val db = FirebaseFirestore.getInstance()
2745
val document = db.collection(FEATURE_FLIPPING_COLLECTION)
2846
.get()
@@ -37,9 +55,14 @@ class FirebaseService {
3755
featureFlippingList[featureFlippingList.indexOf(featureFlipping)] = featureFlipping
3856
}
3957
}
58+
CrashlyticsUtils.logEvent("Firebase: Feature flags " +
59+
"récupérés avec succès (${featureFlippingList.size} éléments)")
4060
return featureFlippingList
4161
} catch (firebaseException: FirebaseException) {
4262
Log.e("fetchFeatureFlipping", "Error getting documents: $firebaseException")
63+
CrashlyticsUtils.logEvent("Firebase: Erreur lors " +
64+
"de la récupération des feature flags")
65+
CrashlyticsUtils.recordException(firebaseException)
4366
return emptyList()
4467
}
4568
}
@@ -50,6 +73,7 @@ class FirebaseService {
5073
val defaultSystemOfZone = ZoneId.systemDefault()
5174

5275
try {
76+
CrashlyticsUtils.logEvent("Firebase: Récupération des newsletters")
5377
db.collection(NEWSLETTERS_COLLECTION)
5478
.get()
5579
.await()
@@ -73,8 +97,13 @@ class FirebaseService {
7397
)
7498
newslettersList.add(newsletter)
7599
}
100+
CrashlyticsUtils.logEvent("Firebase: Newsletters " +
101+
"récupérées avec succès (${newslettersList.size} éléments)")
76102
} catch (firebaseException: FirebaseException) {
77103
Log.d("fetchNewsletters", "Error getting documents: ", firebaseException)
104+
CrashlyticsUtils.logEvent("Firebase: Erreur lors de " +
105+
"la récupération des newsletters")
106+
CrashlyticsUtils.recordException(firebaseException)
78107
}
79108

80109
return newslettersList.sortedByDescending { it.date }

app/src/main/java/com/xpeho/xpeapp/data/service/WordpressRepository.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.xpeho.xpeapp.data.model.qvst.QvstCampaign
1717
import com.xpeho.xpeapp.data.model.qvst.QvstProgress
1818
import com.xpeho.xpeapp.data.model.qvst.QvstQuestion
1919
import com.xpeho.xpeapp.data.model.user.UpdatePasswordResult
20+
import com.xpeho.xpeapp.utils.CrashlyticsUtils
2021
import retrofit2.HttpException
2122
import java.net.ConnectException
2223
import java.net.SocketTimeoutException
@@ -126,6 +127,8 @@ class WordpressRepository(
126127
username: String,
127128
onlyActive: Boolean = false
128129
): List<QvstCampaignEntity>? {
130+
CrashlyticsUtils.setCurrentFeature("qvst")
131+
CrashlyticsUtils.setCustomKey("qvst_only_active", onlyActive.toString())
129132
handleServiceExceptions(
130133
tryBody = {
131134
val campaigns = if (onlyActive) {
@@ -403,11 +406,25 @@ class WordpressRepository(
403406
catchBody: (Exception) -> T
404407
): T {
405408
return try {
406-
tryBody()
409+
val result = tryBody()
410+
// Crashlytics : Log de succès pour les appels API
411+
CrashlyticsUtils.logEvent("API WordPress: Appel réussi")
412+
CrashlyticsUtils.setCurrentFeature("api_wordpress")
413+
result
407414
} catch (e: Exception) {
408415
if (isNetworkError(e)) {
416+
// Crashlytics : Log des erreurs réseau
417+
CrashlyticsUtils.logEvent("API WordPress: Erreur réseau - ${e.javaClass.simpleName}")
418+
CrashlyticsUtils.recordException(e)
419+
CrashlyticsUtils.setCustomKey("last_api_error", e.message ?: "Erreur inconnue")
420+
CrashlyticsUtils.setCustomKey("last_api_error_time", System.currentTimeMillis().toString())
421+
CrashlyticsUtils.setCurrentFeature("api_wordpress_error")
409422
catchBody(e)
410423
} else {
424+
// Crashlytics : Log des erreurs non-réseau (généralement plus graves)
425+
CrashlyticsUtils.logEvent("API WordPress: Erreur critique - ${e.javaClass.simpleName}")
426+
CrashlyticsUtils.recordException(e)
427+
CrashlyticsUtils.setCurrentFeature("api_wordpress_critical")
411428
throw e
412429
}
413430
}

app/src/main/java/com/xpeho/xpeapp/di/AppModule.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.xpeho.xpeapp.di
33
import android.content.Context
44
import com.google.firebase.analytics.FirebaseAnalytics
55
import com.google.gson.GsonBuilder
6-
import com.google.type.DateTime
76
import com.xpeho.xpeapp.BuildConfig
87
import com.xpeho.xpeapp.data.DatastorePref
98
import com.xpeho.xpeapp.data.dateConverter.DateTimeTypeAdapter
@@ -42,7 +41,7 @@ class MainAppModule(
4241
// Register custom date format for Date and DateTime
4342
.registerTypeAdapter(Date::class.java, DateTypeAdapter())
4443
.registerTypeAdapter(LocalTime::class.java, DateTimeTypeAdapter())
45-
.setLenient().create()
44+
.create()
4645

4746
private val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }
4847
private val authorization by lazy {
@@ -111,5 +110,3 @@ class MainAppModule(
111110
)
112111
}
113112
}
114-
115-

app/src/main/java/com/xpeho/xpeapp/domain/AuthenticationManager.kt

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.xpeho.xpeapp.data.model.WordpressToken
99
import com.xpeho.xpeapp.data.service.FirebaseService
1010
import com.xpeho.xpeapp.data.service.WordpressRepository
1111
import com.xpeho.xpeapp.di.TokenProvider
12+
import com.xpeho.xpeapp.utils.CrashlyticsUtils
1213
import kotlinx.coroutines.async
1314
import kotlinx.coroutines.coroutineScope
1415
import kotlinx.coroutines.flow.MutableStateFlow
@@ -92,6 +93,15 @@ class AuthenticationManager(
9293
}
9394

9495
suspend fun login(username: String, password: String): AuthResult<WordpressToken> = coroutineScope {
96+
// Crashlytics : Contexte de connexion
97+
CrashlyticsUtils.setCurrentScreen("login")
98+
CrashlyticsUtils.setCurrentFeature("authentication")
99+
CrashlyticsUtils.setUserContext(isLoggedIn = false)
100+
101+
// Crashlytics : Log de la tentative de connexion
102+
CrashlyticsUtils.logEvent("Tentative de connexion pour: $username")
103+
CrashlyticsUtils.setCustomKey("last_login_attempt", System.currentTimeMillis().toString())
104+
95105
val wpDefRes = async {
96106
wordpressRepo.authenticate(AuthentificationBody(username, password))
97107
}
@@ -103,14 +113,25 @@ class AuthenticationManager(
103113
},
104114
catchBody = { e ->
105115
Log.e("AuthenticationManager: login", "Network error: ${e.message}")
116+
// Crashlytics : Enregistrer l'erreur réseau
117+
CrashlyticsUtils.recordException(e)
106118
return@async AuthResult.NetworkError
107119
}
108120
)
109121
}
110122

111123
val result = when (val wpRes = wpDefRes.await()) {
112-
is AuthResult.NetworkError -> AuthResult.NetworkError
113-
is AuthResult.Unauthorized -> AuthResult.Unauthorized
124+
is AuthResult.NetworkError -> {
125+
// Crashlytics : Log erreur réseau
126+
CrashlyticsUtils.logEvent("Erreur réseau lors de la connexion pour: $username")
127+
AuthResult.NetworkError
128+
}
129+
is AuthResult.Unauthorized -> {
130+
// Crashlytics : Log erreur d'authentification
131+
CrashlyticsUtils.logEvent("Erreur d'authentification pour: $username")
132+
CrashlyticsUtils.setCustomKey("last_failed_login", username)
133+
AuthResult.Unauthorized
134+
}
114135
else -> {
115136
val fbRes = fbDefRes.await()
116137
when (fbRes) {
@@ -121,6 +142,15 @@ class AuthenticationManager(
121142
writeAuthentication(authData)
122143
_authState.value = AuthState.Authenticated(authData)
123144
tokenProvider.set("Bearer ${authData.token.token}")
145+
146+
// Crashlytics : Log connexion réussie
147+
CrashlyticsUtils.logEvent("Connexion réussie pour: $username")
148+
CrashlyticsUtils.setUserId(authData.token.userEmail)
149+
CrashlyticsUtils.setCustomKey("user_email", authData.token.userEmail)
150+
CrashlyticsUtils.setCustomKey("last_successful_login", System.currentTimeMillis().toString())
151+
CrashlyticsUtils.setUserContext(isLoggedIn = true)
152+
CrashlyticsUtils.setCurrentScreen("home")
153+
124154
wpRes
125155
}
126156
}
@@ -140,10 +170,24 @@ class AuthenticationManager(
140170
}
141171

142172
suspend fun logout() {
173+
// Crashlytics : Contexte de déconnexion
174+
CrashlyticsUtils.setCurrentFeature("authentication")
175+
CrashlyticsUtils.setCurrentScreen("logout")
176+
177+
// Crashlytics : Log de la déconnexion
178+
val currentUser = getAuthData()?.token?.userEmail ?: "utilisateur_inconnu"
179+
CrashlyticsUtils.logEvent("Déconnexion de l'utilisateur: $currentUser")
180+
143181
firebaseService.signOut()
144182
datastorePref.clearAuthData()
145183
datastorePref.setWasConnectedLastTime(false)
146184
_authState.value = AuthState.Unauthenticated
185+
186+
// Crashlytics : Nettoyer les infos utilisateur
187+
CrashlyticsUtils.setUserId("")
188+
CrashlyticsUtils.setCustomKey("user_email", "")
189+
CrashlyticsUtils.setUserContext(isLoggedIn = false)
190+
CrashlyticsUtils.setCurrentScreen("login")
147191
}
148192
}
149193

0 commit comments

Comments
 (0)