Skip to content

Make candle commands time-aware and order Android option lists (PRO-4970) - #22

Closed
furkando wants to merge 2 commits into
mainfrom
fix/time-aware-commands
Closed

furkando wants to merge 2 commits into
mainfrom
fix/time-aware-commands

Conversation

@furkando

@furkando furkando commented Sep 21, 2026 •

Copy link
Copy Markdown

Summary

Hardening for the native dataset, prompted by a report on perps-fe-mobile #2236 (native KLine, Android, return to a retained Trade tab: a candle disappeared from the canvas while its OHLC was still correct in the JS data).

Two properties of the native side make that possible:

  1. updateLastCandlestick and addCandlesticksAtTheEnd are position-based on both platforms. updateLastCandlestick overwrites the last native bar without comparing timestamps, so whenever the native array is behind the JS data (commands dropped while the view was detached, or a bar appended while a replacement was in flight), the first live update overwrites an older candle and every later update lands one slot off until the next full replacement.
  2. Android parses each optionList on a new thread per call and swaps modelArray from that thread, while commands apply immediately on the UI thread. A command can therefore run before an earlier replacement, and two replacements can finish out of order. iOS applies the prop synchronously.

Changes:

  • Both platforms: commands compare bar timestamps. Same time as the last native bar replaces it, a newer bar is appended, an older bar is ignored. addCandlesticksAtTheEnd skips bars the array already has. Replacements and commands can now arrive in either order without dropping or overwriting a candle. When the native array and the JS data agree (the normal case) behaviour is unchanged.
  • Android: option lists are parsed in order on a per-chart single-thread executor with a generation counter, so a superseded result is discarded. Bars delivered by live commands while a snapshot is still being parsed are recorded and merged back by time on the UI thread when it lands, so a late replacement cannot remove them. The parsing code itself is unchanged; the executor is shut down when the view is dropped.
  • Missing indicator lists are reused only for an update to the same bar, not for a newer bar that is appended.
  • iOS: an append through updateLastCandlestick reloads the content size; an append call that only replaces the last bar still redraws.
  • README documents the contract.

No JS, prop or command API change; consumers need no code change. It is native code, so it ships with a new binary, not OTA.

Linear ticket: PRO-4970

Validation

  • 29 JS command tests pass (they do not exercise native code).
  • Swift file passes swiftc -parse; the two Java files show no syntax errors under javac (only missing React Native dependency symbols, as expected outside a Gradle build).
  • Not yet compiled in a Gradle/Xcode build and not run on a device. The original report is intermittent and was not reproduced on the current mobile head (iOS simulator by me, Android device by Furkan), so this is a robustness fix rather than a confirmed repro-and-fix.
  • To validate: build the example or the mobile app with the prerelease on both platforms; on Android return to a retained native chart 2-3 s before a 1m bar boundary, repeat about ten times, and compare the canvas with a fresh reload; run the usual interval/market/background regression on both platforms.

Author Checklist

  • PR tested locally
  • PR tested on preview

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

📋 PR Checklist Validation Failed

⚠️ This PR cannot be merged until the following issues are resolved:

  • ❌ Checklist: 0/2 items checked (2 remaining)
  • ❌ Linear ticket: Linear ticket format is invalid: "PRO-4970 (follow-up)"

