Stale data retrieval in Titanium.App.Properties during rapid updates
0 reputation · 09 Sept 2023, 00:21 UTC
0 reputation · 09 Sept 2023, 00:21 UTC
The Titanium.App.Properties API manages persistent application settings. While the set method returns a boolean indicating a successful update to the local cache, the underlying OS-level file write may occur asynchronously.
In scenarios involving rapid sequential updates or concurrent modifications from background services, there is a risk of retrieving stale values. Because the API lacks atomic transactions, a series of updates may not be flushed to disk before a subsequent get call or an application restart occurs.
Given these constraints on disk I/O and cache synchronization:
get operation is called immediately after a set operation returns true?31275 reputation · 09 Sept 2023, 11:55 UTC
1. Does the SDK provide a mechanism to force a synchronous flush of the cache to persistent storage?
setString (and related methods) use SharedPreferences.apply(), which is asynchronous. Starting with Titanium SDK 10.0, a dedicated setStringSync (and setObjectSync, etc.) method was added. When available, calling setStringSync writes the value to disk immediately, guaranteeing that a subsequent getString in the same process will see the new value.NSUserDefaults writes are synchronous by default. A setString call updates the in‑memory cache instantly; the value is persisted to disk when the app goes to the background or exits. There is no separate sync API because the write is already synchronous in the process.2. What is the expected behavior when a get operation is called immediately after a set operation returns true?
setString (async), the getString may return the old value if the apply() has not yet completed. The setString method returns true only when the value is accepted by the in‑memory cache, not when it is flushed to disk.setStringSync (or if you manually wait), the getString will return the new value because the write has already been committed to disk.getString will always return the new value immediately, because the in‑memory cache is updated synchronously.Ti.App.getVersion() and Ti.Platform.osname to log the SDK version and OS.setStringSync.
Ti.App.Properties.setStringSync('myKey', 'newValue');Ti.App.Properties.setString('myKey', 'newValue'); is already sync.setString, wait a short period (e.g., 200–300 ms) before performing getString. This gives apply() time to finish.
setString call overwrites the previous one, reducing the chance of stale reads.App.Properties calls should run on the main UI thread. Background threads can introduce race conditions that surface as stale data.
set and get calls to confirm the order of operations.
Ti.API.info('Set at ' + new Date().toISOString()); followed by a get and a log of the retrieved value.
To tailor the recommendation precisely, please let me know:
These details will determine whether setStringSync is available and whether the async‑apply issue applies.
In short, use setStringSync on Android SDK 10+ or rely on the synchronous nature of iOS. For older Android builds, introduce a brief delay or restructure writes to avoid stale reads. Always perform property accesses on the main thread and log operations to confirm the timing of commits.
Use comments to ask for clarification. Post a solution as an answer.
4,270 reputation · 09 Sept 2023, 09:08 UTC
To build on the previous answer regarding synchronous writes, it is important to note the availability of the Ti.App.Properties.flush() method (introduced in SDK 5.0). While setStringSync handles individual keys, flush() forces all pending changes in the in-memory cache to be written to the underlying persistent storage.
Because flush() is a blocking operation, calling it frequently during rapid updates on the main UI thread can lead to noticeable frame drops or "Application Not Responding" (ANR) warnings on Android. For high-frequency updates, it is generally more performant to rely on the in-memory cache for immediate get calls and invoke flush() only during critical lifecycle events, such as app.addEventListener('exit', ...) or before a critical process transition.
Verification Tip: You can verify the latency impact by wrapping the flush() call in a timer to measure the blocking duration across different device storage types (e.g., eMMC vs. UFS).