public final class SyncedStore
- Object
- SyncedStore
A small key/value store the platform carries between the devices one person signed in to, without them ever being in the same room.
This is the slow, patient half of continuity. com.codename1.continuity.Continuity hands the
current activity to a device that is here, now; this keeps a handful of durable settings –
which theme, which sort order, which tutorial they already dismissed, the id of the document
they are working through – in step across everything they own.
SyncedStore.put("sortOrder", "byDate");
String order = SyncedStore.get("sortOrder", "byName");
What it is not
Not storage. Not a database, not a cache, and not a place for anything the app cannot cheerfully do without: the platform decides when to sync, the user can turn the whole mechanism off, and a device that has never been online has an empty store. Treat every read as “the value, or the default” – which is why there is no read without a default.
Not secret. The contents leave the device and are held by the platform on the user’s behalf.
Credentials belong in com.codename1.security.SecureStorage.
Not large. The platform imposes a total size and a key count, both small; put reports a
failure to write rather than pretending it stored something.
What it costs
Referencing this package is what makes an iOS build ask for the entitlement that gives the app
a synced store, which in turn requires the capability to be enabled on the App ID. That is why
it is a package of its own: an app that wants continuation to a nearby device and nothing else
should not have to arrange an entitlement to get it. Where the platform has no such store –
Android, desktop, the browser – isSupported() is false and every call here is an inert
no-op, so the sensible shape is a synced value with a local default behind it.
Threading
Called on the event dispatch thread, like the rest of the toolkit. Codename One is single threaded by design – one thread on each side of a native boundary, marshalled at the boundary rather than locked – and this class follows that rule rather than making an exception to it.
It is worth stating because the simulation behind isSupported() == true on a desktop keeps
its key index as a second stored value: two threads writing different NEW keys at once would
each read that index, add their own key, and write it back, so one of them would vanish from
keys() while its value stayed readable by name. The platform stores have no such structure
and no such exposure. The answer is the toolkit’s answer everywhere else – call it from the
event thread, and use com.codename1.ui.Display#callSerially(Runnable) if you are on another
one – not a lock inside a framework that does not have them.
Methods
public static boolean isSupported() | Whether this platform has a store that follows the user between devices. |
public static boolean put(String key, String value) | Writes a value, replacing any previous value for the key. |
public static String get(String key, String def) | Reads a value. |
public static void remove(String key) | Deletes a key. |
public static String[] keys() | Every key currently in the store, in no particular order. |
public static void addChangeListener(SyncedStoreListener l) | Registers a listener for changes made on the user’s other devices. |
public static void removeChangeListener(SyncedStoreListener l) | Removes a listener. |
public static void notifyChanged() | Internal. |
Inherited methods
Method details
isSupported
public static boolean isSupported()Returns
put
public static boolean put(String key, String value)Writes a value, replacing any previous value for the key.
Not gated on isSupported()
That was the THIRD layer this was wrong in.
isSupported() asks whether this build has a store that follows the user between devices, which is the right question for an application deciding whether to offer the feature and the wrong gate for the calls themselves. On iOS the store is a LOCAL persistent one whose cloud propagation is asynchronous, so reads and writes work and reach other devices later.
The gate was on all three of IOSNative.m, IOSContinuityBridge and here. Removing it from the first two changed nothing, because this one still made every call unreachable – a fix verified at one layer and dead at the next. Each bridge answers for itself when there is no store: the Android one returns null and no-ops, the iOS one checks its own port flag, and the simulation reads local preferences.
Parameters
keyString- the key, must not be null or empty
valueString- the value, must not be null; use
remove(String)to delete
Returns
get
public static String get(String key, String def)Reads a value.
There is no overload without a default on purpose: the store is genuinely empty on a device that has not synced yet, so every read has to have an answer for that.
Parameters
keyString- the key, must not be null or empty
defString- what to return when the key is absent or the store is unavailable
Returns
defremove
public static void remove(String key)Parameters
keyString- the key, must not be null or empty
keys
public static String[] keys()Returns
addChangeListener
public static void addChangeListener(SyncedStoreListener l)Parameters
lSyncedStoreListener- the listener
removeChangeListener
public static void removeChangeListener(SyncedStoreListener l)Parameters
lSyncedStoreListener- the listener
notifyChanged
public static void notifyChanged()SyncedStoreListener instead.