From c5983066fcd7ca69bf92727eaae1c41008f0d877 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 10 May 2024 15:27:00 +0100 Subject: [PATCH 1/4] Remove ANR when showing snackbar --- .../android/mainmenu/MainMenuFragment.kt | 56 +++++++++---------- .../android/mainmenu/MainMenuViewModel.kt | 45 ++++++++++++--- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt index d27987e352c..8741b968f2e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt @@ -2,7 +2,6 @@ package org.odk.collect.android.mainmenu import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -45,8 +44,9 @@ class MainMenuFragment( private lateinit var permissionsViewModel: RequestPermissionsViewModel private val formEntryFlowLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - displayFormSavedSnackbar(it.data?.data) + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + val uri = result.data?.data + mainMenuViewModel.setSavedForm(uri) } override fun onAttach(context: Context) { @@ -85,6 +85,30 @@ class MainMenuFragment( this.parentFragmentManager ) } + + mainMenuViewModel.savedForm.observe(viewLifecycleOwner) { + val value = it.value + + if (value != null && !it.isConsumed()) { + it.consume() + + SnackbarUtils.showLongSnackbar( + requireView(), + getString(value.second), + action = value.third?.let { action -> + SnackbarUtils.Action(getString(action)) { + formEntryFlowLauncher.launch( + FormFillingIntentFactory.editInstanceIntent( + requireContext(), + value.first + ) + ) + } + }, + displayDismissButton = true + ) + } + } } override fun onResume() { @@ -243,30 +267,4 @@ class MainMenuFragment( binding.googleDriveDeprecationBanner.root.visibility = View.GONE } } - - private fun displayFormSavedSnackbar(uri: Uri?) { - if (uri == null) { - return - } - - val formSavedSnackbarDetails = mainMenuViewModel.getFormSavedSnackbarDetails(uri) - - formSavedSnackbarDetails?.let { - SnackbarUtils.showLongSnackbar( - requireView(), - getString(it.first), - action = it.second?.let { action -> - SnackbarUtils.Action(getString(action)) { - formEntryFlowLauncher.launch( - FormFillingIntentFactory.editInstanceIntent( - requireContext(), - uri - ) - ) - } - }, - displayDismissButton = true - ) - } - } } diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt index 147989d9123..3bc9938bb30 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt @@ -3,7 +3,9 @@ package org.odk.collect.android.mainmenu import android.app.Application import android.net.Uri import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.map import org.odk.collect.android.instancemanagement.InstanceDiskSynchronizer import org.odk.collect.android.instancemanagement.InstancesDataService import org.odk.collect.android.instancemanagement.autosend.AutoSendSettingsProvider @@ -15,6 +17,7 @@ import org.odk.collect.android.utilities.ContentUriHelper import org.odk.collect.android.utilities.FormsRepositoryProvider import org.odk.collect.android.utilities.InstancesRepositoryProvider import org.odk.collect.android.version.VersionInformation +import org.odk.collect.androidshared.data.Consumable import org.odk.collect.async.Scheduler import org.odk.collect.forms.instances.Instance import org.odk.collect.settings.SettingsProvider @@ -42,7 +45,10 @@ class MainMenuViewModel( var commitDescription = "" if (versionInformation.commitCount != null) { commitDescription = - appendToCommitDescription(commitDescription, versionInformation.commitCount.toString()) + appendToCommitDescription( + commitDescription, + versionInformation.commitCount.toString() + ) } if (versionInformation.commitSHA != null) { commitDescription = @@ -58,29 +64,38 @@ class MainMenuViewModel( } } + private val _savedForm = MutableLiveData?>() + val savedForm: LiveData?>> = _savedForm.map { Consumable(it) } + fun shouldEditSavedFormButtonBeVisible(): Boolean { - return settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_EDIT_SAVED) + return settingsProvider.getProtectedSettings() + .getBoolean(ProtectedProjectKeys.KEY_EDIT_SAVED) } fun shouldSendFinalizedFormButtonBeVisible(): Boolean { - return settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_SEND_FINALIZED) + return settingsProvider.getProtectedSettings() + .getBoolean(ProtectedProjectKeys.KEY_SEND_FINALIZED) } fun shouldViewSentFormButtonBeVisible(): Boolean { - return settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_VIEW_SENT) + return settingsProvider.getProtectedSettings() + .getBoolean(ProtectedProjectKeys.KEY_VIEW_SENT) } fun shouldGetBlankFormButtonBeVisible(): Boolean { - val buttonEnabled = settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_GET_BLANK) + val buttonEnabled = + settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_GET_BLANK) return !isMatchExactlyEnabled() && buttonEnabled } fun shouldDeleteSavedFormButtonBeVisible(): Boolean { - return settingsProvider.getProtectedSettings().getBoolean(ProtectedProjectKeys.KEY_DELETE_SAVED) + return settingsProvider.getProtectedSettings() + .getBoolean(ProtectedProjectKeys.KEY_DELETE_SAVED) } private fun isMatchExactlyEnabled(): Boolean { - return settingsProvider.getUnprotectedSettings().getFormUpdateMode(application) == FormUpdateMode.MATCH_EXACTLY + return settingsProvider.getUnprotectedSettings() + .getFormUpdateMode(application) == FormUpdateMode.MATCH_EXACTLY } private fun appendToCommitDescription(commitDescription: String, part: String): String { @@ -114,7 +129,8 @@ class MainMenuViewModel( val message = if (instance.isDraft()) { org.odk.collect.strings.R.string.form_saved_as_draft } else if (instance.status == Instance.STATUS_COMPLETE || instance.status == Instance.STATUS_SUBMISSION_FAILED) { - val form = formsRepositoryProvider.get().getAllByFormIdAndVersion(instance.formId, instance.formVersion).first() + val form = formsRepositoryProvider.get() + .getAllByFormIdAndVersion(instance.formId, instance.formVersion).first() if (form.shouldFormBeSentAutomatically(autoSendSettingsProvider.isAutoSendEnabledInSettings())) { org.odk.collect.strings.R.string.form_sending } else { @@ -139,4 +155,17 @@ class MainMenuViewModel( null } } + + fun setSavedForm(uri: Uri?) { + if (uri == null) { + return + } + + scheduler.immediate { + val details = getFormSavedSnackbarDetails(uri) + if (details != null) { + _savedForm.postValue(Triple(uri, details.first, details.second)) + } + } + } } From 9430289cd4a7a723a11ac1f92e8e1539c79d96f2 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 10 May 2024 15:31:35 +0100 Subject: [PATCH 2/4] Add helper for Consumable --- .../org/odk/collect/androidshared/data/Consumable.kt | 12 ++++++++++++ .../odk/collect/android/mainmenu/MainMenuFragment.kt | 9 +++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/data/Consumable.kt b/androidshared/src/main/java/org/odk/collect/androidshared/data/Consumable.kt index a945258c59b..317bbb3c0cd 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/data/Consumable.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/data/Consumable.kt @@ -1,5 +1,8 @@ package org.odk.collect.androidshared.data +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData + /** * Useful for values that are read multiple times but only used * once (like an error that shows a dialog once). @@ -16,3 +19,12 @@ data class Consumable(val value: T) { consumed = true } } + +fun LiveData>.consume(lifecycleOwner: LifecycleOwner, consumer: (T) -> Unit) { + observe(lifecycleOwner) { consumable -> + if (!consumable.isConsumed()) { + consumable.consume() + consumer(consumable.value) + } + } +} diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt index 8741b968f2e..398ca6cde52 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt @@ -27,6 +27,7 @@ import org.odk.collect.android.projects.ProjectIconView import org.odk.collect.android.projects.ProjectSettingsDialog import org.odk.collect.android.utilities.ActionRegister import org.odk.collect.android.utilities.ApplicationConstants +import org.odk.collect.androidshared.data.consume import org.odk.collect.androidshared.ui.DialogFragmentUtils import org.odk.collect.androidshared.ui.SnackbarUtils import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard @@ -86,12 +87,8 @@ class MainMenuFragment( ) } - mainMenuViewModel.savedForm.observe(viewLifecycleOwner) { - val value = it.value - - if (value != null && !it.isConsumed()) { - it.consume() - + mainMenuViewModel.savedForm.consume(viewLifecycleOwner) { value -> + if (value != null) { SnackbarUtils.showLongSnackbar( requireView(), getString(value.second), From 71b43f907da60b55ce3ca2b86c8593ac0398a7df Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 10 May 2024 15:53:10 +0100 Subject: [PATCH 3/4] Introduce data class for form saved event --- .../android/mainmenu/MainMenuFragment.kt | 30 ++-- .../android/mainmenu/MainMenuViewModel.kt | 32 +++-- .../android/mainmenu/MainMenuViewModelTest.kt | 129 ++++++++++++------ 3 files changed, 119 insertions(+), 72 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt index 398ca6cde52..6da427fe83f 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuFragment.kt @@ -88,23 +88,21 @@ class MainMenuFragment( } mainMenuViewModel.savedForm.consume(viewLifecycleOwner) { value -> - if (value != null) { - SnackbarUtils.showLongSnackbar( - requireView(), - getString(value.second), - action = value.third?.let { action -> - SnackbarUtils.Action(getString(action)) { - formEntryFlowLauncher.launch( - FormFillingIntentFactory.editInstanceIntent( - requireContext(), - value.first - ) + SnackbarUtils.showLongSnackbar( + requireView(), + getString(value.message), + action = value.action?.let { action -> + SnackbarUtils.Action(getString(action)) { + formEntryFlowLauncher.launch( + FormFillingIntentFactory.editInstanceIntent( + requireContext(), + value.uri ) - } - }, - displayDismissButton = true - ) - } + ) + } + }, + displayDismissButton = true + ) } } diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt index 3bc9938bb30..aba49bddcb7 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuViewModel.kt @@ -64,8 +64,8 @@ class MainMenuViewModel( } } - private val _savedForm = MutableLiveData?>() - val savedForm: LiveData?>> = _savedForm.map { Consumable(it) } + private val _savedForm = MutableLiveData() + val savedForm: LiveData> = _savedForm.map { Consumable(it) } fun shouldEditSavedFormButtonBeVisible(): Boolean { return settingsProvider.getProtectedSettings() @@ -123,7 +123,20 @@ class MainMenuViewModel( val sentInstancesCount: LiveData get() = instancesDataService.sentCount - fun getFormSavedSnackbarDetails(uri: Uri): Pair? { + fun setSavedForm(uri: Uri?) { + if (uri == null) { + return + } + + scheduler.immediate { + val details = getFormSavedSnackbarDetails(uri) + if (details != null) { + _savedForm.postValue(SavedForm(uri, details.first, details.second)) + } + } + } + + private fun getFormSavedSnackbarDetails(uri: Uri): Pair? { val instance = instancesRepositoryProvider.get().get(ContentUriHelper.getIdFromUri(uri)) return if (instance != null) { val message = if (instance.isDraft()) { @@ -156,16 +169,5 @@ class MainMenuViewModel( } } - fun setSavedForm(uri: Uri?) { - if (uri == null) { - return - } - - scheduler.immediate { - val details = getFormSavedSnackbarDetails(uri) - if (details != null) { - _savedForm.postValue(Triple(uri, details.first, details.second)) - } - } - } + data class SavedForm(val uri: Uri, val message: Int, val action: Int?) } diff --git a/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuViewModelTest.kt b/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuViewModelTest.kt index e242d376290..07e9cc43536 100644 --- a/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuViewModelTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuViewModelTest.kt @@ -1,8 +1,10 @@ package org.odk.collect.android.mainmenu +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.ext.junit.runners.AndroidJUnit4 import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn @@ -14,6 +16,7 @@ import org.odk.collect.android.projects.ProjectsDataService import org.odk.collect.android.utilities.FormsRepositoryProvider import org.odk.collect.android.utilities.InstancesRepositoryProvider import org.odk.collect.android.version.VersionInformation +import org.odk.collect.androidtest.getOrAwaitValue import org.odk.collect.forms.instances.Instance import org.odk.collect.formstest.FormUtils import org.odk.collect.formstest.InMemFormsRepository @@ -22,6 +25,8 @@ import org.odk.collect.projects.Project import org.odk.collect.settings.InMemSettingsProvider import org.odk.collect.settings.keys.ProtectedProjectKeys import org.odk.collect.shared.TempFiles +import org.odk.collect.testshared.FakeScheduler +import java.util.concurrent.TimeoutException @RunWith(AndroidJUnit4::class) class MainMenuViewModelTest { @@ -40,6 +45,11 @@ class MainMenuViewModelTest { on { getCurrentProject() } doReturn Project.DEMO_PROJECT } + private val scheduler = FakeScheduler() + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun `version when beta release returns semantic version with prefix and beta version`() { val viewModel = createViewModelWithVersion("v1.23.0-beta.1") @@ -101,7 +111,7 @@ class MainMenuViewModelTest { } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is saved as draft, editing drafts is enabled and encryption is disabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is saved as draft, editing drafts is enabled and encryption is disabled`() { val viewModel = createViewModelWithVersion("") settingsProvider.getProtectedSettings().save(ProtectedProjectKeys.KEY_EDIT_SAVED, true) @@ -116,13 +126,16 @@ class MainMenuViewModelTest { ) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarType = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarType.first, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) - assertThat(formSavedSnackbarType.second, equalTo(org.odk.collect.strings.R.string.edit_form)) + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.edit_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is saved as draft, editing drafts is enabled and encryption is enabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is saved as draft, editing drafts is enabled and encryption is enabled`() { val viewModel = createViewModelWithVersion("") settingsProvider.getProtectedSettings().save(ProtectedProjectKeys.KEY_EDIT_SAVED, true) @@ -138,13 +151,17 @@ class MainMenuViewModelTest { ) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarType = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarType.first, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) - assertThat(formSavedSnackbarType.second, equalTo(org.odk.collect.strings.R.string.edit_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.edit_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is saved as draft, editing drafts is disabled and encryption is disabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is saved as draft, editing drafts is disabled and encryption is disabled`() { val viewModel = createViewModelWithVersion("") settingsProvider.getProtectedSettings().save(ProtectedProjectKeys.KEY_EDIT_SAVED, false) @@ -159,13 +176,17 @@ class MainMenuViewModelTest { ) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarType = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarType.first, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) - assertThat(formSavedSnackbarType.second, equalTo(org.odk.collect.strings.R.string.view_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.view_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is saved as draft, editing drafts is disabled and encryption is enabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is saved as draft, editing drafts is disabled and encryption is enabled`() { val viewModel = createViewModelWithVersion("") settingsProvider.getProtectedSettings().save(ProtectedProjectKeys.KEY_EDIT_SAVED, false) @@ -181,13 +202,17 @@ class MainMenuViewModelTest { ) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarType = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarType.first, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) - assertThat(formSavedSnackbarType.second, equalTo(org.odk.collect.strings.R.string.view_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved_as_draft)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.view_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is finalized, auto send is disabled and encryption is disabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is finalized, auto send is disabled and encryption is disabled`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -202,13 +227,17 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(false) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarDetails.first, equalTo(org.odk.collect.strings.R.string.form_saved)) - assertThat(formSavedSnackbarDetails.second, equalTo(org.odk.collect.strings.R.string.view_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.view_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is finalized, auto send is disabled and encryption is enabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is finalized, auto send is disabled and encryption is enabled`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -224,13 +253,17 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(false) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarDetails.first, equalTo(org.odk.collect.strings.R.string.form_saved)) - assertThat(formSavedSnackbarDetails.second, equalTo(null)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_saved)) + assertThat(formSavedSnackbarType.action, equalTo(null)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is finalized, auto send is enabled and encryption is disabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is finalized, auto send is enabled and encryption is disabled`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -246,13 +279,17 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(true) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarDetails.first, equalTo(org.odk.collect.strings.R.string.form_sending)) - assertThat(formSavedSnackbarDetails.second, equalTo(org.odk.collect.strings.R.string.view_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_sending)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.view_form)) } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance is finalized, auto send is enabled and encryption is enabled`() { + fun `setSavedForm should set proper message and action when the corresponding instance is finalized, auto send is enabled and encryption is enabled`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -269,13 +306,17 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(true) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarDetails.first, equalTo(org.odk.collect.strings.R.string.form_sending)) - assertThat(formSavedSnackbarDetails.second, equalTo(null)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_sending)) + assertThat(formSavedSnackbarType.action, equalTo(null)) } - @Test - fun `getFormSavedSnackbarDetails should return null when the corresponding instance is already sent`() { + @Test(expected = TimeoutException::class) + fun `setSavedForm should not set when the corresponding instance is already sent`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -291,12 +332,14 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(true) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri) - assertThat(formSavedSnackbarDetails, equalTo(null)) + viewModel.setSavedForm(uri) + scheduler.flush() + + viewModel.savedForm.getOrAwaitValue() } @Test - fun `getFormSavedSnackbarDetails should return proper message and action when the corresponding instance failed to sent`() { + fun `setSavedForm should set proper message and action when the corresponding instance failed to sent`() { val viewModel = createViewModelWithVersion("") formsRepository.save(FormUtils.buildForm("1", "1", TempFiles.createTempDir().absolutePath).build()) @@ -312,12 +355,16 @@ class MainMenuViewModelTest { whenever(autoSendSettingsProvider.isAutoSendEnabledInSettings()).thenReturn(true) val uri = InstancesContract.getUri(Project.DEMO_PROJECT_ID, instance.dbId) - val formSavedSnackbarDetails = viewModel.getFormSavedSnackbarDetails(uri)!! - assertThat(formSavedSnackbarDetails.first, equalTo(org.odk.collect.strings.R.string.form_sending)) - assertThat(formSavedSnackbarDetails.second, equalTo(org.odk.collect.strings.R.string.view_form)) + + viewModel.setSavedForm(uri) + scheduler.flush() + + val formSavedSnackbarType = viewModel.savedForm.getOrAwaitValue().value!! + assertThat(formSavedSnackbarType.message, equalTo(org.odk.collect.strings.R.string.form_sending)) + assertThat(formSavedSnackbarType.action, equalTo(org.odk.collect.strings.R.string.view_form)) } private fun createViewModelWithVersion(version: String): MainMenuViewModel { - return MainMenuViewModel(mock(), VersionInformation { version }, settingsProvider, mock(), mock(), formsRepositoryProvider, instancesRepositoryProvider, autoSendSettingsProvider, projectsDataService) + return MainMenuViewModel(mock(), VersionInformation { version }, settingsProvider, mock(), scheduler, formsRepositoryProvider, instancesRepositoryProvider, autoSendSettingsProvider, projectsDataService) } } From eb8891c7cdc40fd0ae7fbcca282680af6f64aee2 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 10 May 2024 16:50:39 +0100 Subject: [PATCH 4/4] Fix test --- .../org/odk/collect/android/mainmenu/MainMenuActivityTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuActivityTest.kt b/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuActivityTest.kt index b370c2eafc4..e1a206b1292 100644 --- a/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuActivityTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/mainmenu/MainMenuActivityTest.kt @@ -61,6 +61,7 @@ class MainMenuActivityTest { on { sendableInstancesCount } doReturn MutableLiveData(0) on { sentInstancesCount } doReturn MutableLiveData(0) on { editableInstancesCount } doReturn MutableLiveData(0) + on { savedForm } doReturn MutableLiveData() } private val currentProjectViewModel = mock {