# Class Continuity

java.lang.Object
com.codename1.continuity.Continuity

public final class Continuity extends Object

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.

- ## Method Summary

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

  ### Methods inherited from class Object

  `clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## 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`: 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`: the listener

  - ### removeContinuationListener

    public static void removeContinuationListener(ContinuityListener l)

    Removes a listener.

    ###### Parameters

    - `l`: 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`: 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`: 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`: 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`: 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`: 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`: 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`: 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.