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.

  • Method Summary

    Modifier and Type
    Method
    Description
    static void
    Registers a listener for states arriving from elsewhere.
    Internal.
    static AppState
    Builds a state from the route stack and the provider.
    static void
    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
    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
    Turns the framework off.
    static void
    Turns the framework on.
    static String
    The activity type this app publishes and answers to, which is the app's package name followed by .continuity.
    static String
    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
    The staleness limit in milliseconds, or 0 for none.
    static StateRelay
    The installed relay, or null.
    static AppState
    The state waiting to be restored: one that arrived from another device if there is one, otherwise the last checkpoint written on this device.
    The installed state provider, or null.
    static String
    The current continuation label, or null.
    static void
    Internal.
    static boolean
    Whether automatic restoration is on.
    static boolean
    Internal.
    static boolean
    Whether this platform can advertise the current state to the user's other devices while they are together.
    static boolean
    Whether the framework is on.
    static boolean
    Whether this platform can save and restore state at all.
    static void
    Asks the relay for anything newer than what is here, on a background thread.
    static void
    Internal.
    static void
    Removes a listener.
    static boolean
    Restores whatever getRestorableState() offers.
    static boolean
    Restores a specific state: hands its payload to the provider, then replays its route stack.
    static void
    Internal.
    static void
    setAutoRestore(boolean b)
    Whether a restorable state found at startup, or arriving from another device, is applied automatically.
    static void
    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
    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
    Installs the object that supplies and consumes the application half of the state, and enables the framework.
    static void
    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
    • 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

    • 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.

      Returns

      the current state, or null when the framework is not enabled

      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

    • 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. 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.