Skip to content

Fix infinite loading circular progress bar after nominating for deletion #6324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

rohit9625
Copy link
Collaborator

Description (required)

Fixes #5531

What changes did you make and why?
Parneet fixed the issue in his PR #5610 by making changes in the MediaDetailFragment.java file. However, that file was migrated to Kotlin and caused merge conflicts. So, I pull from his branch, copy the changes to the migrated file and refactor the related code for better code quality.

Tests performed (required)

Tested prodDebug on Samsung A14 with API level 34.

Screenshots (for UI changes only)

After_Fix_Progress_Bar.mp4

Copy link
Member

@nicolas-raoul nicolas-raoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this branch and unfortunately when I tap the red button the progress thing kept circling fo several minutes.

Also, the picture it was about did not get the nomination banner:
https://commons.m.wikimedia.org/wiki/File%3AOktamov_Rozimattilla.jpg

@rohit9625
Copy link
Collaborator Author

rohit9625 commented May 26, 2025

Which variant did you tried? I tried on prodDebug and it worked. Also, what about the Toast message?

@nicolas-raoul
Copy link
Member

prodDebug too.
I saw no toast.
would a logcat help?

@rohit9625
Copy link
Collaborator Author

prodDebug too. I saw no toast. would a logcat help?

Yes, the logs would be helpful here, thanks :)

@rohit9625
Copy link
Collaborator Author

rohit9625 commented May 27, 2025

I just found out that @parneet-guraya was already working on the fix. I just missed it and looked at @neeldoshii's draft PR. However, both of them are not working for @nicolas-raoul. Could you please try to record a screencast while reproducing the issue and logs would be very helpful in fixing this problem?

@nicolas-raoul
Copy link
Member

Here is all I see in logcat after entering a deletion reason ad tapping OK:

05-27 22:52:50.729 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ViewRootImpl$$ExternalSyntheticLambda12@d3fc3f3
05-27 22:52:50.730 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): null
05-27 22:52:50.736 14770 14797 D HWUI    : endAllActiveAnimators on 0xb40000726ea8eb40 (RippleDrawable) with handle 0xb4000071eea3f5d0
05-27 22:52:50.740 14770 15795 V OkHttp  : --> GET https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py?user=Syced
05-27 22:52:50.740 14770 15795 V OkHttp  : --> END GET
05-27 22:52:50.798 14770 14770 D InsetsController: hide(ime(), fromIme=false)
05-27 22:52:50.798 14770 14770 I ImeTracker: fr.free.nrw.commons:bc489dd2: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
05-27 22:52:50.807 14770 14770 W RemoteInputConnectionImpl: requestCursorUpdates on inactive InputConnection
05-27 22:52:50.827 14770 14770 E ImeBackDispatcher: Ime callback not found. Ignoring unregisterReceivedCallback. callbackId: 196534055 remaining callbacks: 0
05-27 22:52:50.877 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ImeBackAnimationController@39697e9
05-27 22:52:50.897 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ViewRootImpl$$ExternalSyntheticLambda12@51f3c0d

@nicolas-raoul
Copy link
Member

Do not hesitate to add much more logcat calls everywhere, then I can provide another log. :-)

@rohit9625
Copy link
Collaborator Author

I've added some log statements depending on the expected flow of operation. Please try to repro the issue again and also a screencast as well.

Copy link

✅ Generated APK variants!

@nicolas-raoul
Copy link
Member

nicolas-raoul commented May 28, 2025

Thanks!

Here is what I am getting on Pixel 7 after entering a deletion reason and tapping OK:

