Skip to content

fix(android): use float division for download progress ratio - #482

Open
armamini wants to merge 1 commit into
RonRadtke:masterfrom
armamini:fix/android-progress-count-throttle
Open

fix(android): use float division for download progress ratio#482
armamini wants to merge 1 commit into
RonRadtke:masterfrom
armamini:fix/android-progress-count-throttle

Conversation

@armamini

@armamini armamini commented Aug 4, 2026

Copy link
Copy Markdown

Problem

On Android, the count option of .progress() is silently ignored for downloads:

ReactNativeBlobUtil.config({ path: dest })
  .fetch('GET', url)
  .progress({ count: 10 }, (written, total) => { /* ... */ });

No matter what count is set to, callbacks arrive on the interval schedule only. iOS honours count correctly, so the same JS produces different callback cadence per platform.

Root cause

Three sites compute the progress ratio using long / long, which is integer division. The result truncates to 0 for the entire transfer and only reaches 1 on the final read:

// ReactNativeBlobUtilFileResp.java:141
float progress = (contentLength() != -1) ? bytesDownloaded / contentLength() : ...;

// ReactNativeBlobUtilDefaultResp.java:70
reportConfig.shouldReport(bytesRead / contentLength())

// ReactNativeBlobUtilReq.java:220  (DownloadManager polling)
float progress = (total > 0) ? written / total : 0;

Assigning to a float happens after the truncation, so the cast never helps.

ReactNativeBlobUtilProgressConfig.shouldReport gates the count throttle on the ratio being positive:

public boolean shouldReport(float progress) {
    boolean checkCount = true;
    if (count > 0 && progress > 0)
        checkCount = Math.floor(progress * count) > tick;
    boolean result = (System.currentTimeMillis() - lastTick > interval) && enable && checkCount;
    ...
}

Since progress is always 0, count > 0 && progress > 0 is never true, checkCount keeps its default of true, and the count bucket is never consulted. Reporting degrades to interval-only.

The upload path already gets this right — ReactNativeBlobUtilBody.java:438 reads config.shouldReport((float) written / contentLength) — so the intended semantics are unambiguous; the three download sites simply missed the cast.

Fix

Cast to float before dividing, at all three sites:

  • Response/ReactNativeBlobUtilFileResp.java — download-to-file responses
  • Response/ReactNativeBlobUtilDefaultResp.java — in-memory responses
  • ReactNativeBlobUtilReq.java — DownloadManager polling loop

DefaultResp additionally guards the chunked case, where contentLength() is -1 and the old expression yielded a negative ratio. Old and new code both take the interval-only path there, so this is a clarification rather than a behaviour change.

Compatibility

Usage Before After
.progress(fn) (count -1) interval-only unchanged
.progress({ interval: N }, fn) interval-only unchanged
.progress({ count: N }, fn) count silently ignored count honoured
Upload progress already correct unchanged

No public API change. iOS and Windows are untouched.

Verification

Download a file large enough to span many reads with .progress({ count: 10, interval: 0 }, fn) and count the callbacks: previously they tracked the interval only; now they arrive at roughly each 10% boundary, and written advances monotonically to Content-Length.

 was always receiving 0 for in-flight
downloads because the ratio was computed with long/long integer
division, which truncates to 0 until the transfer completes.

The count-based throttle in ReactNativeBlobUtilProgressConfig is gated
on , so that guard never became true and
 stayed at its default of . The result is that
 was silently ignored on Android and every
consumer fell back to interval-only reporting.

Cast to float at the three sites that compute the ratio:

- ReactNativeBlobUtilFileResp: download-to-file responses
- ReactNativeBlobUtilDefaultResp: in-memory responses
- ReactNativeBlobUtilReq: DownloadManager polling loop

DefaultResp additionally guarded against chunked responses, where
contentLength() is -1 and the old expression produced a negative
ratio. Both the old and new code take the interval-only path there, so
this is a clarification rather than a behaviour change.

Default usage (, count -1) is unaffected.
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.

1 participant