diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 2c03f87d424..d74d2594db4 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -24,6 +24,8 @@ plugins {
alias(libs.plugins.com.diffplug.spotless) apply false
alias(libs.plugins.android.junit5) apply false
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) apply false
+ alias(libs.plugins.google.services) apply false
+ alias(libs.plugins.firebase.crashlytics) apply false
}
tasks.dokkaHtmlMultiModule {
diff --git a/android/buildSrc/src/main/kotlin/project-properties.gradle.kts b/android/buildSrc/src/main/kotlin/project-properties.gradle.kts
index bc186f2337a..7828cec109c 100644
--- a/android/buildSrc/src/main/kotlin/project-properties.gradle.kts
+++ b/android/buildSrc/src/main/kotlin/project-properties.gradle.kts
@@ -26,7 +26,6 @@ val requiredFhirProperties =
"OAUTH_CLIENT_ID",
"OAUTH_SCOPE",
"MAPBOX_SDK_TOKEN",
- "SENTRY_DSN",
"OPENSRP_APP_ID",
"GEMINI_API_KEY",
"SPEECH_TO_TEXT_API_KEY",
@@ -58,23 +57,3 @@ requiredKeystoreProperties.forEach { property ->
project.extra.set(property, keystoreProperties.getProperty(property, "sample_$property"))
}
-// Set Sentry properties
-val requiredSentryProperties =
- listOf(
- "org",
- "project",
- "auth.token",
- "url"
- )
-
-val sentryProperties = try {
- readProperties((project.properties["sentryPropertiesFile"] ?: "${rootProject.projectDir}/sentry.properties").toString())
-} catch (e: FileNotFoundException) {
- if (project.properties["sentryPropertiesFile"] != null) {
- throw e
- } else Properties()
-}
-
-requiredSentryProperties.forEach { property ->
- project.extra.set(property, sentryProperties.getProperty(property, "sentry_$property"))
-}
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index bc5581098a8..03ce5193791 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -95,6 +95,9 @@ uiautomator = "2.3.0"
work = "2.9.1"
xercesImpl = "2.12.2"
zip4j = "2.11.5"
+firebase-bom = "34.2.0"
+firebase-crashlytics-gradle = "3.0.6"
+google-services = "4.4.3"
[libraries]
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" }
@@ -226,6 +229,9 @@ workflow = { group = "org.smartregister", name = "workflow", version.ref = "fhir
xercesImpl = { group = "xerces", name = "xercesImpl", version.ref = "xercesImpl" }
androidx-fragment-compose = { group = "androidx.fragment", name = "fragment-compose", version.ref = "compose-fragment" }
zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
+firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
+firebase-crashlytics-ndk = { group = "com.google.firebase", name = "firebase-crashlytics-ndk" }
+firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
[plugins]
@@ -239,6 +245,8 @@ org-jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "jetbrains" }
org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
org-owasp-dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "owasp" }
org-jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
+firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle" }
[bundles]
accompanist = ["accompanist-placeholder", "accompanist-flowlayout"]
diff --git a/android/quest/build.gradle.kts b/android/quest/build.gradle.kts
index 7fe8627ff1c..76c70bc6879 100644
--- a/android/quest/build.gradle.kts
+++ b/android/quest/build.gradle.kts
@@ -1,6 +1,4 @@
import com.android.build.api.variant.FilterConfiguration.FilterType
-import io.sentry.android.gradle.extensions.InstrumentationFeature
-import io.sentry.android.gradle.instrumentation.logcat.LogcatLevel
import java.io.FileReader
import java.text.SimpleDateFormat
import java.util.Date
@@ -22,10 +20,11 @@ plugins {
id("dagger.hilt.android.plugin")
id("androidx.navigation.safeargs")
id("org.sonarqube") version "3.5.0.2730"
- id("io.sentry.android.gradle") version "3.11.1"
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose)
+ id("com.google.gms.google-services")
+ id("com.google.firebase.crashlytics")
}
sonar {
@@ -77,7 +76,6 @@ android {
buildConfigField("String", "OAUTH_SCOPE", """"${project.extra["OAUTH_SCOPE"]}"""")
buildConfigField("String", "OPENSRP_APP_ID", """${project.extra["OPENSRP_APP_ID"]}""")
buildConfigField("String", "CONFIGURATION_SYNC_PAGE_SIZE", """"100"""")
- buildConfigField("String", "SENTRY_DSN", """"${project.extra["SENTRY_DSN"]}"""")
buildConfigField("String", "BUILD_DATE", "\"$buildDate\"")
buildConfigField("String", "GEMINI_API_KEY", """"${project.extra["GEMINI_API_KEY"]}"""")
buildConfigField(
@@ -559,6 +557,9 @@ dependencies {
implementation(libs.androidx.fragment.compose)
implementation(libs.bundles.cameraX)
implementation(libs.log4j)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.crashlytics.ndk)
+ implementation(libs.firebase.analytics)
// AI dependencies
implementation(libs.google.cloud.speech) {
@@ -706,154 +707,6 @@ tasks.register("evaluatePerformanceBenchmarkResults") {
}
}
-sentry {
- // Disables or enables debug log output, e.g. for for sentry-cli.
- // Default is disabled.
- debug.set(true)
-
- // The slug of the Sentry organization to use for uploading proguard mappings/source contexts.
- org.set("""${project.extra["org"]}""")
-
- // The slug of the Sentry project to use for uploading proguard mappings/source contexts.
- projectName.set("""${project.extra["project"]}""")
-
- // The authentication token to use for uploading proguard mappings/source contexts.
- // WARNING: Do not expose this token in your build.gradle files, but rather set an environment
- // variable and read it into this property.
- authToken.set("""${project.extra["auth.token"]}""")
-
- // The url of your Sentry instance. If you're using SAAS (not self hosting) you do not have to
- // set this. If you are self hosting you can set your URL here
- // url = null
-
- // Disables or enables the handling of Proguard mapping for Sentry.
- // If enabled the plugin will generate a UUID and will take care of
- // uploading the mapping to Sentry. If disabled, all the logic
- // related to proguard mapping will be excluded.
- // Default is enabled.
- includeProguardMapping.set(false)
-
- // Whether the plugin should attempt to auto-upload the mapping file to Sentry or not.
- // If disabled the plugin will run a dry-run and just generate a UUID.
- // The mapping file has to be uploaded manually via sentry-cli in this case.
- // Default is enabled.
- autoUploadProguardMapping.set(false)
-
- // Experimental flag to turn on support for GuardSquare's tools integration (Dexguard and
- // External Proguard).
- // If enabled, the plugin will try to consume and upload the mapping file produced by Dexguard
- // and External Proguard.
- // Default is disabled.
- // dexguardEnabled.set(false)
-
- // Disables or enables the automatic configuration of Native Symbols
- // for Sentry. This executes sentry-cli automatically so
- // you don't need to do it manually.
- // Default is disabled.
- uploadNativeSymbols.set(false)
-
- // Whether the plugin should attempt to auto-upload the native debug symbols to Sentry or not.
- // If disabled the plugin will run a dry-run.
- // Default is enabled.
- autoUploadNativeSymbols.set(true)
-
- // Does or doesn't include the source code of native code for Sentry.
- // This executes sentry-cli with the --include-sources param. automatically so
- // you don't need to do it manually.
- // Default is disabled.
- includeNativeSources.set(false)
-
- // Generates a JVM (Java, Kotlin, etc.) source bundle and uploads your source code to Sentry.
- // This enables source context, allowing you to see your source
- // code as part of your stack traces in Sentry.
- includeSourceContext.set(false)
-
- // Configure additional directories to be included in the source bundle which is used for
- // source context. The directories should be specified relative to the Gradle module/project's
- // root. For example, if you have a custom source set alongside 'main', the parameter would be
- // 'src/custom/java'.
- additionalSourceDirsForSourceContext.set(emptySet())
-
- // Enable or disable the tracing instrumentation.
- // Does auto instrumentation for specified features through bytecode manipulation.
- // Default is enabled.
- tracingInstrumentation {
- enabled.set(true)
-
- // Specifies a set of instrumentation features that are eligible for bytecode manipulation.
- // Defaults to all available values of InstrumentationFeature enum class.
- features.set(
- setOf(
- InstrumentationFeature.DATABASE,
- InstrumentationFeature.FILE_IO,
- InstrumentationFeature.OKHTTP,
- InstrumentationFeature.COMPOSE,
- ),
- )
-
- // Enable or disable logcat instrumentation through bytecode manipulation.
- // Default is enabled.
- logcat {
- enabled.set(true)
-
- // Specifies a minimum log level for the logcat breadcrumb logging.
- // Defaults to LogcatLevel.WARNING.
- minLevel.set(LogcatLevel.WARNING)
- }
-
- // The set of glob patterns to exclude from instrumentation. Classes matching any of these
- // patterns in the project's sources and dependencies JARs won't be instrumented by the
- // Sentry Gradle plugin.
- //
- // Don't include the file extension. Filtering is done on compiled classes and
- // the .class suffix isn't included in the pattern matching.
- //
- // Example usage:
- // ```
- // excludes.set(setOf("com/example/donotinstrument/**", "**/*Test"))
- // ```
- //
- // Only supported when using Android Gradle plugin (AGP) version 7.4.0 and above.
- // excludes.set(emptySet())
- }
-
- // Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber, fragment
- // and compose integrations).
- // Default is enabled.
- // Only available v3.1.0 and above.
- autoInstallation {
- enabled.set(true)
-
- // Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations.
- //
- // This is also useful, when you have the sentry-android SDK already included into a transitive
- // dependency/module and want to
- // align integration versions with it (if it's a direct dependency, the version will be
- // inferred).
- //
- // NOTE: if you have a higher version of the sentry-android SDK or integrations on the
- // classpath, this setting will have no effect
- // as Gradle will resolve it to the latest version.
- //
- // Defaults to the latest published Sentry version.
- sentryVersion.set("7.14.0")
- }
-
- // Disables or enables dependencies metadata reporting for Sentry.
- // If enabled, the plugin will collect external dependencies and
- // upload them to Sentry as part of events. If disabled, all the logic
- // related to the dependencies metadata report will be excluded.
- //
- // Default is enabled.
- includeDependenciesReport.set(true)
-
- // Whether the plugin should send telemetry data to Sentry.
- // If disabled the plugin won't send telemetry data.
- // This is auto disabled if running against a self hosted instance of Sentry.
- // Default is enabled.
- // telemetry.set(true)
-}
-
fun JSONObject.getTestName(): String {
val className = getString("className").substringAfterLast(".")
val methodName = getString("name").substringAfterLast("_")
diff --git a/android/quest/src/main/AndroidManifest.xml b/android/quest/src/main/AndroidManifest.xml
index 47b29ba70cd..79540f2d001 100644
--- a/android/quest/src/main/AndroidManifest.xml
+++ b/android/quest/src/main/AndroidManifest.xml
@@ -34,9 +34,6 @@
android:enabled="true"
tools:targetApi="29" />
-
- options.dsn = dsn.trim { it <= ' ' }
- // To set a uniform sample rate
- options.tracesSampleRate = 1.0
- options.isEnableUserInteractionTracing = true
- options.isEnableUserInteractionBreadcrumbs = true
- options.addIntegration(
- FragmentLifecycleIntegration(
- this,
- enableFragmentLifecycleBreadcrumbs = true,
- enableAutoFragmentLifecycleTracing = true,
- ),
- )
- try {
- options.environment = URL(BuildConfig.FHIR_BASE_URL).getSubDomain().replace('-', '.')
- } catch (e: Exception) {
- Timber.e(e)
- }
- }
-
- SentryAndroid.init(this, sentryConfiguration)
+ private fun initFirebaseCrashlytics() {
+ try {
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
+ Timber.d("Firebase Crashlytics initialized successfully")
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to initialize Firebase Crashlytics")
}
}
diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ReleaseTree.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ReleaseTree.kt
index 560a0ced3cc..21044bc8de2 100644
--- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ReleaseTree.kt
+++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ReleaseTree.kt
@@ -18,7 +18,7 @@ package org.smartregister.fhircore.quest
import android.util.Log
import android.util.Log.ERROR
-import io.sentry.Sentry
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import timber.log.Timber
class ReleaseTree : Timber.Tree() {
@@ -33,7 +33,9 @@ class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
if (priority == ERROR || priority == Log.WARN || priority == Log.ASSERT) {
if (throwable != null) {
- Sentry.captureException(throwable)
+ FirebaseCrashlytics.getInstance().recordException(throwable)
+ } else {
+ FirebaseCrashlytics.getInstance().log("$tag: $message")
}
}
}
diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/login/LoginViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/login/LoginViewModel.kt
index 3dac0b323a3..8f43b3dfbb0 100644
--- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/login/LoginViewModel.kt
+++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/login/LoginViewModel.kt
@@ -28,8 +28,6 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import dagger.hilt.android.lifecycle.HiltViewModel
-import io.sentry.Sentry
-import io.sentry.protocol.User
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.concurrent.TimeUnit
@@ -68,7 +66,6 @@ import org.smartregister.fhircore.engine.util.extension.practitionerEndpointUrl
import org.smartregister.fhircore.engine.util.extension.removeHashPrefix
import org.smartregister.fhircore.engine.util.extension.showToast
import org.smartregister.fhircore.engine.util.extension.valueToString
-import org.smartregister.fhircore.quest.BuildConfig
import org.smartregister.model.location.LocationHierarchy
import org.smartregister.model.practitioner.PractitionerDetails
import retrofit2.HttpException
@@ -171,16 +168,6 @@ constructor(
} else if (
accountAuthenticator.validateLoginCredentials(trimmedUsername, passwordAsCharArray)
) {
- try {
- // Configure Sentry scope
- Sentry.configureScope { scope ->
- scope.setTag("versionCode", BuildConfig.VERSION_CODE.toString())
- scope.setTag("versionName", BuildConfig.VERSION_NAME)
- scope.user = User().apply { username = trimmedUsername }
- }
- } catch (e: Exception) {
- Timber.e(e)
- }
_showProgressBar.postValue(false)
updateNavigateHome(true)
} else {
diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt
index 58d4b1b0f43..884c71e0d89 100644
--- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt
+++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt
@@ -30,13 +30,11 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
-import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.fhir.sync.CurrentSyncJobStatus
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import dagger.hilt.android.AndroidEntryPoint
-import io.sentry.android.navigation.SentryNavigationListener
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.async
@@ -84,8 +82,6 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
@Inject lateinit var dispatcherProvider: DispatcherProvider
val appMainViewModel by viewModels()
- private val sentryNavListener =
- SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true)
private val locationPermissionLauncher: ActivityResultLauncher> =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
@@ -163,13 +159,11 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
override fun onResume() {
super.onResume()
- findNavController(R.id.nav_host).addOnDestinationChangedListener(sentryNavListener)
syncListenerManager.registerSyncListener(this, lifecycle)
}
override fun onPause() {
super.onPause()
- findNavController(R.id.nav_host).removeOnDestinationChangedListener(sentryNavListener)
}
override fun onQuestionnaireLaunched(questionnaireConfig: QuestionnaireConfig) {
diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt
index 34616b0c359..171faef6f1f 100644
--- a/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt
+++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt
@@ -19,13 +19,7 @@ package org.smartregister.fhircore.quest
import android.content.Intent
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
-import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
-import io.mockk.spyk
-import io.sentry.Sentry
-import io.sentry.android.core.SentryAndroid
-import io.sentry.android.core.SentryAndroidOptions
import kotlin.test.assertNotNull
import org.junit.Assert
import org.junit.Before
@@ -48,30 +42,6 @@ class QuestApplicationTest : RobolectricTest() {
application.xFhirQueryResolver = mockk()
}
- @Test
- fun testSentryMonitoringWhenDsnNotBlank() {
- val sentryDsn = "debb3087-167a-47ff-b6d4-737be3965a4c"
- val spyApp = spyk(application)
- val sentryOptions = spyk()
-
- mockkStatic(SentryAndroid::class) {
- every {
- SentryAndroid.init(any(), any>())
- } answers
- {
- val optionsConfiguration = secondArg>()
- optionsConfiguration.configure(sentryOptions)
- }
-
- spyApp.initSentryMonitoring(dsn = sentryDsn)
-
- Assert.assertEquals(sentryDsn, sentryOptions.dsn)
- Assert.assertEquals(1.0, sentryOptions.tracesSampleRate)
- Assert.assertTrue(sentryOptions.isEnableUserInteractionTracing)
- Assert.assertTrue(sentryOptions.isEnableUserInteractionBreadcrumbs)
- }
- }
-
@Test
fun testGetDataCaptureConfig() {
val config = application.getDataCaptureConfig()