
You are halfway through a draft on your phone when you sit down at your computer. The words are already written. The document is already open. Starting again should not be the price of changing screens.
Finishing the draft raises another familiar task: getting its output into another application. Copy and paste can help, but dragging the document onto its destination is often the action you reach for first.
This week we added cross-device continuity and native drag and drop to Codename One. The first carries a description of the task to another process or device. The second offers its content to another application through the operating system. Both start by separating useful data from the screen that happens to show it.
Cross-device continuity: save the work, not the screen
PR #5663 adds com.codename1.continuity for local restoration, Apple Handoff, and application-owned relays.
The router already represents navigation as a stack of paths. Those paths can be serialized and reconstructed without animating through every intermediate screen. A StateProvider adds the application data needed to resume the task.
This example assumes draftField is the application’s TextArea and the routes have already been registered:
Continuity.setStateProvider(new StateProvider() {
public Map<String, Object> saveState() {
Map<String, Object> state = new HashMap<String, Object>();
state.put("draft", draftField.getText());
return state;
}
public void restoreState(Map<String, Object> state) {
Object draft = state.get("draft");
draftField.setText(draft instanceof String ? (String) draft : "");
}
});
Registering the provider enables continuity. The saved map should describe reconstructible application state, not retain UI objects or carry credentials to another device. Keep the payload small and decide explicitly whether sensitive draft contents belong in it.
Navigation schedules a checkpoint once per event-loop pass. Data edits that do not navigate can request one too:
Continuity.setTitle("Draft message");
Continuity.checkpoint();
Saving continuously avoids making shutdown the only opportunity to save. It also avoids placing all the persistence work into a suspend callback that may be blocking a platform thread.
Restore after the account is known
Restoration is explicit. The framework cannot decide whether your login flow, encryption setup, or account selection has finished.
import com.codename1.continuity.Continuity;
import com.codename1.router.Navigation;
// Run after authentication and route registration are complete.
if (!Continuity.restore()) {
Navigation.navigate("/home");
}
A restored route identifies work; it does not authorize access to it. The application must still check that the current account can open the document or perform the action named by that route.
Handoff and an application relay are separate ways to transport the checkpoint. In both cases, the receiving application checks the state against the signed-in account.
The platform boundaries are visible
| Operation | Availability in this work |
|---|---|
| Local state and route-stack restoration | iOS, macOS, Android, desktop/simulator, JavaScript |
| Platform device continuation | Apple Handoff; simulator support for testing |
| Platform key-value synchronization | Apple iCloud; simulator support for testing |
| Application-defined cross-device relay | StateRelay, backed by your endpoint |
Continuity.isContinuationSupported() lets the application check the platform facility. Apple devices use Handoff. An application relay can use your existing accounts to carry state between Android, Apple devices, or a browser.
RestStateRelay supplies the HTTP starting point for your server. You provide the endpoint and decide how accounts, retention, and conflicting checkpoints work.
The iCloud key-value store lives in com.codename1.continuity.sync. That package requires the appropriate entitlement on the Apple App ID. Basic restoration and Handoff do not acquire that requirement just because the app wants to resume a screen.
A received activity need not interrupt the current task
An application can turn off automatic restoration with Continuity.setAutoRestore(false) and use a continuation listener to offer a choice. Declining should call Continuity.acknowledge(state) if the app does not want the same unchanged relay state offered again after every relaunch.
For short-lived workflows, Continuity.setMaxAge(...) limits how old a checkpoint may be. A checkout or confirmation screen should not resume indefinitely with stale assumptions about price, availability, or authorization.
Logout closes the restoration path
Clear saved state and disable arrivals when the account signs out:
Continuity.clear();
Continuity.disable();
Both calls matter. Clearing stored state alone leaves continuity enabled, so a later activity could restore the previous account’s route over the login screen. Re-enable it only after the next account is ready. The guide’s continuity examples include relay setup, user confirmation, expiry, and logout.
Android task removal handles a different part of ending a session, covered in the security follow-up. Neither operation replaces server-side credential revocation.
The Apple builder also combines the continuity and App Intents declarations into a single NSUserActivityTypes array. Use a builder with the paired continuity integration when testing the device-to-device flow.
Native drag and drop: bring other apps into the workflow
A checkpoint carries a task to a new process or device. A drag carries a document, image, or selection into another application. Both need a payload the receiver can understand without access to the source’s live UI objects.
PR #5662 adds native operating-system drag and drop beside the lightweight setDraggable and setDropTarget API. Its payload is the same ClipboardContent used for copy and paste. The existing in-form drag behavior remains available.
Reuse the representations you can already copy
A drag target may want plain text, HTML, or a list of files. The source can offer several representations and let the receiver request one it understands. That is already the clipboard’s job.
The new API separates that payload from the operation’s allowed actions, drag image, and completion. A file source and destination can be configured like this, with paths holding existing export paths and inbox identifying the receiving component:
Label file = new Label("report.pdf");
file.setNativeDragOperation(NativeDragOperation.createFileDrag(paths));
inbox.setNativeDropTarget(true);
inbox.setAcceptedDropMimeTypes(ClipboardContent.MIME_FILE);
inbox.addNativeDropListener(event -> {
String[] received = ((NativeDropEvent) event).getFiles();
if (received != null) {
queueImport(received);
}
});
queueImport belongs to the application. It should validate the input and move expensive parsing off the event dispatch thread. Accepting a file MIME type is a format filter, not proof that a document is safe to parse or trusted to execute.
A provider can still run when the drag starts
ClipboardContent.setDataProvider lets a representation supply its data through a callback. The native port determines when that callback runs. An abandoned drag can still pay for an expensive export.
| Port or representation | When the provider may run |
|---|---|
| JavaSE | When a receiver requests the representation |
| Android | At drag start, while building the complete ClipData |
| iOS file lists | At drag start, because UIKit needs the item count |
Other iOS representations can be deferred, but code should not treat every representation as lazy. Keep providers cheap enough to run at drag start. Prepare or cache expensive exports before enabling the gesture instead of relying on cancellation to avoid the work.
A move adds an ownership decision. The source must wait for native completion before acting on an accepted move. Starting a drag is not confirmation that another application received the bytes, so deleting the source at that point risks data loss.
The drag callback cannot wait for the wrong thread
Native drops arrive on the platform’s drag thread. Codename One resolves the target there using accepted MIME types and actions, then delivers application callbacks on the event dispatch thread.
JavaSE makes the reason concrete. Its event dispatch thread can wait on AWT to present a frame. If an AWT drag callback synchronously waited for the Codename One thread, both sides could wait forever.
Static MIME filters therefore affect the cursor immediately. A decision made later in an application callback can reach the cursor on the following drag event. canAcceptNativeDrop is the exception that runs off the Codename One event dispatch thread; implementations must respect that contract rather than treating it like an ordinary UI listener.
Where native drags work in this release
| Port | Native drag/drop | Crossing into another app |
|---|---|---|
| JavaSE and simulator | Supported | Supported through AWT |
| Android | Supported | Android Nougat and later using a global drag |
| iPadOS and Mac Catalyst | Supported | Supported |
| iPhone | Supported | Not offered by this implementation’s interaction model |
| JavaScript, native AppKit, native Windows/Linux | Not implemented here | Not implemented here |
Check NativeDragAndDrop.isSupported() before offering the native workflow. The lightweight drag/drop API remains available on the other ports. Mac Catalyst uses this UIKit implementation; the native AppKit port still needs its own bridge.
On Android, the conversion reuses the clipboard’s ClipData machinery and file-provider URIs. On iOS, UIKit owns recognition of the gesture. Recognition and data preparation are separate steps; once a session begins, the provider timing above applies.
Try the whole handoff
Use the simulator to exercise restoration, then try the task on the devices your users will move between. For dragging, test the receiving application too: a text editor and a file manager can request different representations of the same content.
The bridge has core, JavaSE, and native compilation checks behind it. A useful integration test goes further: finish the move, cancel it, reject the payload, and sign out before an old checkpoint arrives. Those are the moments when a feature either preserves the user’s work or surprises them.
Move the work, keep the account boundary
The performance changes this week reduce what a running process needs. Continuity lets useful work survive when that process goes away anyway, and native drag and drop lets the user choose another application to receive its output.
The framework now supplies more of the transfer machinery, while the application decides what may cross each boundary. Restore after authentication, check access to the restored route, and validate incoming files before parsing them. An old checkpoint does not grant access, and a file offered by another app is still external input. Those rules make continuity and native integration useful without quietly weakening the security of the application using them.
Discussion
What should your users be able to carry to another device or application without starting over?