diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt index 041f328421d4..8e5c789113da 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt @@ -153,6 +153,7 @@ import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider import com.ichi2.anki.snackbar.SnackbarBuilder import com.ichi2.anki.snackbar.showSnackbar import com.ichi2.anki.ui.setupNoteTypeSpinner +import com.ichi2.anki.utils.RunOnlyOnce import com.ichi2.anki.utils.ext.sharedPrefs import com.ichi2.anki.utils.ext.showDialogFragment import com.ichi2.anki.utils.ext.window @@ -224,7 +225,7 @@ class NoteEditorFragment : private var changed = false private var isTagsEdited = false private var isFieldEdited = false - + private var addNoteJob = RunOnlyOnce(scope = lifecycleScope) private var multimediaActionJob: Job? = null private val getColUnsafe: Collection @@ -1338,7 +1339,8 @@ class NoteEditorFragment : reloadRequired = true - lifecycleScope.launch { + // Use addNoteJob to ignore any requests made after the initial add note request, this prevents multiple clicks from crashing the app + addNoteJob.launch { val noteFieldsCheck = checkNoteFieldsResponse(editorNote!!) if (noteFieldsCheck is NoteFieldsCheckResult.Failure) { addNoteErrorMessage = noteFieldsCheck.localizedMessage ?: getString(R.string.something_wrong) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/OnlyOnce.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/OnlyOnce.kt index 94c02273432c..8856ab60fffa 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/OnlyOnce.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/OnlyOnce.kt @@ -16,7 +16,9 @@ package com.ichi2.anki.utils +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import timber.log.Timber /** @@ -51,3 +53,22 @@ object OnlyOnce { } } } + +/** + * Enforces single execution of a coroutine task + * if a task is running, ignore any other requests till it finishes + * @param scope The coroutine scope where the task is launched + */ +class RunOnlyOnce( + private val scope: CoroutineScope, +) { + private var job: Job? = null + + fun launch(block: suspend CoroutineScope.() -> Unit) { + if (job?.isActive == true) { + Timber.d("skipped multiple executions of job") + return + } + job = scope.launch(block = block) + } +}