Class Continuity
Saves what the user was doing, brings it back when the app starts again, and -- where the platform or your own endpoint can carry it -- lets them pick it up on another device.
// in init()
Continuity.setStateProvider(new StateProvider() {
public Map<String, Object> saveState() {
Map<String, Object> m = new HashMap<String, Object>();
m.put("draft", draftField.getText());
return m;
}
public void restoreState(Map<String, Object> payload) {
pendingDraft = (String) payload.get("draft");
}
});
// in start()
if (!Continuity.restore()) {
Navigation.navigate("/home");
}
What is saved
Two halves. The framework contributes the com.codename1.router.Navigation stack, so an app
whose screens are declared with @Route gets them back with no code at all. Your
StateProvider contributes everything else. An app that navigates with new MyForm().show()
has no route stack to save -- those navigations are not addressable -- and restores from the
payload alone.
When it is saved
Continuously, not at shutdown. Every navigation marks the state dirty and a checkpoint is
written once per event loop pass, so by the time the operating system suspends the app the work
is already done. This is deliberate: on Android the platform blocks its own main thread until
the app's stop() returns, and an app that did its saving there would be paying for it on
every single suspend. Call checkpoint() directly after changing something the provider
reports but no navigation touched.
Getting it back
restore() returns true when it showed something, so start() reads as "restore, or else
begin". It is never called for you: an app that adopts this API decides where restoration fits
in its own launch, and an app that does not is completely unaffected.
Other devices
isContinuationSupported() reports whether this platform can advertise the current state to
the user's nearby devices; on Apple platforms it can, elsewhere it cannot and the call is a
no-op rather than an error. For everything the platform will not carry -- iOS to Android, two
devices that are never together -- set a StateRelay, which is your own endpoint. Codename One
runs no server for this, because deciding which states belong to the same person is your
account system's job.
Arriving states are offered to every ContinuityListener before anything happens, and this
device's own echo is never offered at all.
Zero cost when unused
Referencing this package is what makes the build declare the activity type on Apple platforms
and compile the native continuation handling in. An app that never touches
com.codename1.continuity gets none of it.
-
Method Summary
Modifier and TypeMethodDescriptionstatic voidRegisters a listener for states arriving from elsewhere.static ContinuityBridgeInternal.static AppStatecapture()Builds a state from the route stack and the provider.static voidWrites the current state now, and offers it to every enabled channel: storage always, the platform's continuation where there is one, and the relay if one is set.static voidclear()Forgets everything: the stored checkpoint, any parked arrival, the activity advertised to the user's other devices, and anything queued for the relay.static voiddisable()Turns the framework off.static voidenable()Turns the framework on.static StringThe activity type this app publishes and answers to, which is the app's package name followed by.continuity.static StringThis installation's device id, the value that lets a state be recognized as this device's own echo when it comes back through a relay.static longThe staleness limit in milliseconds, or 0 for none.static StateRelaygetRelay()The installed relay, or null.static AppStateThe state waiting to be restored: one that arrived from another device if there is one, otherwise the last checkpoint written on this device.static StateProviderThe installed state provider, or null.static StringgetTitle()The current continuation label, or null.static voidInternal.static booleanWhether automatic restoration is on.static booleanInternal.static booleanWhether this platform can advertise the current state to the user's other devices while they are together.static booleanWhether the framework is on.static booleanWhether this platform can save and restore state at all.static voidAsks the relay for anything newer than what is here, on a background thread.static voidInternal.static voidRemoves a listener.static booleanrestore()Restores whatevergetRestorableState()offers.static booleanRestores a specific state: hands its payload to the provider, then replays its route stack.static voidInternal.static voidsetAutoRestore(boolean b) Whether a restorable state found at startup, or arriving from another device, is applied automatically.static voidTest seam: installs a bridge, bypassing platform resolution.static voidsetMaxAge(long millis) How old a stored state may be and still be restored, in milliseconds.static voidInstalls the endpoint that carries state to devices the platform will not reach, and asks it immediately for anything newer than what is here.static voidInstalls the object that supplies and consumes the application half of the state, and enables the framework.static voidSets the label a receiving device may show before the user accepts a continuation -- "Draft to Dana", "Invoice 2031".
-
Method Details
-
enable
public static void enable()Turns the framework on. Called for you by
setStateProvider(StateProvider); call it directly when the route stack alone is all you need saved.Nothing before this call has any effect, which is what keeps an app that does not use this API behaving exactly as it always did.
-
disable
public static void disable()Turns the framework off. Checkpoints stop, the advertised activity is withdrawn, and arriving states are ignored. What is already in storage is left alone -- useclear()to remove it. -
isEnabled
public static boolean isEnabled()Whether the framework is on.
Returns
true when enabled
-
isSupported
public static boolean isSupported()Whether this platform can save and restore state at all. False only where there is no storage to write to, which in practice means before
Displayhas been initialized.Returns
true when state can be saved on this device
-
isContinuationSupported
public static boolean isContinuationSupported()Whether this platform can advertise the current state to the user's other devices while they are together.
Branch on this rather than on the platform name: it is true on Apple platforms today and the set is expected to grow, and a
com.codename1.ui.Display#getPlatformNametest would have to be found and changed when it does.Returns
true when continuation to a nearby device is supported
-
setStateProvider
Installs the object that supplies and consumes the application half of the state, and enables the framework.
Parameters
p: the provider, or null to contribute nothing beyond the route stack
-
getStateProvider
The installed state provider, or null.
Returns
the provider
-
addContinuationListener
Registers a listener for states arriving from elsewhere.
Parameters
l: the listener
-
removeContinuationListener
Removes a listener.
Parameters
l: the listener
-
setRelay
Installs the endpoint that carries state to devices the platform will not reach, and asks it immediately for anything newer than what is here.
Parameters
r: the relay, or null to stop using one
-
getRelay
The installed relay, or null.
Returns
the relay
-
setAutoRestore
public static void setAutoRestore(boolean b) Whether a restorable state found at startup, or arriving from another device, is applied automatically. On by default.
Turning it off leaves
restore()and every listener working exactly as before; what stops is the framework acting on its own. Use it when the decision to move the user is always the app's.Parameters
b: true to restore automatically
-
isAutoRestore
public static boolean isAutoRestore()Whether automatic restoration is on.
Returns
true when on
-
setTitle
Sets the label a receiving device may show before the user accepts a continuation -- "Draft to Dana", "Invoice 2031". Update it as the user moves around; it is read at every checkpoint.
Parameters
t: the label, or null for none
-
getTitle
The current continuation label, or null.
Returns
the label
-
setMaxAge
public static void setMaxAge(long millis) How old a stored state may be and still be restored, in milliseconds. Zero, the default, means no limit: an app the user opens after a month comes back where they left it, which is what they expect of it.
Set it when coming back is only meaningful for a while -- a checkout, a queue position, a booking hold.
Parameters
millis: the limit, or 0 for none
-
getMaxAge
public static long getMaxAge()The staleness limit in milliseconds, or 0 for none.
Returns
the limit
-
getDeviceId
This installation's device id, the value that lets a state be recognized as this device's own echo when it comes back through a relay. Stable across restarts.
Returns
the device id, never null
-
routeStackChanged
public static void routeStackChanged()Internal. Called bycom.codename1.router.Navigationafter every change to the navigation stack; schedules a checkpoint rather than taking one, so a burst of navigations costs a single write. -
checkpoint
public static void checkpoint()Writes the current state now, and offers it to every enabled channel: storage always, the platform's continuation where there is one, and the relay if one is set.
Cheap enough to call freely -- the state is a list of paths and a small map -- but it does touch storage, so it belongs at the end of a change rather than inside a loop.
Throws
IllegalArgumentException: when the provider returned a payload that cannot cross to another device
-
isCheckpointPending
public static boolean isCheckpointPending()Internal. Whether a checkpoint is owed -- something changed since the last one was written.
Exists so a port with a suspend callback can skip the event-thread round trip entirely in the common case, where the write-through already happened as the user navigated.
Returns
true when
checkpoint()would write something new -
capture
Builds a state from the route stack and the provider. Useful for sending one somewhere of your own.
The state itself is not stored -- only
checkpoint()does that -- but the sequence counter it allocates is remembered, so states keep a rising order across a relaunch even for an application that never checkpoints.Returns
the current state, or null when the framework is not enabled
Throws
IllegalArgumentException: when the provider returned an unrepresentable payload
-
getRestorableState
The state waiting to be restored: one that arrived from another device if there is one, otherwise the last checkpoint written on this device.
Returns
the state, or null when there is nothing to restore or it is older than
getMaxAge() -
restore
public static boolean restore()Restores whatever
getRestorableState()offers.Written to read as "restore, or else begin":
public void start() { if (!Continuity.restore()) { Navigation.navigate("/home"); } }Returns
true when a form was shown, so the caller should not show its own
-
restore
Restores a specific state: hands its payload to the provider, then replays its route stack.
This is the second half of the "ask first" pattern -- a
ContinuityListenerthat returned false to hold a state calls this once the user accepts it.Parameters
state: the state, or null
Returns
true when a form was shown
-
pollRelay
public static void pollRelay()Asks the relay for anything newer than what is here, on a background thread. Returns immediately.
Worth calling when the app comes back to the foreground: a continuation reaches a nearby device on its own, but a relay is only read when something asks it to be.
-
clear
public static void clear()Forgets everything: the stored checkpoint, any parked arrival, the activity advertised to the user's other devices, and anything queued for the relay.
Belongs on your logout path. The advertised activity outlives the app's own screen, so an account's work would otherwise stay offered to the devices around it after the user signed out -- and a queued relay publish would have gone out later under whatever credentials the relay returned by then, which after a logout is the NEXT account's.
One thing it cannot undo: a relay request already on the wire when this is called. Nothing in this process can recall that. What this guarantees is that nothing follows it.
-
getActivityType
The activity type this app publishes and answers to, which is the app's package name followed by
.continuity.Fixed by the build, which declares the same string to the platform in
NSUserActivityTypes; the two have to agree or the operating system refuses to deliver anything. Exposed because an app that also publishes activities of its own needs to know which one is this framework's, and because it is the first thing to check when a continuation never arrives.Returns
the activity type, never null
-
setBridge
Test seam: installs a bridge, bypassing platform resolution.
Parameters
b: the bridge, or null to resolve from the platform again
-
bridgeForSyncedStore
Internal. The resolved platform bridge, for
com.codename1.continuity.sync, which is a package of its own so that its entitlement is earned separately. Application code usescom.codename1.continuity.sync.SyncedStore.Returns
the bridge, or null when this port has none
-
installSyncedStoreCallback
public static void installSyncedStoreCallback()Internal. Installs the inbound seam WITHOUT turning continuity on. Application code uses
com.codename1.continuity.sync.SyncedStore.addChangeListener.com.codename1.continuity.syncis a package of its own precisely so that its cost is earned separately, andenable()is not a cost the synced store asks for: it makes every route change checkpoint, and a checkpoint advertises the app's navigation to the devices around it over Handoff. Registering a store listener used to call it, so an application that wanted a key/value store the user's devices share -- and nothing else -- was opted into broadcasting its route stack.The store's own notification does not go through
enabled(see Callback.syncedStoreChanged), which is what lets the listener work with continuity still off. -
refreshBridge
public static void refreshBridge()Internal. Re-installs the framework's inbound seam on whatever bridge the port now returns. Called by a port that swaps its bridge while the app is running, which only the simulator does -- a device's bridge is created once and lives as long as the process.
-