public final class Continuity

  1. Object
  2. 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.

Threading

Call this class from the event dispatch thread, like the rest of the toolkit. Codename One is single threaded: every method here runs on the EDT and every field it keeps is owned by the EDT, so there is nothing to synchronize and nothing that can interleave.

Two kinds of foreign thread exist, and both hand over at the boundary rather than reaching in. A port delivering a continuation arrives on the platform’s own thread and is marshalled with com.codename1.ui.Display#callSerially. The relay’s publish and fetch are blocking calls that must not sit on the EDT, so they run on a worker – one that is handed the state it needs as a parameter, touches no field of this class, and returns its answer through callSerially as well. That is the whole concurrency design, and it is deliberately the toolkit’s: one thread on each side of a boundary, never two on the same state.

Methods

public static void enable()Turns the framework on.
public static void disable()Turns the framework off.
public static boolean isEnabled()Whether the framework is on.
public static boolean isSupported()Whether this platform can save and restore state at all.
public static boolean isContinuationSupported()Whether this platform can advertise the current state to the user’s other devices while they are together.
public static void setStateProvider(StateProvider p)Installs the object that supplies and consumes the application half of the state, and enables the framework.
public static StateProvider getStateProvider()The installed state provider, or null.
public static void addContinuationListener(ContinuityListener l)Registers a listener for states arriving from elsewhere.
public static void removeContinuationListener(ContinuityListener l)Removes a listener.
public static void setRelay(StateRelay r)Installs the endpoint that carries state to devices the platform will not reach, and asks it immediately for anything newer than what is here.
public static StateRelay getRelay()The installed relay, or null.
public static void setAutoRestore(boolean b)Whether a restorable state found at startup, or arriving from another device, is applied automatically.
public static boolean isAutoRestore()Whether automatic restoration is on.
public static void setTitle(String t)Sets the label a receiving device may show before the user accepts a continuation – “Draft to Dana”, “Invoice 2031”.
public static String getTitle()The current continuation label, or null.
public static void setMaxAge(long millis)How old a stored state may be and still be restored, in milliseconds.
public static long getMaxAge()The staleness limit in milliseconds, or 0 for none.
public static String 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.
public static boolean restoreSessionEnded()Internal.
public static void routeStackChanged()Internal.
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.
public static boolean isCheckpointPending()Internal.
public static AppState capture()Builds a state from the route stack and the provider.
public static AppState 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.
public static boolean restore()Restores whatever getRestorableState() offers.
public static void acknowledge(AppState state)Records that the application has handled state itself, so it is not offered again.
public static boolean restore(AppState state)Restores a specific state: hands its payload to the provider, then replays its route stack.
public static void pollRelay()Asks the relay for anything newer than what is here, on a background thread.
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.
public static String getActivityType()The activity type this app publishes and answers to, which is the app’s package name followed by .continuity.
public static void setBridge(ContinuityBridge b)Test seam: installs a bridge, bypassing platform resolution.
public static ContinuityBridge bridgeForSyncedStore()Internal.
public static void installSyncedStoreCallback()Internal.
public static void refreshBridge()Internal.

Inherited methods

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 – use clear() 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 Display has 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#getPlatformName test would have to be found and changed when it does.

Returns

true when continuation to a nearby device is supported

setStateProvider

public static void setStateProvider(StateProvider p)
Installs the object that supplies and consumes the application half of the state, and enables the framework.

Parameters

p StateProvider
the provider, or null to contribute nothing beyond the route stack

getStateProvider

public static StateProvider getStateProvider()
The installed state provider, or null.

Returns

the provider

addContinuationListener

public static void addContinuationListener(ContinuityListener l)
Registers a listener for states arriving from elsewhere.

Parameters

l ContinuityListener
the listener

removeContinuationListener

public static void removeContinuationListener(ContinuityListener l)
Removes a listener.

Parameters

l ContinuityListener
the listener

setRelay

public static void setRelay(StateRelay r)
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 StateRelay
the relay, or null to stop using one

getRelay

public static StateRelay 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 boolean
true to restore automatically

isAutoRestore

public static boolean isAutoRestore()
Whether automatic restoration is on.

Returns

true when on

setTitle

public static void setTitle(String t)
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 String
the label, or null for none

Throws

java.lang.IllegalArgumentException
when the label is longer than a stored checkpoint can hold

getTitle

public static String 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 long
the limit, or 0 for none

getMaxAge

public static long getMaxAge()
The staleness limit in milliseconds, or 0 for none.

Returns

the limit

getDeviceId

public static String 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

restoreSessionEnded

public static boolean restoreSessionEnded()

Internal. Called by com.codename1.router.Navigation between the route factories of a restore, and again before it shows the rebuilt screen. True once a factory has ended the session – so the rebuild stops instead of running every remaining factory against an account that has just signed out.

The lifecycle check in restore() runs only after restoreStack() has returned, which is far too late for this: by then those factories have constructed their forms, and whatever they queried or wrote for the signed-out account is done. Emptying the stack afterwards undoes none of it.

A direct call for the reason routeStackChanged() gives: it answers false immediately unless a restore is actually in progress, and a listener registry here would be public API earned by one internal caller.

Returns

true when the session that the restore in progress began in has ended

routeStackChanged

public static void routeStackChanged()
Internal. Called by com.codename1.router.Navigation after 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

public static AppState 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. When that counter cannot be written this returns null, because a state carrying a number this device will hand out again is unsafe to send.

Returns

the current state, or null when the framework is not enabled or the sequence counter could not be stored – see below for why the second one is refused rather than returned

Throws

IllegalArgumentException
when the provider returned an unrepresentable payload

getRestorableState

public static AppState 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

acknowledge

public static void acknowledge(AppState state)

Records that the application has handled state itself, so it is not offered again.

For the pattern ContinuityListener documents: do the work yourself and return false. That path never reaches restore(), so nothing recorded the acknowledgement durably – the sequence stayed in this process only, and after a relaunch the relay’s unchanged document was accepted again and the listener repeated its side effects, against the act-once guarantee.

Deliberately NOT inferred from a false return. False also means “keep it, I will prompt and call restore() when the user accepts”, and marking that handled immediately would lose the state if the process died before they answered – which is the same data loss as marking a parked state. The two intentions are different, so the application says which it means.

Parameters

state AppState
the state that has been dealt with

restore

public static boolean restore(AppState state)

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 ContinuityListener that returned false to hold a state calls this once the user accepts it.

Parameters

state AppState
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, PAIRED WITH disable(), and enable() on the way back in.

This call alone is not a logout. It forgets the account’s data and deliberately leaves continuity switched ON, because forgetting state and turning the feature off are two different things: an application is entitled to do the first without the second – a “start over” that is not a sign-out – and making this imply the other would stop continuity dead for every app that used it that way and never called enable() again.

The consequence, if it is not paired: a continuation that arrives while the login screen is up reaches a framework that is still listening and still enabled. It is a valid arrival by every test this class makes – it came AFTER the clear, so it is not from the session that ended – so the signed-out account’s routes and payload are restored over the login screen and written to storage. disable() is what closes that gap; enable() at login reopens it.

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

public static String 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

public static void setBridge(ContinuityBridge b)
Test seam: installs a bridge, bypassing platform resolution.

Parameters

b ContinuityBridge
the bridge, or null to resolve from the platform again

bridgeForSyncedStore

public static ContinuityBridge 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 uses com.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.sync is a package of its own precisely so that its cost is earned separately, and enable() 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.