From 001e3781b2b982a597b2f439295862e64b63d094 Mon Sep 17 00:00:00 2001 From: tuancoltech Date: Sat, 16 Nov 2024 16:24:32 +0700 Subject: [PATCH] Handle crash upon app launch when storage folder is removed externally --- .../arkbuilders/arkmemo/models/LoadError.kt | 5 +++ .../arkmemo/preferences/MemoPreferences.kt | 2 ++ .../preferences/MemoPreferencesImpl.kt | 5 +++ .../arkmemo/repo/NotesRepoHelper.kt | 14 +++++++- .../arkmemo/repo/graphics/GraphicNotesRepo.kt | 1 + .../arkmemo/repo/text/TextNotesRepo.kt | 8 ++++- .../arkmemo/repo/voices/VoiceNotesRepo.kt | 1 + .../arkmemo/ui/activities/MainActivity.kt | 33 +++++++++++++++++-- .../arkmemo/ui/dialogs/FilePickerDialog.kt | 6 ++-- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 21 +++++++++++- app/src/main/res/values/strings.xml | 4 +++ gradle/libs.versions.toml | 2 +- settings.gradle | 1 + 13 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt new file mode 100644 index 0000000..194053b --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt @@ -0,0 +1,5 @@ +package dev.arkbuilders.arkmemo.models + +sealed interface LoadError + +data class RootNotFound(val rootPath: String) : LoadError diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt index 36b4dd8..da9238e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt @@ -12,4 +12,6 @@ interface MemoPreferences { fun storeCrashReportEnabled(enabled: Boolean) fun getCrashReportEnabled(): Boolean + + fun storageNotAvailable(): Boolean } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt index 253dd2b..4472a7f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt @@ -7,6 +7,7 @@ import dev.arkbuilders.arkmemo.utils.CRASH_REPORT_ENABLE import java.nio.file.Path import javax.inject.Inject import kotlin.io.path.Path +import kotlin.io.path.exists private const val NAME = "memo_prefs" private const val CURRENT_NOTES_PATH = "current_notes_path" @@ -33,4 +34,8 @@ class MemoPreferencesImpl } override fun getCrashReportEnabled(): Boolean = sharedPreferences.getBoolean(CRASH_REPORT_ENABLE, true) + + override fun storageNotAvailable(): Boolean { + return getPath().isEmpty() || !getNotesStorage().exists() + } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt index 63d4c42..2952e7b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt @@ -10,6 +10,7 @@ import dev.arkbuilders.arklib.user.properties.PropertiesStorageRepo import dev.arkbuilders.arkmemo.models.Note import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.utils.isEqual +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.nio.file.Path @@ -30,9 +31,20 @@ class NotesRepoHelper private lateinit var root: Path private lateinit var propertiesStorage: PropertiesStorage + private val exceptionHandler by lazy { + CoroutineExceptionHandler { _, throwable -> + Log.e("tuancoltech", "exceptionHandler got an exception: " + throwable::class.simpleName) + } + } + + @Throws(UnknownError::class) suspend fun init() { root = memoPreferences.getNotesStorage() - propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) + try { + propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root, exceptionHandler)) + } catch (e: Exception) { + Log.e("tuancoltech", "NotesRepoHelper init() exception: " + e.message) + } } suspend fun persistNoteProperties( diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt index 055773d..bfa96b9 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt @@ -54,6 +54,7 @@ class GraphicNotesRepo private val thumbDirectory by lazy { context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) } override suspend fun init() { + Log.d("tuancoltech", "GraphicNotesRepo initializing!") helper.init() root = memoPreferences.getNotesStorage() } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt index 93521be..c9cd36b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt @@ -36,7 +36,12 @@ class TextNotesRepo override suspend fun init() { root = memoPreferences.getNotesStorage() - helper.init() + try { + Log.d("tuancoltech", "TextNotesRepo initializing!") + helper.init() + } catch (exception: Exception) { + Log.e("tuancoltech", "helper.init exception: " + exception) + } } override suspend fun save( @@ -127,6 +132,7 @@ class TextNotesRepo } } catch (e: Exception) { e.printStackTrace() + Log.e("tuancoltech", "TextNotesRepo readStorage exception path: " + path) TextNote( text = "", ) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt index e83a4e3..eb18723 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt @@ -34,6 +34,7 @@ class VoiceNotesRepo override suspend fun init() { root = memoPreferences.getNotesStorage() + Log.d("tuancoltech", "VoicesNotesRepo initializing!") helper.init() } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt index f8d3852..1491bd0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt @@ -15,13 +15,16 @@ import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.contracts.PermissionContract import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding +import dev.arkbuilders.arkmemo.models.RootNotFound import dev.arkbuilders.arkmemo.preferences.MemoPreferences +import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment import dev.arkbuilders.components.filepicker.onArkPathPicked import javax.inject.Inject +import kotlin.io.path.exists @AndroidEntryPoint class MainActivity : AppCompatActivity(R.layout.activity_main) { @@ -92,8 +95,13 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } - if (memoPreferences.getPath().isEmpty()) { - FilePickerDialog.show(this, supportFragmentManager) + val storageFolderExisting = memoPreferences.getNotesStorage().exists() + if (memoPreferences.storageNotAvailable()) { + if (!storageFolderExisting) { + showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath())) + } else { + FilePickerDialog.show(this, supportFragmentManager) + } supportFragmentManager.onArkPathPicked(this) { memoPreferences.storePath(it.toString()) @@ -104,6 +112,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } + private fun showNoNoteStorageDialog(error: RootNotFound) { + val loadFailDialog = + CommonActionDialog( + title = getString(R.string.error_load_notes_failed_title), + message = getString(R.string.error_load_notes_failed_description, error.rootPath), + positiveText = R.string.error_load_notes_failed_positive_action, + negativeText = R.string.error_load_notes_failed_negative_action, + isAlert = false, + onPositiveClick = { + FilePickerDialog.show(this, supportFragmentManager) + }, + onNegativeClicked = { + finish() + }, + onCloseClicked = { + finish() + }, + ) + loadFailDialog.show(supportFragmentManager, CommonActionDialog.TAG) + } + override fun onSaveInstanceState(outState: Bundle) { outState.putString(CURRENT_FRAGMENT_TAG, fragment.tag) super.onSaveInstanceState(outState) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt index a0c5f8a..eb65bc2 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt @@ -24,20 +24,18 @@ class FilePickerDialog : ArkFilePickerFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (storageNotAvailable()) { + if (memoPreferences.storageNotAvailable()) { isCancelable = false } } override fun dismiss() { super.dismiss() - if (storageNotAvailable()) { + if (memoPreferences.storageNotAvailable()) { activity?.finish() } } - private fun storageNotAvailable(): Boolean = memoPreferences.getPath().isEmpty() - companion object { private const val TAG = "file_picker" private lateinit var fragmentManager: FragmentManager diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt index 57374c4..ad2d582 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt @@ -1,5 +1,6 @@ package dev.arkbuilders.arkmemo.ui.viewmodels +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -15,6 +16,7 @@ import dev.arkbuilders.arkmemo.models.VoiceNote import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.utils.extractDuration import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -38,10 +40,27 @@ class NotesViewModel private val notes = MutableStateFlow(listOf()) private val mSaveNoteResultLiveData = MutableLiveData() private var searchJob: Job? = null + private var initializeSuccess = true + + private val initExceptionHandler by lazy { + CoroutineExceptionHandler { _, throwable -> + Log.e("tuancoltech", "NotesViewModel initExceptionHandler got an exception: " + throwable::class.simpleName) + throwable.printStackTrace() + initializeSuccess = false + } + } + + private val readStorageExceptionHandler by lazy { + CoroutineExceptionHandler { _, throwable -> + Log.e("tuancoltech", "NotesViewModel readStorageExceptionHandler got an exception: " + throwable::class.simpleName) + throwable.printStackTrace() + initializeSuccess = false + } + } fun init(extraBlock: () -> Unit) { val initJob = - viewModelScope.launch(iODispatcher) { + viewModelScope.launch(iODispatcher + initExceptionHandler) { textNotesRepo.init() graphicNotesRepo.init() voiceNotesRepo.init() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e68d460..e4fb86c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,5 +117,9 @@ Select All Deselect All + Cannot find notes + The folder %s with notes data cannot be located.\nPlease select a new folder. + Select + Leave \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0d7ee5..21cc55b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ navigationFragmentKtx = "2.5.2" preferenceKtx = "1.2.0" lifecycleViewmodelKtx = "2.5.1" arkFilepicker = "0.2.0" -arkLib = "0.3.5" +arkLib = "0.3.6-snapshot-crash-01" googleHiltAndroid = "2.48" googleHiltCompiler = "2.48" googleFirebaseBom = "33.3.0" diff --git a/settings.gradle b/settings.gradle index 35057ec..5535534 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ dependencyResolutionManagement { password = "\u0037\u0066\u0066\u0036\u0030\u0039\u0033\u0066\u0032\u0037\u0033\u0036\u0033\u0037\u0064\u0036\u0037\u0066\u0038\u0030\u0034\u0039\u0062\u0030\u0039\u0038\u0039\u0038\u0066\u0034\u0066\u0034\u0031\u0064\u0062\u0033\u0064\u0033\u0038\u0065" } } + mavenLocal() } versionCatalogs {