Skip to content

Commit

Permalink
Handle crash upon app launch when storage folder is removed externally
Browse files Browse the repository at this point in the history
  • Loading branch information
tuancoltech committed Nov 28, 2024
1 parent 8d48ed4 commit 001e378
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 10 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.arkbuilders.arkmemo.models

sealed interface LoadError

data class RootNotFound(val rootPath: String) : LoadError
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ interface MemoPreferences {
fun storeCrashReportEnabled(enabled: Boolean)

fun getCrashReportEnabled(): Boolean

fun storageNotAvailable(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -127,6 +132,7 @@ class TextNotesRepo
}
} catch (e: Exception) {
e.printStackTrace()
Log.e("tuancoltech", "TextNotesRepo readStorage exception path: " + path)
TextNote(
text = "",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class VoiceNotesRepo

override suspend fun init() {
root = memoPreferences.getNotesStorage()
Log.d("tuancoltech", "VoicesNotesRepo initializing!")
helper.init()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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())
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -38,10 +40,27 @@ class NotesViewModel
private val notes = MutableStateFlow(listOf<Note>())
private val mSaveNoteResultLiveData = MutableLiveData<SaveNoteResult>()
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()
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,9 @@
</plurals>
<string name="select_all">Select All</string>
<string name="deselect_all">Deselect All</string>
<string name="error_load_notes_failed_title">Cannot find notes</string>
<string name="error_load_notes_failed_description">The folder %s with notes data cannot be located.\nPlease select a new folder.</string>
<string name="error_load_notes_failed_positive_action">Select</string>
<string name="error_load_notes_failed_negative_action">Leave</string>

</resources>
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 001e378

Please sign in to comment.