What to do:

  1. Review the PR description
  2. Check all applicable boxes (- [x])
  3. If an item doesn't apply, check it and add a note explaining why
  4. Add a valid Linear ticket (format: PRO-123 or https://linear.app/...)
  5. Push an update or edit the PR description to re-trigger this check

This check ensures all PR requirements are met before merging. If you believe this check should not apply to your PR, please discuss with the team.

@linear

linear Bot commented Sep 21, 2026

Copy link
Copy Markdown

PRO-4970

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

🚀 Prerelease Published

Version: 0.22.0-furkando.bda3095.fix-time-aware-commands
Tag: fix-time-aware-commands

Update package.json:

"@elliottech/react-native-kline-view": "0.22.0-furkando.bda3095.fix-time-aware-commands"

Previous prereleases for this PR have been cleaned up. This prerelease will be automatically cleaned up when the PR is merged.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Devin Review

Comment on lines +86 to +88
if (generation != containerView.optionListGeneration) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Late option replacement drops live candle

When updateLastCandlestick appends before pending optionList work finishes, the replacement overwrites that candle. optionListGeneration tracks only option calls, so the live candle disappears on both platforms.

Learn more

Option parsing runs asynchronously, while candle commands mutate the current native array immediately. The generation check rejects an option result only when another option call supersedes it. A candle command does not change the generation, so an older snapshot can still replace modelArray after that command. Android performs the replacement in reloadOptionList, and iOS has the same replacement behavior in reloadOptionList. Timestamp checks inside the commands only protect the opposite ordering, where the replacement lands first.

Example: The native array ends at 10:00. An option snapshot ending at 10:00 starts parsing, then updateLastCandlestick appends 10:01. The pending snapshot subsequently replaces the array, removing 10:01 even though no newer option call changed the generation.

Recommended fix: Parse off-thread, but serialize dataset application and candle commands on the UI thread. Before replacing modelArray, reconcile bars added or updated since the snapshot was scheduled by timestamp. Apply the equivalent ordering or reconciliation on iOS.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid for Android, fixed in bda3095. A bar delivered by a live command while an option list is still being parsed is now recorded per chart and merged back by time on the UI thread when that snapshot lands (onOptionListScheduled / onOptionListApplied / mergeBar), so a late replacement can no longer remove it. Only bars delivered after the latest option list was scheduled are replayed, so bars from a previous market or timeframe are never carried into a new dataset. Not applicable on iOS: optionList is applied synchronously in the prop setter there, so there is no pending window between a command and a replacement.

Comment on lines +378 to +379
if (incomingTime > 0 && currentTime > 0 && incomingTime > currentTime) {
configManager.modelArray.add(newEntity);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 New candles inherit stale indicators

When newer updateLastCandlestick data omits indicators, the appended model retains the previous candle’s values. Both platforms render stale overlays for the new candle.

Learn more

The existing fallback copies empty indicator lists from the current last candle because this command previously replaced that same candle. The new timestamp branch changes the operation to an append after those copies have already happened. The iOS branch follows the same sequence in updateLastCandlestick. Values derived for one candle are therefore attached to a different timestamp.

Example: The 10:00 candle has MA5 100 and RSI 45. A 10:01 update contains OHLCV but no maList or rsiList. The new 10:01 candle is appended with MA5 100 and RSI 45 instead of values for 10:01 or empty indicators.

Recommended fix: Decide whether the command will replace or append before preserving indicators. Preserve missing lists only for equal timestamps. For a newer timestamp, leave missing lists empty or calculate them from the expanded dataset on both platforms.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid, fixed in bda3095 on both platforms. The indicator fallback now runs only when the update is for the same bar (sameBar); a newer bar that gets appended keeps its own lists, empty if the caller sent none.

Comment on lines +65 to +66
private static final java.util.concurrent.ExecutorService OPTION_LIST_EXECUTOR =
java.util.concurrent.Executors.newSingleThreadExecutor();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 All charts share one parser

The static OPTION_LIST_EXECUTOR serializes unrelated charts. One expensive option list delays every chart, while ordering requirements remain per view.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed, fixed in bda3095. The executor is now a per-chart field on HTKLineContainerView and is shut down in onDropViewInstance, so one chart's large option list cannot delay another chart.

@furkando furkando closed this Sep 21, 2026
@furkando

Copy link
Copy Markdown
Author

For the record: shelved. The original report was a single intermittent sighting that nobody could reproduce on the current mobile build (iOS simulator and an Android device). The impact is visual only and clears on the next timeframe, market or settings change. The branch fix/time-aware-commands stays in place so this can be reopened if the issue is ever seen in production; the analysis is in the description and the review threads.

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