From b14f5376729e511771e84c54abfd3d8fe3ba7308 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Mon, 15 Dec 2025 18:46:21 +0100 Subject: [PATCH 1/2] Illustrating timing issue with FileActivity#dismissLoadingDialog This timing issue was reproducible when testing RemoveFilesDialogFragment#removeFiles and sporadically "in the wild". However, no solution offered so far Signed-off-by: Philipp Hasper --- .../nextcloud/client/FileDisplayActivityIT.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt index ad6c28f597ed..0805ef65e365 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2025 Philipp Hasper * SPDX-FileCopyrightText: 2019 Tobias Kaminsky * SPDX-FileCopyrightText: 2019 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only @@ -14,12 +15,14 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.DrawerActions import androidx.test.espresso.contrib.NavigationViewActions import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -36,6 +39,7 @@ import com.owncloud.android.operations.CreateFolderOperation import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.adapter.OCFileListItemViewHolder import com.owncloud.android.utils.EspressoIdlingResource +import org.hamcrest.Matchers.allOf import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -44,6 +48,7 @@ import org.junit.Rule import org.junit.Test class FileDisplayActivityIT : AbstractOnServerIT() { + @Before fun registerIdlingResource() { IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource) @@ -236,4 +241,53 @@ class FileDisplayActivityIT : AbstractOnServerIT() { } } } + + @Test + fun testShowAndDismissLoadingDialog() { + launchActivity().use { scenario -> + val loadingText = "Some text displayed while loading" + + // Test that display works + scenario.onActivity { sut -> + sut.showLoadingDialog(loadingText) + } + onView(withText(loadingText)) + .check(matches(isDisplayed())) + + // Test that hiding works + scenario.onActivity { sut -> + sut.dismissLoadingDialog() + } + onView(allOf(withText(loadingText), isDisplayed())) + .check(doesNotExist()) + + // Test that there is no timing issue when hiding the dialog directly after. + // This timing issue was reproducible when testing RemoveFilesDialogFragment#removeFiles + // as well as sporadically "in the wild". + scenario.onActivity { sut -> + sut.showLoadingDialog(loadingText) + sut.dismissLoadingDialog() + } + onView(allOf(withText(loadingText), isDisplayed())) + .check(doesNotExist()) + // Wait for a potential timing issue - dialog appearing belatedly + Thread.sleep(1000) + onView(allOf(withText(loadingText), isDisplayed())) + .check(doesNotExist()) + + // Test that multiple display calls after another don't cause a timing issue + scenario.onActivity { sut -> + sut.showLoadingDialog(loadingText) + sut.showLoadingDialog(loadingText) + sut.showLoadingDialog(loadingText) + sut.dismissLoadingDialog() + } + onView(allOf(withText(loadingText), isDisplayed())) + .check(doesNotExist()) + // Wait for a potential timing issue - dialog appearing belatedly + Thread.sleep(1000) + onView(allOf(withText(loadingText), isDisplayed())) + .check(doesNotExist()) + } + } } From 407c1bdc575726aa15c02e849c906bb68945985c Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Tue, 16 Dec 2025 18:24:55 +0100 Subject: [PATCH 2/2] Fixing timing issue with FileActivity's loading dialog show and dismiss. Before dismissing the dialog, we need to wait for a potentially pending transaction. As showing the dialog also includes the dismissing of prior instances, we need to wait there as well. Both is needed to satisfy the test case added in the previous commit. Otherwise, the dialog might be shown after it was meant to be dismissed already. This issue was observed when testing RemoveFilesDialogFragment's removeFiles() and also sporadically "in the wild". Signed-off-by: Philipp Hasper --- .../java/com/owncloud/android/ui/activity/FileActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index 1e1727283d18..2e87286462a2 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -563,6 +563,7 @@ protected void updateFileFromDB(){ public void showLoadingDialog(String message) { runOnUiThread(() -> { FragmentManager fragmentManager = getSupportFragmentManager(); + fragmentManager.executePendingTransactions(); Fragment existingDialog = fragmentManager.findFragmentByTag(DIALOG_WAIT_TAG); if (existingDialog instanceof LoadingDialog loadingDialog) { @@ -585,6 +586,7 @@ public void showLoadingDialog(String message) { public void dismissLoadingDialog() { runOnUiThread(() -> { FragmentManager fragmentManager = getSupportFragmentManager(); + fragmentManager.executePendingTransactions(); Fragment fragment = fragmentManager.findFragmentByTag(DIALOG_WAIT_TAG); if (fragment instanceof LoadingDialog loadingDialogFragment) {