Skip to content

Commit

Permalink
Error handling when generating Health summary (#146)
Browse files Browse the repository at this point in the history
# *Health summary PDF generation*

## ♻️ Current situation & Problem
- Even though the summary pdf was generated successfully, the service
was throwing because there was no PDF viewer app installed in the device
- We were able to catch the error and were attempting to show a toast.
However, since the code is being executed in a background thread, the
app was crashing because toast must be executed in the main thread.

Addressed the issue by:
- Ensuring that toast messages are executed always in the main thread
- Explicitly check whether after PDF generation whether there is an
appropriate pdf reader app installed in the device. If not we display a
toast message that summary is generated successfully but no pdf reader
installed in the device. In case of success, the user will have the
option to select the the pdf reader app via a default android bottom
sheet


## ⚙️ Release Notes 
*Add a bullet point list summary of the feature and possible migration
guides if this is a breaking change so this section can be added to the
release notes.*
*Include code snippets that provide examples of the feature implemented
or links to the documentation if it appends or changes the public
interface.*


## 📚 Documentation
*Please ensure that you properly document any additions in conformance
to [Spezi Documentation
Guide](https://github.com/StanfordSpezi/.github/blob/main/DOCUMENTATIONGUIDE.md).*
*You can use this section to describe your solution, but we encourage
contributors to document your reasoning and changes using in-line
documentation.*


## ✅ Testing
*Please ensure that the PR meets the testing requirements set by CodeCov
and that new functionality is appropriately tested.*
*This section describes important information about the tests and why
some elements might not be testable.*


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [ ] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).

---------

Co-authored-by: Paul Schmiedmayer <[email protected]>
  • Loading branch information
eldcn and PSchmiedmayer authored Dec 8, 2024
1 parent c6d63bf commit d101a43
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 7 deletions.
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@
</provider>
</application>

<queries>
<!-- Intent query to open PDF files -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="application/pdf" />
</intent>
</queries>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package edu.stanford.bdh.engagehf.messages

import android.content.Context
import android.content.Intent
import android.os.Environment
import androidx.core.content.FileProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import edu.stanford.bdh.engagehf.R
import edu.stanford.spezi.core.coroutines.di.Dispatching
import edu.stanford.spezi.core.logging.speziLogger
import edu.stanford.spezi.core.utils.MessageNotifier
Expand All @@ -31,21 +31,25 @@ class HealthSummaryService @Inject constructor(
"${context.packageName}.provider",
savePdfToFile
)
Intent(Intent.ACTION_VIEW).run {
val pdfIntent = Intent(Intent.ACTION_VIEW).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setDataAndType(pdfUri, MIME_TYPE_PDF)
context.startActivity(this)
}
if (pdfIntent.resolveActivity(context.packageManager) == null) {
messageNotifier.notify(messageId = R.string.no_pdf_reader_app_installed_error_message)
} else {
context.startActivity(pdfIntent)
}
}.onFailure {
messageNotifier.notify("Failed to generate Health Summary")
messageNotifier.notify(messageId = R.string.health_summary_generate_error_message)
}
}

private fun savePdfToFile(pdfBytes: ByteArray): File {
logger.i { "PDF size: ${pdfBytes.size}" }
val storageDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

val storageDir = context.getExternalFilesDir(null) ?: context.filesDir
if (!storageDir.exists()) {
storageDir.mkdirs()
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@
<string name="dizziness_score_title">Dizziness Score</string>
<string name="dizziness_score_description">Your dizziness is important to keep track of because dizziness can be a side effect of your heart or your heart medicines.</string>
<string name="error_while_handling_message_action">Error while handling message action.</string>
<string name="no_pdf_reader_app_installed_error_message">Health Summary generated successfully, but no PDF reader is installed on the device</string>
<string name="health_summary_generate_error_message">Failed to generate Health Summary</string>
</resources>
5 changes: 5 additions & 0 deletions core/utils/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ plugins {

android {
namespace = "edu.stanford.spezi.utils"

defaultConfig {
testInstrumentationRunner = "edu.stanford.spezi.core.utils.HiltApplicationTestRunner"
}
}

dependencies {
Expand All @@ -16,4 +20,5 @@ dependencies {
implementation(libs.kotlinx.serialization.json)

testImplementation(libs.bundles.unit.testing)
androidTestImplementation(libs.bundles.unit.testing)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package edu.stanford.spezi.core.utils

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

@Suppress("Unused")
class HiltApplicationTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package edu.stanford.spezi.core.utils

import androidx.test.platform.app.InstrumentationRegistry
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

@HiltAndroidTest
class MessageNotifierTest {

@get:Rule(order = 1)
val hiltRule = HiltAndroidRule(this)

@Inject
lateinit var messageNotifier: MessageNotifier

@Before
fun init() {
hiltRule.inject()
}

@Test
fun `it should not fail when toasting in main thread`() {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
messageNotifier.notify("Some message")
}
}

@Test
fun `it should not fail when notifying on a background thread`() = runTest(StandardTestDispatcher()) {
messageNotifier.notify("Some message")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package edu.stanford.spezi.core.utils

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.annotation.StringRes
import dagger.hilt.android.qualifiers.ApplicationContext
Expand All @@ -9,9 +11,15 @@ import javax.inject.Inject
class MessageNotifier @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val mainHandler by lazy { Handler(Looper.getMainLooper()) }

fun notify(message: String, duration: Duration = Duration.SHORT) {
Toast.makeText(context, message, duration.value).show()
val toast = { Toast.makeText(context, message, duration.value).show() }
if (Looper.myLooper() == Looper.getMainLooper()) {
toast()
} else {
mainHandler.post { toast() }
}
}

fun notify(@StringRes messageId: Int, duration: Duration = Duration.SHORT) {
Expand Down

0 comments on commit d101a43

Please sign in to comment.