05-28 22:50:56.051 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 416KB AllocSpace bytes, 0(0B) LOS objects, 75% free, 17MB/70MB, paused 931us,4.079ms total 75.911ms
05-28 22:50:56.415 32573 32573 D ReasonBuilder: Fetching article number
05-28 22:50:56.416 32573 32573 D ReasonBuilder: Fetching achievements for Syced
05-28 22:50:56.416 32573 32573 D OkHttpJsonApiClient: Url : https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py and User Name : Syced
05-28 22:50:56.418 32573 32573 W WindowOnBackDispatcher: sendCancelIfRunning: isInProgress=false callback=android.view.ViewRootImpl$$ExternalSyntheticLambda13@a1a26c3
05-28 22:50:56.418 32573  4297 D OkHttpJsonApiClient: Formatted URL: https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py
05-28 22:50:56.419 32573  4297 V OkHttp  : --> GET https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py?user=Syced
05-28 22:50:56.419 32573  4297 V OkHttp  : --> END GET
05-28 22:50:56.420 32573 32734 D HWUI    : endAllActiveAnimators on 0xb400007b5f47c660 (RippleDrawable) with handle 0xb400007b6f409be0
05-28 22:50:56.469 32573 32573 D InsetsController: hide(ime(), fromIme=false)
05-28 22:50:56.469 32573 32573 I ImeTracker: fr.free.nrw.commons:35c1bd6a: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
05-28 22:50:58.240 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 1016KB AllocSpace bytes, 2(104KB) LOS objects, 75% free, 17MB/70MB, paused 1.283ms,3.556ms total 80.383ms
05-28 22:51:00.183 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:00.187 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:00.188 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:00.189 32573 32573 D ExploreMapPresenter: Means location changed slightly
05-28 22:51:00.418 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 569KB AllocSpace bytes, 4(80KB) LOS objects, 75% free, 17MB/69MB, paused 830us,2.852ms total 71.023ms
05-28 22:51:02.605 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 176KB AllocSpace bytes, 4(80KB) LOS objects, 75% free, 17MB/69MB, paused 1.008ms,3.452ms total 80.361ms
05-28 22:51:04.792 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 128KB AllocSpace bytes, 0(0B) LOS objects, 75% free, 17MB/69MB, paused 876us,3.827ms total 79.337ms
05-28 22:51:05.174 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:05.174 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:05.174 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:05.175 32573 32573 D ExploreMapPresenter: Means location changed slightly
05-28 22:51:06.172 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:06.173 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:06.173 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:06.174 32573 32573 D ExploreMapPresenter: Means location changed slightly
[...]
[many unrelated messages about location and garbage collection]
[...]
05-28 22:54:55.361 32573  4297 V OkHttp  : <-- HTTP FAILED: java.net.SocketTimeoutException: timeout
05-28 22:54:55.369 32573 32573 E MediaDetailFragment: Error while nominating for deletion: timeout
05-28 22:54:55.369 32573 32573 W System.err: java.net.SocketTimeoutException: timeout
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream$StreamTimeout.newTimeoutException(Http2Stream.kt:675)
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream$StreamTimeout.exitAndThrowIfTimedOut(Http2Stream.kt:684)
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream.takeHeaders(Http2Stream.kt:143)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2ExchangeCodec.readResponseHeaders(Http2ExchangeCodec.kt:96)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:106)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:79)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:221)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
05-28 22:54:55.374 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.getAchievements$lambda$5(OkHttpJsonApiClient.kt:274)
05-28 22:54:55.375 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.$r8$lambda$C43DlEx4bcHOhyTBOAxdle8hOfc(Unknown Source:0)
05-28 22:54:55.375 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient$$ExternalSyntheticLambda2.call(D8$$SyntheticClass:0)
05-28 22:54:55.375 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:44)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleMap.subscribeActual(SingleMap.java:34)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
05-28 22:54:55.378 32573 32573 W System.err: 	at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.FutureTask.run(FutureTask.java:264)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
05-28 22:54:55.379 32573 32573 W System.err: 	at java.lang.Thread.run(Thread.java:1012)
05-28 22:54:55.380 32573 32573 W System.err: 	Suppressed: java.io.IOException: unexpected end of stream on https://tools.wmflabs.org/...
05-28 22:54:55.380 32573 32573 W System.err: 		at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:202)
05-28 22:54:55.380 32573 32573 W System.err: 		... 33 more
05-28 22:54:55.382 32573 32573 W System.err: 	Caused by: java.io.EOFException: \n not found: limit=0 content=…
05-28 22:54:55.382 32573 32573 W System.err: 		at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:332)
05-28 22:54:55.382 32573 32573 W System.err: 		at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29)
05-28 22:54:55.382 32573 32573 W System.err: 		at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:178)
05-28 22:54:55.382 32573 32573 W System.err: 		... 33 more
05-28 22:54:55.383 32573 32573 W System.err: 	Suppressed: java.net.SocketException: Software caused connection abort
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.socketRead0(Native Method)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.read(SocketInputStream.java:173)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.read(SocketInputStream.java:143)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:989)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:953)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841)
05-28 22:54:55.384 32573 32573 W System.err: 		at okio.InputStreamSource.read(JvmOkio.kt:94)
05-28 22:54:55.384 32573 32573 W System.err: 		at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:125)
05-28 22:54:55.386 32573 32573 W System.err: 		at okio.RealBufferedSource.request(RealBufferedSource.kt:206)
05-28 22:54:55.386 32573 32573 W System.err: 		at okio.RealBufferedSource.require(RealBufferedSource.kt:199)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Reader.nextFrame(Http2Reader.kt:89)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Connection$ReaderRunnable.invoke(Http2Connection.kt:618)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Connection$ReaderRunnable.invoke(Http2Connection.kt:609)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskQueue$execute$1.runOnce(TaskQueue.kt:98)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner.runTask(TaskRunner.kt:116)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner.access$runTask(TaskRunner.kt:42)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner$runnable$1.run(TaskRunner.kt:65)
05-28 22:54:55.387 32573 32573 W System.err: 		... 3 more

Strangely the app is otherwise working great, I even reinstalled it and it is loading all thumbnails very quickly from the Internet.
I am not sure why it is fetching my achievements, this seems unnecessary. By the way, achievements do not work for me because I have 30000 contributions, that;s a different issue but somehow there might be some kind of weird relationship between the two?

Screencast taken at the same time as the logcat above:

395.mp4

@rohit9625
Copy link
Collaborator Author

Thanks for the logs @nicolas-raoul
It seems like a timeout error. I'll look into why it happened; perhaps there's an issue on the backend side, such as the API not returning the correct response or needing additional information to process the request. However, for the frontend we should show an error message to the user, eg. Something went wrong or Please try after some time. What do you think?

@nicolas-raoul
Copy link
Member

Both great ideas! 👍

@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul,
I reviewed the MediaWiki API references and didn't find any useful information about marking an image for deletion. In our codebase, we are required to fetch achievements to construct the reason template by appending "No. of Articles using Image":

private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
val reason1Template = context.getString(R.string.uploaded_by_myself)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
.also { Timber.i("New Reason %s", it) }
}

It's appended to the reason string which is passed when making a deletion request:

return if (checkAccount()) {
okHttpJsonApiClient
.getAchievements(sessionManager.userName)
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }

As you mentioned, fetching achievements for a user with thousands of contributions is time-consuming. So, we should look for some other API that can get us articlesUsingImages so that Mark for Deletion won't take much time.

From the logs:

05-28 22:51:06.174 32573 32573 D ExploreMapPresenter: Means location changed slightly
[...]
[many unrelated messages about location and garbage collection]
[...]
05-28 22:54:55.361 32573  4297 V OkHttp  : <-- HTTP FAILED: java.net.SocketTimeoutException: timeout
05-28 22:54:55.369 32573 32573 E MediaDetailFragment: Error while nominating for deletion: timeout

It seems that it took about 3 minutes before throwing SocketTimeoutException.

The handleDeletionError method executed successfully which further disables the progress bar. However, if we navigated away from the MediaDetailsFragment and an exception is thrown when we were not on that screen then it's possible that UI won't be updated.
That might be the reason @parneet-guraya added this block here, which ensures that the activity!= null, and if it's null then the code to disable the progress bar doesn't execute:

    /**
     * Disables Progress Bar and Update delete button text.
     */
    private fun disableProgressBar() {
        activity?.run {
            runOnUiThread(Runnable {
                binding.progressBarDeletion.visibility = View.GONE
            })
        } ?: return // Prevent NullPointerException when fragment is not attached to activity
    }

@nicolas-raoul
Copy link
Member

Great research, thanks!

I don't think it is vital to know the number of articles using the image, to perform the nomination.

So, in case a timeout happens, how about proceeding with the nomination, just with the number of articles being unknown?

@rohit9625
Copy link
Collaborator Author

Great research, thanks!

I don't think it is vital to know the number of articles using the image, to perform the nomination.

So, in case a timeout happens, how about proceeding with the nomination, just with the number of articles being unknown?

Hmm, I think that also works but we also need to have a lesser timeout because currently, it's like 3 minutes which is enough to frustrate the user. Do know the reason for appending no. of articles in the reason or if there are any other APIs that can do the work?

@nicolas-raoul
Copy link
Member

Frankly, I don't really think we need that number. Do you know what commit added it? (you can use "git blame")

@rohit9625
Copy link
Collaborator Author

Frankly, I don't really think we need that number. Do you know what commit added it? (you can use "git blame")

Okay, I am looking for the reason why it was added.

@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul

This PR added the ReasonBuilder class in 2018. Also, you suggested to include "No. Of articles using this image" in this comment, which makes sense because Commons Admins must know how many articles using an image if that's being considered for deletion.

I also found these methods which return the file usage of a file:

suspend fun getFileUsagesOnCommons(
fileName: String?,
pageSize: Int
): FileUsagesResponse? {
return withContext(Dispatchers.IO) {
return@withContext try {

And this one:

suspend fun getGlobalFileUsages(
fileName: String?,
pageSize: Int
): GlobalFileUsagesResponse? {

Do you think we can utilize the above method which uses the query API of MediaWiki to obtain the same results, as fetching achievements is a time-consuming operation?

@nicolas-raoul
Copy link
Member

Sorry for the delay!

getGlobalFileUsages sounds good, hopefully it should return the result within a few seconds . :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Circular Progress bar keeps on moving even though nomination for deletion is successful
3 participants