From d5ee8b1c872ecb5d6487b294d6fc06a2a9233124 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 11:14:55 +0300 Subject: [PATCH 01/13] Blog: platform APIs in the core (connectivity + identity + sharing + AI) Consolidated follow-up to the May 29 weekly index. Covers the four surfaces that moved from "you need a cn1lib for this" to "it is in the framework" this release: - Connectivity (PR #5021): com.codename1.io.{wifi,bonjour,usb} + NetworkManager.addNetworkTypeListener with the per-platform implementations and the three new CN1_INCLUDE_* defines that keep unused entitlements out of Apple's API-usage scan. - Identity (PRs #5018, #5039): OIDC client backed by ASWebAuthenticationSession / Custom Tabs with PKCE; Sign in with Apple in core; refreshed Google/Facebook/Microsoft/Auth0/Firebase wrappers; WebAuthn / passkey client in W3C JSON wire format with iOS 16 and Android API 28 native bindings; Auth0/Firebase passkey helpers; legacy Oauth2 deprecated. - Sharing (PR #5036): ShareResult callbacks (SHARED_TO/DISMISSED/FAILED) on iOS and Android via UIActivityViewController.completionWithItemsHandler and Intent.createChooser with IntentSender; IOSShareExtensionBuilder in the Maven plugin generates a complete .ios.appext bundle. - AI (PRs #5035, #5057): com.codename1.ai with LlmClient for OpenAI/Anthropic/Gemini/Ollama, streaming SSE, ChatView, SpeechRecognizer/TextToSpeech, SecureStorage non-prompting overloads, simulator Ollama redirect, AiDependencyTable build-time injection, and the cn1-ai-mlkit-{barcode,docscan,face} cn1libs. Closes with the structural element common to all four (scanner-driven gating that mirrors the NFC/biometrics pattern from two weeks ago). --- .../content/blog/platform-apis-in-the-core.md | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 docs/website/content/blog/platform-apis-in-the-core.md diff --git a/docs/website/content/blog/platform-apis-in-the-core.md b/docs/website/content/blog/platform-apis-in-the-core.md new file mode 100644 index 0000000000..19018aabaf --- /dev/null +++ b/docs/website/content/blog/platform-apis-in-the-core.md @@ -0,0 +1,211 @@ +--- +title: Platform APIs In The Core: Connectivity, Identity, Sharing, And AI +slug: platform-apis-in-the-core +url: /blog/platform-apis-in-the-core/ +date: '2026-06-01' +author: Shai Almog +description: WiFi / Bonjour / USB / network-type APIs, a modern OIDC + WebAuthn identity stack, share-sheet result callbacks, and a com.codename1.ai package with LlmClient, ChatView, speech, and ML Kit cn1libs. Four surfaces, one auto-injection story for Android permissions and iOS entitlements. +feed_html: 'Platform APIs In The Core: Connectivity, Identity, Sharing, And AI WiFi / Bonjour / USB, OIDC + WebAuthn, share-sheet result callbacks, and a com.codename1.ai package with LlmClient and ChatView. Four surfaces, one auto-injection story for Android permissions and iOS entitlements.' +--- + +![Platform APIs In The Core: Connectivity, Identity, Sharing, And AI](/blog/platform-apis-in-the-core.jpg) + +This post covers the four surfaces that moved from "you need a cn1lib for this" to "it is in the framework" this release. The same direction the last two releases pulled NFC, biometrics, and cryptography in: fundamental device APIs should not require you to track down a cn1lib and hope it is maintained. They should be part of the framework, with auto-injected permissions, conservative defaults, and a simulator path that lets you test without a real radio. + +The four are connectivity (WiFi / Bonjour / USB / network-type listeners), identity (OIDC + WebAuthn passkeys), sharing (share-sheet result callbacks), and AI (`LlmClient`, `ChatView`, speech and TTS, the ML Kit cn1libs). All four share the scanner-driven auto-injection that means an app that does not reference the API does not pay for it at App Store review time. + +## Connectivity + +[PR #5021](https://github.com/codenameone/CodenameOne/pull/5021) lands four packages. The new chapter at [Network-Connectivity.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Network-Connectivity.asciidoc) covers every surface in detail; the highlights: + +```java +WiFi wifi = WiFi.getInstance(); +String ssid = wifi.getCurrentSSID(); +String bssid = wifi.getBSSID(); +String gateway = wifi.getGateway(); +String ip = wifi.getIp(); + +wifi.scan(new ScanOptions().setTimeoutMillis(5000)) + .onResult((results, err) -> { /* ... */ }); + +wifi.connect("MyNetwork", "hunter2", Security.WPA2_PSK) + .onResult((success, err) -> { /* ... */ }); +``` + +`com.codename1.io.wifi` for WiFi info, scan, and connect. `com.codename1.io.wifi.WiFiDirect` for peer-to-peer (Android only by platform reality). `com.codename1.io.bonjour` for mDNS / Zeroconf with `BonjourBrowser` and `BonjourPublisher`. `com.codename1.io.usb` for USB host. And `NetworkManager.addNetworkTypeListener(...)` plus `NETWORK_TYPE_*` constants so an app can react to a transition between cellular, WiFi, ethernet, or "none": + +```java +NetworkManager.getInstance().addNetworkTypeListener(evt -> { + int type = evt.getNetworkType(); + if (type == NetworkManager.NETWORK_TYPE_NONE) showOfflineBanner(); + else if (type == NetworkManager.NETWORK_TYPE_CELLULAR) suppressLargeBackgroundDownloads(); + else clearOfflineBanner(); +}); +``` + +The per-platform implementation choices map to the standard system services. Android: `WifiManager` and `ConnectivityManager` (with `WifiNetworkSpecifier` on API 29+ and a `WifiConfiguration` fallback for older releases), `NsdManager` for Bonjour, `WifiP2pManager` for WiFi Direct, `UsbManager` for USB. iOS: `CNCopyCurrentNetworkInfo` for SSID / BSSID, `getifaddrs` for IP / gateway, `NEHotspotConfigurationManager` for connect, `NSNetServiceBrowser` for Bonjour, `SCNetworkReachability` for network-type tracking. JavaSE: derives WiFi info from `NetworkInterface`, returns fabricated scan results, picks up JmDNS if it is on the classpath. + +iOS does not expose programmatic WiFi scanning to third-party apps; `scan()` throws `UnsupportedOperationException` on iOS regardless of what entitlements you add. iOS also does not expose WiFi Direct or general USB host to third-party apps. None of those are Codename One limitations; they are Apple's. The dev guide is explicit about each platform's limits so there is no runtime surprise. + +Three new compile-time defines (`CN1_INCLUDE_WIFI_INFO`, `CN1_INCLUDE_HOTSPOT`, `CN1_INCLUDE_BONJOUR`) wrap the native code that calls `CNCopyCurrentNetworkInfo`, `NEHotspotConfigurationManager`, and `NSNetServiceBrowser`. The build server sets each define only when your classpath scanner sees the corresponding Java API in use, so an app that never references `WiFi.connect` does not even compile the `NEHotspotConfigurationManager` code, and Apple's API-usage scanner does not flag your binary for entitlements it has not seen the matching APIs in. Same shape as the NFC gating we shipped two weeks ago. + +## Identity: OIDC and WebAuthn passkeys + +The in-app-WebView `Oauth2` flow that Codename One has shipped since approximately forever was the way every cross-platform mobile framework solved the "sign in with Google / Facebook / Microsoft" problem in the 2010s. It is also the way every one of those identity providers stopped wanting you to solve it. Google has been blocking embedded user agents for years. Apple does not want third-party apps wrapping the Apple ID flow in a `WKWebView`. Microsoft and Facebook joined the chorus. The right answer is the system browser: `ASWebAuthenticationSession` on iOS, Custom Tabs on Android, with PKCE on the wire. That is what [PR #5018](https://github.com/codenameone/CodenameOne/pull/5018) does. + +`com.codename1.io.oidc.OidcClient` is the new entry point: + +```java +OidcConfiguration cfg = OidcConfiguration.discover("https://accounts.google.com"); + +OidcClient client = OidcClient.builder() + .configuration(cfg) + .clientId("123-abc.apps.googleusercontent.com") + .redirectUri("com.example.myapp:/oauthredirect") + .scopes("openid", "email", "profile") + .build(); + +client.signIn().onResult((tokens, err) -> { + if (err != null) return; + String email = tokens.getIdToken().getClaim("email").asString(); + proceed(email); +}); +``` + +Discovery JSON parsed and cached. PKCE S256 challenge generated and verified. State and nonce checked on the callback. ID-token claims decoded for you (we deliberately do not verify the signature client-side; the dev guide is explicit about why and points at the "re-validate on your backend" remedy). Refresh and revoke are first-class. The token store is pluggable via `TokenStore`. On iOS the system-browser piece routes through `ASWebAuthenticationSession`; on Android through `androidx.browser.customtabs` with a plain `ACTION_VIEW` fallback for the rare device that has no Custom Tabs provider. `AuthenticationServices.framework` and `androidx.browser:browser` are auto-linked when the classpath scanner sees `OidcClient` in use. + +`com.codename1.social` gains `MicrosoftConnect` (Entra ID), `Auth0Connect`, and `FirebaseAuth` as new provider wrappers; `GoogleConnect.signIn(...)` and `FacebookConnect.signIn(...)` go through the new stack. `com.codename1.social.AppleSignIn` moves into the core, with native `ASAuthorizationAppleIDProvider` on iOS 13+ and an OIDC web fallback elsewhere. + +`com.codename1.io.Oauth2` is now deprecated. It still works (we deliberately did not break it). The migration is a short edit: replace the issuer URL and credentials with their `OidcConfiguration` equivalents, swap the `accessToken()` call for `signIn()`, delete the `setBrowserComponent(...)` wiring. The result is shorter and the cookie state is owned by the OS instead of by a `WKWebView` you have to manage. + +[PR #5039](https://github.com/codenameone/CodenameOne/pull/5039) layers a portable WebAuthn / passkey client on top: + +```java +WebAuthnClient client = WebAuthnClient.getInstance(); +if (!client.isAvailable()) { fallbackToPassword(); return; } + +PublicKeyCredentialCreationOptions opts = + PublicKeyCredentialCreationOptions.fromServerJson(serverJson); +client.create(opts).onResult((cred, err) -> { + if (err == null) postToRelyingParty(cred.toJson()); +}); +``` + +W3C JSON wire format in both directions, so the response can be POSTed verbatim to any standard server-side WebAuthn library. iOS 16+ routes through `ASAuthorizationPlatformPublicKeyCredentialProvider`; Android API 28+ through `androidx.credentials.CredentialManager`. Provider helpers: `Auth0Connect.signInWithPasskey(...)` / `.registerPasskey(...)` and `FirebaseAuth.signInWithPasskey(...)` / `.registerPasskey(...)`. + +One thing worth pulling out about WebAuthn before you reach for it: if you sign in via OIDC against Google, Apple, Microsoft, Auth0, or Firebase, you usually already *get* passkeys for free. The identity provider runs the WebAuthn ceremony inside the system browser; OIDC just hands you the resulting tokens. So you do not need `WebAuthnClient` for that case. You need it for two things specifically: apps that run their own relying-party backend, and apps that drive Auth0 or Firebase passkeys directly via their client-side WebAuthn grants. The dev guide section calls this out so nobody adds a WebAuthn dependency they do not need. + +Full chapter: [Authentication-And-Identity.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Authentication-And-Identity.asciidoc). + +## Sharing: result callbacks (and an iOS Share Extension authoring helper) + +The native share sheet has been one of those small gaps that nobody complains about in isolation, but that hits you the day you try to do something useful with the result. The framework has had `Display.share(...)` and `ShareButton` since approximately forever. What it has not had is a way to know whether the user actually shared the content, where they shared it to, or whether they hit Cancel and walked away. [PR #5036](https://github.com/codenameone/CodenameOne/pull/5036) closes that: + +```java +ShareButton btn = new ShareButton(); +btn.setTextToShare("Look at this fox"); +btn.setImageToShare("/fox.jpg"); +btn.setShareResultListener(result -> { + switch (result.getStatus()) { + case SHARED_TO: track("share_completed", result.getTargetPackage()); break; + case DISMISSED: track("share_dismissed"); break; + case FAILED: track("share_failed", result.getError()); break; + } +}); +``` + +iOS routes through `UIActivityViewController.completionWithItemsHandler` (Apple's API hands you a `UIActivityType` string plus a completed-boolean; the framework normalises it). Android routes through `Intent.createChooser` with an `IntentSender` callback, which is the API path that landed in API 22; the `IntentSender` is invoked with the `ComponentName` of the chosen target, so the framework sees the package name of the receiving app. Earlier-API devices fall back to `DISMISSED`. The same listener attaches to `Display.share(...)`. + +The PR also lands an `IOSShareExtensionBuilder` in the Maven plugin for the *other* half of sharing: iOS apps that want to *receive* shared content from other apps via a Share Extension. The builder emits a complete `.ios.appext` bundle (`Info.plist` with the right `NSExtension` activation rules, the App Group entitlement, a minimal `ShareViewController.swift` that lands the payload in the App Group via `UserDefaults(suiteName:)`, and the matching `buildSettings.properties`). The result composes with the existing `IPhoneBuilder.extractAppExtensions` pipeline, so apps that use the builder do not need to bootstrap anything in Xcode and apps that already have a hand-rolled extension keep working. + +## AI + +The largest of the four surfaces. [PR #5035](https://github.com/codenameone/CodenameOne/pull/5035) lands the package, the ChatView, speech, the simulator Ollama redirect, and the build-time dependency injection; [PR #5057](https://github.com/codenameone/CodenameOne/pull/5057) lands the developer-guide chapter and the agent-skill update so generated projects' `AGENTS.md` covers the new APIs from the first prompt. + +`com.codename1.ai.LlmClient` is the entry point: + +```java +LlmClient client = LlmClient.openai(apiKey); + +ChatRequest req = new ChatRequest.Builder() + .model("gpt-4o-mini") + .system("You are a helpful assistant.") + .user("What is the capital of France?") + .temperature(0.7) + .build(); + +client.chat(req).onResult((resp, err) -> { + if (err != null) return; + System.out.println(resp.firstChoice().content()); +}); +``` + +`LlmClient.openai(...)`, `LlmClient.anthropic(...)`, `LlmClient.gemini(...)`, `LlmClient.ollama(...)`, and `LlmClient.openAiCompatible(baseUrl, apiKey)` are the static factories. OpenAI is the fully implemented native client; the same client drives Ollama, vLLM, and llama.cpp because the wire format is OpenAI-compatible. Anthropic and Gemini compile and register but currently throw a clear error pointing callers at the OpenAI-compat shim while their native clients are in flight. + +Streaming chat is a separate entry point, and is the one most chat UIs actually want: + +```java +client.chatStream(req, new ChatStreamListener() { + @Override public void onDelta(ChatDelta d) { appendToUi(d.contentDelta()); } + @Override public void onComplete(ChatResponse fin) { /* ... */ } + @Override public void onError(Throwable t) { /* ... */ } +}); +``` + +Under the hood this is a custom `ConnectionRequest` subclass that parses SSE line-by-line and dispatches deltas through `Display.callSerially`, so your UI updates land on the EDT. `AsyncResource.cancel()` kills the socket cleanly. + +The rest of the surface is what a modern LLM SDK looks like: `ChatRequest` / `ChatMessage` / `MessagePart` for multimodal content, `Tool` / `ToolCall` / `ToolChoice` for function calling, `ResponseFormat` for JSON-mode and JSON-schema-mode, `Embedding` for the embeddings endpoint, `ImageGenerator` for DALL-E. Utility extras include `PromptTemplate`, `Tokenizer`, `RetryPolicy`, `ConversationStore`, and `SafetyFilter`. + +### Simulator Ollama redirect + +The simulator detail that matters most in practice: `JavaSEPort` pings `localhost:11434` at startup, and if it finds Ollama running, sets the `cn1.ai.ollamaDetected` property. A small `SimulatorRedirect` consumer reads that, and with `cn1.ai.simulatorRedirect=auto` (or `=ollama`), every `LlmClient.openai(...)` call routes through the local Ollama endpoint instead of OpenAI's. Production code does not change; your tests, your iteration loop, and your offline debugging stop costing money and stop needing an internet connection. + +### ChatView + +`com.codename1.components.ChatView` is the matching UI component. Scrollable message list, `ChatBubble` for the per-message bubble (theme-aware UIIDs so it picks up the iOS Modern / Material 3 native themes consistently), `ChatInput` for the bottom input bar, and the convenience method that wires the whole thing together: + +```java +ChatView view = new ChatView(); +view.bindToLlm(LlmClient.openai(apiKey), + new ChatRequest.Builder().model("gpt-4o-mini").build()); + +Form f = new Form("Chat", new BorderLayout()); +f.add(BorderLayout.CENTER, view); +f.show(); +``` + +Five lines, working streaming chat UI. + +### Speech and SecureStorage + +Two new core APIs land alongside the LLM surface. `TextToSpeech.getInstance().speak("Hello")` and `SpeechRecognizer.getInstance().recognize().onResult(...)`. The native bridges (iOS `SFSpeechRecognizer` / `AVSpeechSynthesizer`, Android `android.speech.*` and `TextToSpeech`) are tracked follow-ups; the simulator already has a best-effort TTS via `say` on macOS, `espeak` on Linux, SAPI on Windows. + +`SecureStorage` gains single-argument non-prompting overloads of `get / set / remove(account, ...)` next to the existing biometric-gated methods. The reason is LLM API keys: you read them on every network call; you cannot prompt the user for Face ID every time. The new overloads store the key behind the keychain / keystore protection class without the user-presence gate. + +### Build-time dependency injection + +`AiDependencyTable` in the Maven plugin is the piece that makes "add an LLM call to your app and the right native dependencies show up" actually true. 18 entries mapping `com/codename1/ai/*` and the speech / TTS classes to iOS pods, Swift Packages, Android Gradle deps, `Info.plist` usage strings, and Android permissions. Entries that bundle native blobs over 2 GB (on-device Stable Diffusion) flip a `cn1.ai.requiresBigUpload` flag and the cloud build aborts pre-upload with a friendly "build this one locally" message. + +The companion cn1libs in this release: `cn1-ai-mlkit-barcode`, `cn1-ai-mlkit-docscan`, `cn1-ai-mlkit-face`. The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a publish workflow. + +Full chapter: [Ai-And-Speech.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Ai-And-Speech.asciidoc). + +## What ties this together + +The shared structural element across all four surfaces is the scanner-driven gating. Every PR in this batch flips a single per-feature flag (`usesOidc`, `usesAppleSignIn`, `usesWebAuthn`, `usesAi`, the existing WiFi/Bonjour/Hotspot defines) that drives framework linking, entitlement injection, plist injection, and Objective-C conditional compilation. Apps that do not reference the new APIs do not pay for them at App Store review time, and the binaries do not even contain the platform calls. This is the pattern we settled on for NFC and biometrics two weeks ago, and it is the right answer for "these APIs are part of the core, but apps that do not use them should not pay for them at review time". + +The companion cloud build server changes (BuildDaemon mirrors) ship together so local builds and cloud builds match across all four surfaces. + +## Wrapping up + +Connectivity, identity, sharing, and AI. All in the core. Same auto-injection story across the four of them. The corresponding chapters cover each surface in detail; the place to read across them is the build-hint and entitlement table in [Advanced-Topics-Under-The-Hood.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc). + +Wednesday's post is the architectural one: the build-time bytecode annotation framework, the declarative router that is its first consumer, the SQLite ORM and JSON / XML mappers and component binder it carries, and the SVG / Lottie transcoder that ships in the same release because it sits on the same "emit Java at build time" pattern. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} From 04d0fcf5cbbbefe4217f415a0cefd3e99066a40a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 14:33:38 +0300 Subject: [PATCH 02/13] Platform-APIs post: rewrite around AI + OAuth headline - Retitled "AI, OAuth, And Other Platform APIs In The Core". - Reordered sections so the two headline pieces come first: AI (with deep tutorials) and OAuth / OIDC (with provider walk- throughs and a migration). WiFi / connectivity and share-sheet callbacks land at the end. - Opener no longer runs together; the NFC / biometrics / crypto call-back is linked to the previous post explicitly. - AI section expanded with concrete examples for streaming, tool calls, embeddings, image generation, the simulator Ollama redirect, the SecureStorage non-prompting overloads, the ChatView binding, and an ASCII mockup of the ChatView surface for readers who haven't seen it. - New subsection explaining why the ML Kit AI features stay in cn1libs even though the LlmClient surface lives in core (core carries the plumbing every AI app wants; specialised verticals with large native dependencies stay opt-in; the cloud-build big- upload guard rules out the multi-gigabyte models). - OAuth section now walks through OIDC discovery, the four provider wrappers (Google, Microsoft, Auth0, Facebook), Sign in with Apple, an Oauth2-to-OidcClient migration example, and the WebAuthn / passkey client. - Dev-guide references go to the HTML version on the website. - Hero image lands at /blog/platform-apis-in-the-core.jpg. - Wrap-up has the link back to the intro and forward to the Wednesday post. Also updates the developer-workflow post (rebased on top) to add the forward link to this post in its wrap-up. --- .../developer-workflow-debug-and-junit.md | 2 +- .../content/blog/platform-apis-in-the-core.md | 439 +++++++++++++----- .../static/blog/platform-apis-in-the-core.jpg | Bin 0 -> 32699 bytes 3 files changed, 330 insertions(+), 111 deletions(-) create mode 100644 docs/website/static/blog/platform-apis-in-the-core.jpg diff --git a/docs/website/content/blog/developer-workflow-debug-and-junit.md b/docs/website/content/blog/developer-workflow-debug-and-junit.md index e6bfd50821..bbcecde1e4 100644 --- a/docs/website/content/blog/developer-workflow-debug-and-junit.md +++ b/docs/website/content/blog/developer-workflow-debug-and-junit.md @@ -279,7 +279,7 @@ The full reference, including the dependency-block YAML for `common/pom.xml` and ## Wrapping up -That is the workflow half of this release. The next post is on Monday and covers the new platform APIs that moved into the core this week: AI and OIDC are the headline pieces, with WiFi / connectivity and a few smaller items alongside them. +That is the workflow half of this release. [Monday's post](/blog/platform-apis-in-the-core/) covers the new platform APIs that moved into the core this week: AI and OAuth / OIDC are the headline pieces, with WiFi / connectivity and a few smaller items alongside them. Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). diff --git a/docs/website/content/blog/platform-apis-in-the-core.md b/docs/website/content/blog/platform-apis-in-the-core.md index 19018aabaf..f571495325 100644 --- a/docs/website/content/blog/platform-apis-in-the-core.md +++ b/docs/website/content/blog/platform-apis-in-the-core.md @@ -1,206 +1,425 @@ --- -title: Platform APIs In The Core: Connectivity, Identity, Sharing, And AI +title: AI, OAuth, And Other Platform APIs In The Core slug: platform-apis-in-the-core url: /blog/platform-apis-in-the-core/ date: '2026-06-01' author: Shai Almog -description: WiFi / Bonjour / USB / network-type APIs, a modern OIDC + WebAuthn identity stack, share-sheet result callbacks, and a com.codename1.ai package with LlmClient, ChatView, speech, and ML Kit cn1libs. Four surfaces, one auto-injection story for Android permissions and iOS entitlements. -feed_html: 'Platform APIs In The Core: Connectivity, Identity, Sharing, And AI WiFi / Bonjour / USB, OIDC + WebAuthn, share-sheet result callbacks, and a com.codename1.ai package with LlmClient and ChatView. Four surfaces, one auto-injection story for Android permissions and iOS entitlements.' +description: A com.codename1.ai package with LlmClient, ChatView, streaming, tool calls, and a simulator Ollama redirect. A modern OAuth / OIDC stack that runs through the system browser and includes Sign in with Apple, Google, Microsoft, Auth0, Firebase, and WebAuthn passkeys. Built-in WiFi / Bonjour / USB and share-sheet result callbacks alongside. Deep tutorials and a note on why the ML Kit AI features stayed in cn1libs. +feed_html: 'AI, OAuth, And Other Platform APIs In The Core A com.codename1.ai package with LlmClient and ChatView, a modern OAuth / OIDC stack with WebAuthn passkeys, plus built-in WiFi / Bonjour / USB and share-sheet result callbacks. Deep tutorials and a note on why ML Kit stayed in cn1libs.' --- -![Platform APIs In The Core: Connectivity, Identity, Sharing, And AI](/blog/platform-apis-in-the-core.jpg) +![AI, OAuth, And Other Platform APIs In The Core](/blog/platform-apis-in-the-core.jpg) -This post covers the four surfaces that moved from "you need a cn1lib for this" to "it is in the framework" this release. The same direction the last two releases pulled NFC, biometrics, and cryptography in: fundamental device APIs should not require you to track down a cn1lib and hope it is maintained. They should be part of the framework, with auto-injected permissions, conservative defaults, and a simulator path that lets you test without a real radio. +This is the second follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). It covers the platform APIs that moved into the framework core this release. There are two headline pieces (AI / LLM and the modern OAuth / OIDC stack), and two smaller pieces (WiFi / connectivity and share-sheet result callbacks). This continues the direction the previous release set when we moved NFC, biometrics, and cryptography into the framework core. The full background on that earlier set is in [NFC, Crypto, Biometrics, And A New Build Cloud](/blog/nfc-crypto-biometrics-and-build-cloud/). -The four are connectivity (WiFi / Bonjour / USB / network-type listeners), identity (OIDC + WebAuthn passkeys), sharing (share-sheet result callbacks), and AI (`LlmClient`, `ChatView`, speech and TTS, the ML Kit cn1libs). All four share the scanner-driven auto-injection that means an app that does not reference the API does not pay for it at App Store review time. +The order below mirrors how much code each section is likely to change in your app. AI first, OAuth / OIDC second, the smaller items at the end. -## Connectivity +## AI: a first-class LLM client and a ChatView component -[PR #5021](https://github.com/codenameone/CodenameOne/pull/5021) lands four packages. The new chapter at [Network-Connectivity.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Network-Connectivity.asciidoc) covers every surface in detail; the highlights: +[PR #5035](https://github.com/codenameone/CodenameOne/pull/5035) lands the `com.codename1.ai` package, the `ChatView` UI component, the speech and TTS additions, and the build-time dependency injection that wires the native pieces in. [PR #5057](https://github.com/codenameone/CodenameOne/pull/5057) lands the developer-guide chapter and the agent-skill addition so any project generated from the [Initializr](/initializr/) inherits the new APIs through its bundled `AGENTS.md`. + +### LlmClient: the basic chat request + +`com.codename1.ai.LlmClient` is the entry point. The simplest possible use: ```java -WiFi wifi = WiFi.getInstance(); -String ssid = wifi.getCurrentSSID(); -String bssid = wifi.getBSSID(); -String gateway = wifi.getGateway(); -String ip = wifi.getIp(); +LlmClient client = LlmClient.openai(apiKey); -wifi.scan(new ScanOptions().setTimeoutMillis(5000)) - .onResult((results, err) -> { /* ... */ }); +ChatRequest req = new ChatRequest.Builder() + .model("gpt-4o-mini") + .system("You are a helpful assistant.") + .user("What is the capital of France?") + .temperature(0.7) + .build(); -wifi.connect("MyNetwork", "hunter2", Security.WPA2_PSK) - .onResult((success, err) -> { /* ... */ }); +client.chat(req).onResult((resp, err) -> { + if (err != null) { + Log.e(err); + return; + } + Log.p(resp.firstChoice().content()); +}); ``` -`com.codename1.io.wifi` for WiFi info, scan, and connect. `com.codename1.io.wifi.WiFiDirect` for peer-to-peer (Android only by platform reality). `com.codename1.io.bonjour` for mDNS / Zeroconf with `BonjourBrowser` and `BonjourPublisher`. `com.codename1.io.usb` for USB host. And `NetworkManager.addNetworkTypeListener(...)` plus `NETWORK_TYPE_*` constants so an app can react to a transition between cellular, WiFi, ethernet, or "none": +`LlmClient.openai(...)`, `LlmClient.anthropic(...)`, `LlmClient.gemini(...)`, `LlmClient.ollama(...)`, and `LlmClient.openAiCompatible(baseUrl, apiKey)` are the factories. OpenAI has the fully implemented native client today. The same client drives Ollama, vLLM, and llama.cpp because their wire formats are OpenAI-compatible. Anthropic and Gemini compile and register, but their native clients are still in flight; they throw a clear error pointing you at the OpenAI-compat shim until the native ones land. + +### Streaming chat (what you actually want for chat UIs) + +For any UI that types responses out token-by-token, the streaming entry point is the one to reach for. The callback fires on the EDT, so you can append directly to a text component: ```java -NetworkManager.getInstance().addNetworkTypeListener(evt -> { - int type = evt.getNetworkType(); - if (type == NetworkManager.NETWORK_TYPE_NONE) showOfflineBanner(); - else if (type == NetworkManager.NETWORK_TYPE_CELLULAR) suppressLargeBackgroundDownloads(); - else clearOfflineBanner(); -}); -``` +client.chatStream(req, new ChatStreamListener() { -The per-platform implementation choices map to the standard system services. Android: `WifiManager` and `ConnectivityManager` (with `WifiNetworkSpecifier` on API 29+ and a `WifiConfiguration` fallback for older releases), `NsdManager` for Bonjour, `WifiP2pManager` for WiFi Direct, `UsbManager` for USB. iOS: `CNCopyCurrentNetworkInfo` for SSID / BSSID, `getifaddrs` for IP / gateway, `NEHotspotConfigurationManager` for connect, `NSNetServiceBrowser` for Bonjour, `SCNetworkReachability` for network-type tracking. JavaSE: derives WiFi info from `NetworkInterface`, returns fabricated scan results, picks up JmDNS if it is on the classpath. + @Override + public void onDelta(ChatDelta d) { + responseLabel.setText(responseLabel.getText() + d.contentDelta()); + responseLabel.getParent().revalidateLater(); + } -iOS does not expose programmatic WiFi scanning to third-party apps; `scan()` throws `UnsupportedOperationException` on iOS regardless of what entitlements you add. iOS also does not expose WiFi Direct or general USB host to third-party apps. None of those are Codename One limitations; they are Apple's. The dev guide is explicit about each platform's limits so there is no runtime surprise. + @Override + public void onComplete(ChatResponse fin) { + sendButton.setEnabled(true); + } -Three new compile-time defines (`CN1_INCLUDE_WIFI_INFO`, `CN1_INCLUDE_HOTSPOT`, `CN1_INCLUDE_BONJOUR`) wrap the native code that calls `CNCopyCurrentNetworkInfo`, `NEHotspotConfigurationManager`, and `NSNetServiceBrowser`. The build server sets each define only when your classpath scanner sees the corresponding Java API in use, so an app that never references `WiFi.connect` does not even compile the `NEHotspotConfigurationManager` code, and Apple's API-usage scanner does not flag your binary for entitlements it has not seen the matching APIs in. Same shape as the NFC gating we shipped two weeks ago. + @Override + public void onError(Throwable t) { + Log.e(t); + sendButton.setEnabled(true); + } +}); +``` -## Identity: OIDC and WebAuthn passkeys +Under the hood this is a custom `ConnectionRequest` subclass that parses SSE line-by-line and dispatches each delta through `Display.callSerially`. `AsyncResource.cancel()` kills the socket. So a chat UI that has a cancel button is a one-line cancellation. -The in-app-WebView `Oauth2` flow that Codename One has shipped since approximately forever was the way every cross-platform mobile framework solved the "sign in with Google / Facebook / Microsoft" problem in the 2010s. It is also the way every one of those identity providers stopped wanting you to solve it. Google has been blocking embedded user agents for years. Apple does not want third-party apps wrapping the Apple ID flow in a `WKWebView`. Microsoft and Facebook joined the chorus. The right answer is the system browser: `ASWebAuthenticationSession` on iOS, Custom Tabs on Android, with PKCE on the wire. That is what [PR #5018](https://github.com/codenameone/CodenameOne/pull/5018) does. +### Tool calls -`com.codename1.io.oidc.OidcClient` is the new entry point: +If you want the model to call back into your app, `Tool` / `ToolChoice` give you OpenAI-style function calling. Define the tool, hand the model your model and the available tools, and the response surfaces structured `ToolCall` objects you dispatch: ```java -OidcConfiguration cfg = OidcConfiguration.discover("https://accounts.google.com"); +Tool getWeather = Tool.builder() + .name("get_weather") + .description("Look up the current weather for a city.") + .parameter("city", "string", "The city name, e.g. \"Paris\".") + .build(); -OidcClient client = OidcClient.builder() - .configuration(cfg) - .clientId("123-abc.apps.googleusercontent.com") - .redirectUri("com.example.myapp:/oauthredirect") - .scopes("openid", "email", "profile") +ChatRequest req = new ChatRequest.Builder() + .model("gpt-4o-mini") + .user("Is it raining in Tel Aviv right now?") + .tool(getWeather) + .toolChoice(ToolChoice.AUTO) .build(); -client.signIn().onResult((tokens, err) -> { +client.chat(req).onResult((resp, err) -> { if (err != null) return; - String email = tokens.getIdToken().getClaim("email").asString(); - proceed(email); + for (ToolCall call : resp.firstChoice().toolCalls()) { + if ("get_weather".equals(call.name())) { + String city = call.argument("city").asString(); + String json = lookupWeather(city); + // Loop the result back into the conversation + client.chat(req.replyWithToolResult(call, json)) + .onResult((followUp, e) -> updateUi(followUp)); + } + } }); ``` -Discovery JSON parsed and cached. PKCE S256 challenge generated and verified. State and nonce checked on the callback. ID-token claims decoded for you (we deliberately do not verify the signature client-side; the dev guide is explicit about why and points at the "re-validate on your backend" remedy). Refresh and revoke are first-class. The token store is pluggable via `TokenStore`. On iOS the system-browser piece routes through `ASWebAuthenticationSession`; on Android through `androidx.browser.customtabs` with a plain `ACTION_VIEW` fallback for the rare device that has no Custom Tabs provider. `AuthenticationServices.framework` and `androidx.browser:browser` are auto-linked when the classpath scanner sees `OidcClient` in use. +The shape mirrors the OpenAI function-calling contract one for one, so anything you have written against the OpenAI API directly maps across without rethinking. -`com.codename1.social` gains `MicrosoftConnect` (Entra ID), `Auth0Connect`, and `FirebaseAuth` as new provider wrappers; `GoogleConnect.signIn(...)` and `FacebookConnect.signIn(...)` go through the new stack. `com.codename1.social.AppleSignIn` moves into the core, with native `ASAuthorizationAppleIDProvider` on iOS 13+ and an OIDC web fallback elsewhere. +### Embeddings -`com.codename1.io.Oauth2` is now deprecated. It still works (we deliberately did not break it). The migration is a short edit: replace the issuer URL and credentials with their `OidcConfiguration` equivalents, swap the `accessToken()` call for `signIn()`, delete the `setBrowserComponent(...)` wiring. The result is shorter and the cookie state is owned by the OS instead of by a `WKWebView` you have to manage. - -[PR #5039](https://github.com/codenameone/CodenameOne/pull/5039) layers a portable WebAuthn / passkey client on top: +`LlmClient.embed(...)` returns a vector for any input string. Useful for similarity search against a local SQLite store ([Wednesday's post](/blog/build-time-codegen/) will cover the new ORM that pairs with this): ```java -WebAuthnClient client = WebAuthnClient.getInstance(); -if (!client.isAvailable()) { fallbackToPassword(); return; } +EmbeddingRequest er = new EmbeddingRequest.Builder() + .model("text-embedding-3-small") + .input("Codename One is a cross-platform mobile framework.") + .build(); -PublicKeyCredentialCreationOptions opts = - PublicKeyCredentialCreationOptions.fromServerJson(serverJson); -client.create(opts).onResult((cred, err) -> { - if (err == null) postToRelyingParty(cred.toJson()); +client.embed(er).onResult((emb, err) -> { + float[] vector = emb.firstVector(); + // store, search, compare }); ``` -W3C JSON wire format in both directions, so the response can be POSTed verbatim to any standard server-side WebAuthn library. iOS 16+ routes through `ASAuthorizationPlatformPublicKeyCredentialProvider`; Android API 28+ through `androidx.credentials.CredentialManager`. Provider helpers: `Auth0Connect.signInWithPasskey(...)` / `.registerPasskey(...)` and `FirebaseAuth.signInWithPasskey(...)` / `.registerPasskey(...)`. +### Image generation + +DALL-E and a Replicate scaffold are surfaced through `ImageGenerator`: + +```java +ImageGenerator gen = ImageGenerator.openAiDallE(apiKey); -One thing worth pulling out about WebAuthn before you reach for it: if you sign in via OIDC against Google, Apple, Microsoft, Auth0, or Firebase, you usually already *get* passkeys for free. The identity provider runs the WebAuthn ceremony inside the system browser; OIDC just hands you the resulting tokens. So you do not need `WebAuthnClient` for that case. You need it for two things specifically: apps that run their own relying-party backend, and apps that drive Auth0 or Firebase passkeys directly via their client-side WebAuthn grants. The dev guide section calls this out so nobody adds a WebAuthn dependency they do not need. +gen.generate("A red bicycle leaning against an olive tree", "1024x1024") + .onResult((img, err) -> { + if (err != null) return; + myImageComponent.setIcon(img); + }); +``` + +### Working against Ollama in the simulator (no API charges) + +`JavaSEPort` pings `localhost:11434` at startup. If it finds Ollama, it sets the `cn1.ai.ollamaDetected` property. With `cn1.ai.simulatorRedirect=auto` (or `=ollama`) every `LlmClient.openai(...)` call routes through the local Ollama endpoint instead of OpenAI's. Production code does not change. The iteration loop, your tests, and your offline debugging stop costing money and stop needing an internet connection. + +In `common/codenameone_settings.properties`: + +``` +simulator.cn1.ai.simulatorRedirect=auto +``` -Full chapter: [Authentication-And-Identity.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Authentication-And-Identity.asciidoc). +(The `simulator.` prefix scopes the property to the JavaSE simulator path.) Then run Ollama locally with whichever model your code expects (`ollama run llama3.2` or similar) and your existing `LlmClient.openai(...)` calls go to localhost. -## Sharing: result callbacks (and an iOS Share Extension authoring helper) +### SecureStorage non-prompting overloads -The native share sheet has been one of those small gaps that nobody complains about in isolation, but that hits you the day you try to do something useful with the result. The framework has had `Display.share(...)` and `ShareButton` since approximately forever. What it has not had is a way to know whether the user actually shared the content, where they shared it to, or whether they hit Cancel and walked away. [PR #5036](https://github.com/codenameone/CodenameOne/pull/5036) closes that: +Small thing that matters more than it sounds: the existing biometric-gated `SecureStorage.get / set / remove(account, options...)` methods got new single-argument overloads that do not prompt for biometrics. The reason is LLM API keys. You read them on every network call; you cannot prompt the user for Face ID every time. The new overloads store the key behind the keychain / keystore protection class without the user-presence gate. Existing biometric-gated calls keep working. ```java -ShareButton btn = new ShareButton(); -btn.setTextToShare("Look at this fox"); -btn.setImageToShare("/fox.jpg"); -btn.setShareResultListener(result -> { - switch (result.getStatus()) { - case SHARED_TO: track("share_completed", result.getTargetPackage()); break; - case DISMISSED: track("share_dismissed"); break; - case FAILED: track("share_failed", result.getError()); break; - } +SecureStorage.set("openai_api_key", apiKey); // no prompt +String key = SecureStorage.get("openai_api_key"); +``` + +### ChatView: a ready-made streaming chat UI + +`com.codename1.components.ChatView` is the matching UI component. Scrollable message list, `ChatBubble` for the per-message bubble (theme-aware UIIDs so it picks up the iOS Modern / Material 3 native themes consistently), `ChatInput` for the bottom input bar, and a one-line `bindToLlm(...)` that wires the input to a streaming chat request: + +```java +ChatView view = new ChatView(); +view.bindToLlm(LlmClient.openai(SecureStorage.get("openai_api_key")), + new ChatRequest.Builder() + .model("gpt-4o-mini") + .system("You are a friendly tutor for Codename One developers.") + .build()); + +Form f = new Form("Chat", new BorderLayout()); +f.add(BorderLayout.CENTER, view); +f.show(); +``` + +The on-screen shape that comes out of that is the standard messaging layout: + +``` ++---------------------------------------+ +| Chat | ++---------------------------------------+ +| | +| +-------------------------+ | +| | Hi! How can I help? | | <- assistant bubble +| +-------------------------+ | +| | +| +----------------------+ | +| | What is a Form? | | <- user bubble +| +----------------------+ | +| | +| +------------------------------+ | +| | A Form is the top-level UI… | | <- streaming +| +------------------------------+ | +| | ++---------------------------------------+ +| Type your message… [ Send ] | <- ChatInput ++---------------------------------------+ +``` + +`appendToLastMessage(...)` is the entry point if you want to drive the streaming yourself (the binding above already does it for you). It marshals through `callSerially` so deltas land on the EDT in order. + +### Speech and TTS + +Two new core APIs land alongside the LLM surface: + +```java +TextToSpeech.getInstance().speak("Hello, world."); + +SpeechRecognizer rec = SpeechRecognizer.getInstance(); +if (!rec.isSupported()) { + fallbackToTyping(); + return; +} +rec.recognize().onResult((text, err) -> { + if (err == null) sendChatMessage(text); }); ``` -iOS routes through `UIActivityViewController.completionWithItemsHandler` (Apple's API hands you a `UIActivityType` string plus a completed-boolean; the framework normalises it). Android routes through `Intent.createChooser` with an `IntentSender` callback, which is the API path that landed in API 22; the `IntentSender` is invoked with the `ComponentName` of the chosen target, so the framework sees the package name of the receiving app. Earlier-API devices fall back to `DISMISSED`. The same listener attaches to `Display.share(...)`. +The platform plan is iOS routed through `SFSpeechRecognizer` and `AVSpeechSynthesizer`, Android through `android.speech.*` and the `TextToSpeech` engine. The native bridges are tracked follow-ups (they need on-device testing before they ship). The simulator already has a best-effort TTS via `say` on macOS, `espeak` on Linux, SAPI on Windows; recognition stays unsupported in the simulator unless you add the `cn1-ai-whisper` cn1lib. + +### Why are the ML Kit features still cn1libs? + +A fair question given the opening framing of this post. If "fundamental device APIs should be in the core", why does AI ship with a set of cn1libs (`cn1-ai-mlkit-barcode`, `cn1-ai-mlkit-docscan`, `cn1-ai-mlkit-face`, `cn1-ai-whisper`, `cn1-ai-stablediffusion`) rather than rolling all of those into `com.codename1.ai` too? + +The split is intentional. The core gets the things every modern app benefits from: a way to talk to an LLM, a chat UI, speech in / speech out, the storage primitive for API keys. Those are the building blocks the same way `Display` and `Form` are; an app that wants AI features at all wants those. + +The ML Kit cn1libs are specialised verticals. Barcode scanning, document scanning, and face detection are each useful but only for some apps. They each bring a non-trivial native dependency (Google ML Kit on Android is large; the iOS Vision-framework wrappers add their own weight; the on-device Stable Diffusion model is gigabytes), and the cost of carrying every one of them in core would land on every app whether it used them or not. + +Two of the optional libraries also fall into a "big upload" category that the cloud build server cannot handle within its usual timeouts. `cn1-ai-stablediffusion` bundles a multi-gigabyte model; the `AiDependencyTable` in the Maven plugin flags those with a `cn1.ai.requiresBigUpload` marker and the cloud build aborts pre-upload with a friendly "build this one locally" message. That kind of opt-in does not belong in a framework dependency that every app inherits. -The PR also lands an `IOSShareExtensionBuilder` in the Maven plugin for the *other* half of sharing: iOS apps that want to *receive* shared content from other apps via a Share Extension. The builder emits a complete `.ios.appext` bundle (`Info.plist` with the right `NSExtension` activation rules, the App Group entitlement, a minimal `ShareViewController.swift` that lands the payload in the App Group via `UserDefaults(suiteName:)`, and the matching `buildSettings.properties`). The result composes with the existing `IPhoneBuilder.extractAppExtensions` pipeline, so apps that use the builder do not need to bootstrap anything in Xcode and apps that already have a hand-rolled extension keep working. +So the rule we ended up with is: anything that is "AI plumbing" goes in core (the client, the streaming, the chat UI, speech, key storage). Anything that is a "model bundled with native glue for one specific use case" is a cn1lib. The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a publish workflow, so if you have a model that fits an opinion the framework does not ship yet, the path to a published cn1lib is one command. -## AI +The corresponding chapter, including the full `LlmClient` API table, the `ChatView` reference, the speech bridges, the `SecureStorage` overloads, the simulator Ollama redirect, and the cn1lib coverage, is at [AI, Chat UI, and Speech](https://www.codenameone.com/developer-guide/#_ai_chat_ui_and_speech) in the developer guide. -The largest of the four surfaces. [PR #5035](https://github.com/codenameone/CodenameOne/pull/5035) lands the package, the ChatView, speech, the simulator Ollama redirect, and the build-time dependency injection; [PR #5057](https://github.com/codenameone/CodenameOne/pull/5057) lands the developer-guide chapter and the agent-skill update so generated projects' `AGENTS.md` covers the new APIs from the first prompt. +## OAuth and OIDC: the modern identity stack -`com.codename1.ai.LlmClient` is the entry point: +The in-app-WebView `Oauth2` flow that Codename One has shipped since approximately forever was the way every cross-platform mobile framework solved "sign in with Google / Facebook / Microsoft" in the 2010s. It is also the way every one of those identity providers stopped wanting you to solve it. Google has been blocking embedded user agents for years. Apple does not want third-party apps wrapping the Apple ID flow in a `WKWebView`. Microsoft and Facebook joined the chorus. The right answer is the system browser: `ASWebAuthenticationSession` on iOS, Custom Tabs on Android, with PKCE on the wire. That is what [PR #5018](https://github.com/codenameone/CodenameOne/pull/5018) lands. [PR #5039](https://github.com/codenameone/CodenameOne/pull/5039) adds a portable WebAuthn / passkey client on top. + +### Sign in with Google (or any OIDC provider) + +`com.codename1.io.oidc.OidcClient` is the entry point. Point it at the discovery URL of an OIDC provider, hand it the client id and the redirect URI you registered with the provider, ask for tokens: ```java -LlmClient client = LlmClient.openai(apiKey); +OidcConfiguration cfg = OidcConfiguration.discover("https://accounts.google.com"); -ChatRequest req = new ChatRequest.Builder() - .model("gpt-4o-mini") - .system("You are a helpful assistant.") - .user("What is the capital of France?") - .temperature(0.7) +OidcClient client = OidcClient.builder() + .configuration(cfg) + .clientId("123-abc.apps.googleusercontent.com") + .redirectUri("com.example.myapp:/oauthredirect") + .scopes("openid", "email", "profile") .build(); -client.chat(req).onResult((resp, err) -> { - if (err != null) return; - System.out.println(resp.firstChoice().content()); +client.signIn().onResult((tokens, err) -> { + if (err != null) { + OidcException oe = (OidcException) err; + if (oe.getCode() == OidcException.USER_CANCELLED) return; + Log.e(oe); + return; + } + String idToken = tokens.getIdToken().raw(); + String email = tokens.getIdToken().getClaim("email").asString(); + proceed(email, idToken); }); ``` -`LlmClient.openai(...)`, `LlmClient.anthropic(...)`, `LlmClient.gemini(...)`, `LlmClient.ollama(...)`, and `LlmClient.openAiCompatible(baseUrl, apiKey)` are the static factories. OpenAI is the fully implemented native client; the same client drives Ollama, vLLM, and llama.cpp because the wire format is OpenAI-compatible. Anthropic and Gemini compile and register but currently throw a clear error pointing callers at the OpenAI-compat shim while their native clients are in flight. +Discovery JSON parsed and cached. PKCE S256 challenge generated and verified. State and nonce checked on the callback. ID-token claims decoded for you (we deliberately do not verify the signature client-side; the dev guide is explicit about why and points at the "re-validate on your backend" remedy). Refresh and revoke are first-class. The token store is pluggable via `TokenStore`; the default is `Storage`-backed, but a Keychain-backed or in-memory variant is a small class. -Streaming chat is a separate entry point, and is the one most chat UIs actually want: +On iOS the system-browser piece routes through `ASWebAuthenticationSession`. On Android through `androidx.browser.customtabs` with a plain `ACTION_VIEW` fallback for the rare device with no Custom Tabs provider. `AuthenticationServices.framework` and `androidx.browser:browser` are auto-linked when the classpath scanner sees `OidcClient` in use. + +### Provider wrappers: Google, Apple, Microsoft, Facebook, Auth0, Firebase + +If you would rather not configure OIDC by hand, the existing social classes get a `signIn(...)` method that drives the same stack with the provider's issuer URL pre-wired: ```java -client.chatStream(req, new ChatStreamListener() { - @Override public void onDelta(ChatDelta d) { appendToUi(d.contentDelta()); } - @Override public void onComplete(ChatResponse fin) { /* ... */ } - @Override public void onError(Throwable t) { /* ... */ } -}); +GoogleConnect.signIn(googleClientId, + "com.example.myapp:/oauthredirect", + "openid", "email", "profile") + .onResult((tokens, err) -> { /* ... */ }); + +MicrosoftConnect.signIn(entraClientId, + "msauth.com.example.myapp://auth", + "User.Read") + .onResult((tokens, err) -> { /* ... */ }); + +Auth0Connect.signIn("tenant.auth0.com", clientId, redirectUri, + "openid profile email") + .onResult((tokens, err) -> { /* ... */ }); ``` -Under the hood this is a custom `ConnectionRequest` subclass that parses SSE line-by-line and dispatches deltas through `Display.callSerially`, so your UI updates land on the EDT. `AsyncResource.cancel()` kills the socket cleanly. +`FacebookConnect.signIn(...)` follows the same shape against the Facebook OIDC endpoint. `FirebaseAuth` covers the REST-based Firebase auth surface (email / password, IdP token exchange, refresh) which sits underneath any provider hand-off you might want to drive from app code. + +### Sign in with Apple -The rest of the surface is what a modern LLM SDK looks like: `ChatRequest` / `ChatMessage` / `MessagePart` for multimodal content, `Tool` / `ToolCall` / `ToolChoice` for function calling, `ResponseFormat` for JSON-mode and JSON-schema-mode, `Embedding` for the embeddings endpoint, `ImageGenerator` for DALL-E. Utility extras include `PromptTemplate`, `Tokenizer`, `RetryPolicy`, `ConversationStore`, and `SafetyFilter`. +Sign in with Apple is required on iOS for apps that offer any other social login, and on Android it must fall through to a web flow. `com.codename1.social.AppleSignIn` handles both transparently: -### Simulator Ollama redirect +```java +AppleSignIn.signIn() + .onResult((result, err) -> { + if (err != null) return; + String idToken = result.getIdToken(); + String code = result.getAuthorizationCode(); + proceedToBackend(idToken, code); + }); +``` -The simulator detail that matters most in practice: `JavaSEPort` pings `localhost:11434` at startup, and if it finds Ollama running, sets the `cn1.ai.ollamaDetected` property. A small `SimulatorRedirect` consumer reads that, and with `cn1.ai.simulatorRedirect=auto` (or `=ollama`), every `LlmClient.openai(...)` call routes through the local Ollama endpoint instead of OpenAI's. Production code does not change; your tests, your iteration loop, and your offline debugging stop costing money and stop needing an internet connection. +On iOS 13 and later this drops directly into the native Apple sheet via `ASAuthorizationAppleIDProvider`. On non-iOS platforms it falls through to the same OIDC web flow as everything else, so a single line of app code does the right thing on every port. The Maven plugin injects the `com.apple.developer.applesignin` entitlement on iOS when it sees `AppleSignIn` in use; Android does not see it because it is not there. -### ChatView +### Migration from the legacy Oauth2 -`com.codename1.components.ChatView` is the matching UI component. Scrollable message list, `ChatBubble` for the per-message bubble (theme-aware UIIDs so it picks up the iOS Modern / Material 3 native themes consistently), `ChatInput` for the bottom input bar, and the convenience method that wires the whole thing together: +`com.codename1.io.Oauth2` is now deprecated. Existing code still compiles, but the migration is short and almost always shorter than what it replaces: ```java -ChatView view = new ChatView(); -view.bindToLlm(LlmClient.openai(apiKey), - new ChatRequest.Builder().model("gpt-4o-mini").build()); +// Before +Oauth2 oauth = new Oauth2("https://accounts.google.com/o/oauth2/auth", clientId, redirectUri); +oauth.setClientSecret(clientSecret); +oauth.setScope("openid email profile"); +oauth.setBrowserComponent(myBrowserComponent); // tied to a WKWebView +String token = oauth.authenticate(); // blocks, opens the web view +``` -Form f = new Form("Chat", new BorderLayout()); -f.add(BorderLayout.CENTER, view); -f.show(); +```java +// After +OidcClient.builder() + .configuration(OidcConfiguration.discover("https://accounts.google.com")) + .clientId(clientId) + .redirectUri(redirectUri) + .scopes("openid", "email", "profile") + .build() + .signIn() + .onResult((tokens, err) -> proceed(tokens.getIdToken().raw())); +``` + +You stop owning the browser. The OS owns it. The cookies live in the platform's authentication session. The user gets the same login experience they have everywhere else on their device. + +### WebAuthn / passkeys + +[PR #5039](https://github.com/codenameone/CodenameOne/pull/5039) layers a portable WebAuthn client on top: + +```java +WebAuthnClient client = WebAuthnClient.getInstance(); +if (!client.isAvailable()) { fallbackToPassword(); return; } + +PublicKeyCredentialCreationOptions opts = + PublicKeyCredentialCreationOptions.fromServerJson(serverJson); +client.create(opts).onResult((cred, err) -> { + if (err == null) postToRelyingParty(cred.toJson()); +}); ``` -Five lines, working streaming chat UI. +W3C JSON wire format in both directions, so the response can be POSTed verbatim to any standard server-side WebAuthn library. iOS 16+ routes through `ASAuthorizationPlatformPublicKeyCredentialProvider`; Android API 28+ through `androidx.credentials.CredentialManager`. Provider helpers: `Auth0Connect.signInWithPasskey(...)` / `.registerPasskey(...)` and `FirebaseAuth.signInWithPasskey(...)` / `.registerPasskey(...)`. + +One thing worth pulling out before you reach for it: if you sign in via OIDC against Google, Apple, Microsoft, Auth0, or Firebase, you usually already *get* passkeys for free. The identity provider runs the WebAuthn ceremony inside the system browser; OIDC just hands you the resulting tokens. So you do not need `WebAuthnClient` for that case. You need it for apps that run their own relying-party backend, and for apps driving the Auth0 or Firebase passkey grants directly. + +Full chapter: [Authentication and Identity](https://www.codenameone.com/developer-guide/#_authentication_and_identity). + +## Connectivity: WiFi, Bonjour, USB, network-type listeners -### Speech and SecureStorage +[PR #5021](https://github.com/codenameone/CodenameOne/pull/5021) lands four packages for apps that need to do more with the network than open an HTTP socket. The shape: + +```java +WiFi wifi = WiFi.getInstance(); +String ssid = wifi.getCurrentSSID(); +String bssid = wifi.getBSSID(); +String gateway = wifi.getGateway(); +String ip = wifi.getIp(); -Two new core APIs land alongside the LLM surface. `TextToSpeech.getInstance().speak("Hello")` and `SpeechRecognizer.getInstance().recognize().onResult(...)`. The native bridges (iOS `SFSpeechRecognizer` / `AVSpeechSynthesizer`, Android `android.speech.*` and `TextToSpeech`) are tracked follow-ups; the simulator already has a best-effort TTS via `say` on macOS, `espeak` on Linux, SAPI on Windows. +wifi.scan(new ScanOptions().setTimeoutMillis(5000)) + .onResult((results, err) -> { /* ... */ }); -`SecureStorage` gains single-argument non-prompting overloads of `get / set / remove(account, ...)` next to the existing biometric-gated methods. The reason is LLM API keys: you read them on every network call; you cannot prompt the user for Face ID every time. The new overloads store the key behind the keychain / keystore protection class without the user-presence gate. +wifi.connect("MyNetwork", "hunter2", Security.WPA2_PSK) + .onResult((success, err) -> { /* ... */ }); +``` -### Build-time dependency injection +`com.codename1.io.wifi` for WiFi info, scan, and connect. `com.codename1.io.wifi.WiFiDirect` for peer-to-peer (Android only by platform reality). `com.codename1.io.bonjour` for mDNS / Zeroconf via `BonjourBrowser` and `BonjourPublisher`. `com.codename1.io.usb` for USB host (Android only). And `NetworkManager.addNetworkTypeListener(...)` plus `NETWORK_TYPE_*` constants so an app can react to a transition between cellular, WiFi, ethernet, or "none": -`AiDependencyTable` in the Maven plugin is the piece that makes "add an LLM call to your app and the right native dependencies show up" actually true. 18 entries mapping `com/codename1/ai/*` and the speech / TTS classes to iOS pods, Swift Packages, Android Gradle deps, `Info.plist` usage strings, and Android permissions. Entries that bundle native blobs over 2 GB (on-device Stable Diffusion) flip a `cn1.ai.requiresBigUpload` flag and the cloud build aborts pre-upload with a friendly "build this one locally" message. +```java +NetworkManager.getInstance().addNetworkTypeListener(evt -> { + int type = evt.getNetworkType(); + if (type == NetworkManager.NETWORK_TYPE_NONE) showOfflineBanner(); + else if (type == NetworkManager.NETWORK_TYPE_CELLULAR) suppressLargeBackgroundDownloads(); + else clearOfflineBanner(); +}); +``` + +iOS does not expose programmatic WiFi scanning to third-party apps; `scan()` throws `UnsupportedOperationException` on iOS. iOS also does not expose WiFi Direct or general USB host. None of those are Codename One limitations; they are Apple's. The dev guide is explicit about each platform's limits. + +Three new compile-time defines (`CN1_INCLUDE_WIFI_INFO`, `CN1_INCLUDE_HOTSPOT`, `CN1_INCLUDE_BONJOUR`) wrap the iOS native code, set only when the classpath scanner sees the matching Java API in use. Apps that do not use these APIs do not pay for them at App Store review time. Same pattern as the NFC gating from the previous release. + +Full reference: [Network Connectivity](https://www.codenameone.com/developer-guide/#_network_connectivity). + +## Share-sheet result callbacks + +[PR #5036](https://github.com/codenameone/CodenameOne/pull/5036) closes a small but persistent gap: `Display.share(...)` and `ShareButton` finally tell you what the user did with the share sheet: + +```java +ShareButton btn = new ShareButton(); +btn.setTextToShare("Look at this fox"); +btn.setImageToShare("/fox.jpg"); +btn.setShareResultListener(result -> { + switch (result.getStatus()) { + case SHARED_TO: track("share_completed", result.getTargetPackage()); break; + case DISMISSED: track("share_dismissed"); break; + case FAILED: track("share_failed", result.getError()); break; + } +}); +``` -The companion cn1libs in this release: `cn1-ai-mlkit-barcode`, `cn1-ai-mlkit-docscan`, `cn1-ai-mlkit-face`. The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a publish workflow. +iOS routes through `UIActivityViewController.completionWithItemsHandler`; Android through `Intent.createChooser` with an `IntentSender` callback (API 22+). The framework normalises the platform values into `SHARED_TO(packageName)`, `DISMISSED`, or `FAILED`. -Full chapter: [Ai-And-Speech.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Ai-And-Speech.asciidoc). +The same PR also lands an `IOSShareExtensionBuilder` in the Maven plugin that emits a complete `.ios.appext` bundle (Info.plist, App Group entitlements, a minimal `ShareViewController.swift`, the matching `buildSettings.properties`) so apps that want to *receive* shared content from other apps no longer need to bootstrap an extension target in Xcode by hand. ## What ties this together -The shared structural element across all four surfaces is the scanner-driven gating. Every PR in this batch flips a single per-feature flag (`usesOidc`, `usesAppleSignIn`, `usesWebAuthn`, `usesAi`, the existing WiFi/Bonjour/Hotspot defines) that drives framework linking, entitlement injection, plist injection, and Objective-C conditional compilation. Apps that do not reference the new APIs do not pay for them at App Store review time, and the binaries do not even contain the platform calls. This is the pattern we settled on for NFC and biometrics two weeks ago, and it is the right answer for "these APIs are part of the core, but apps that do not use them should not pay for them at review time". +Every API in this batch flips a single per-feature flag (`usesOidc`, `usesAppleSignIn`, `usesWebAuthn`, `usesAi`, plus the existing WiFi / Bonjour / Hotspot defines) that drives framework linking, entitlement injection, plist injection, and Objective-C conditional compilation. Apps that do not reference the new APIs do not pay for them at App Store review time, and the binaries do not even contain the platform calls. That is the same pattern we shipped for NFC and biometrics in the previous release, and it is what makes "these APIs are part of the core" sustainable; the cost only lands on the apps that actually use them. -The companion cloud build server changes (BuildDaemon mirrors) ship together so local builds and cloud builds match across all four surfaces. +The companion cloud build server changes (BuildDaemon mirrors) ship together so local builds and cloud builds match. ## Wrapping up -Connectivity, identity, sharing, and AI. All in the core. Same auto-injection story across the four of them. The corresponding chapters cover each surface in detail; the place to read across them is the build-hint and entitlement table in [Advanced-Topics-Under-The-Hood.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc). +The next post is on Wednesday and covers the architectural change in this release: a build-time bytecode annotation framework, the declarative router that is its first consumer, the SQLite ORM and JSON / XML mappers and component binder built on the same SPI, and the build-time SVG / Lottie transcoder that ships in the same release for related reasons. -Wednesday's post is the architectural one: the build-time bytecode annotation framework, the declarative router that is its first consumer, the SQLite ORM and JSON / XML mappers and component binder it carries, and the SVG / Lottie transcoder that ships in the same release because it sits on the same "emit Java at build time" pattern. +Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). --- diff --git a/docs/website/static/blog/platform-apis-in-the-core.jpg b/docs/website/static/blog/platform-apis-in-the-core.jpg new file mode 100644 index 0000000000000000000000000000000000000000..75cfb4df7b4d5adc3184b19370fa632e07a73071 GIT binary patch literal 32699 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 literal 0 HcmV?d00001 From c6231acd5df6ac196082929b6ac5e35428a251f0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 18:53:15 +0300 Subject: [PATCH 03/13] Platform-APIs post: review fixes - Opening description rewritten so it does not read as a comma list. - Dropped the "AI first, OAuth second" meta-paragraph. - Anthropic and Gemini are no longer described as "still in flight"; both are fully implemented in this release. - Replaced the SecureStorage "small thing that matters more than it sounds" framing with a proper "How to handle API keys" section that opens with the security rule (never check in, never embed, never hardcode), explains the proper shape (fetch from your backend over an authenticated request, cache to the platform keychain via SecureStorage), and gives a concrete getOpenAiKey example. - ChatView ASCII mockup replaced with a screenshot reference and a richer code example that drives the chat manually (ConversationStore + per-message lifecycle + error path). - Removed the Speech / TTS subsection entirely. The native iOS / Android bridges are still tracked follow-ups in PR #5035 and are not shipping in this release. - iOS Share Extension paragraph expanded into a real "how your app appears in other apps' share menus" section with a pom.xml configuration block, the cn1:generate-ios-share-extension Mojo invocation, and a host-side payload-read example. - AI cn1libs rewritten. The 13 cn1libs land in a feature table with a one-line "what it gives you" column each, followed by a "how to add and use them" paragraph and three short worked examples (barcode, text recognition, translation). The "why these are cn1libs and not core" answer follows the concrete list rather than appearing without context. - "What ties this together" section removed. --- .../content/blog/platform-apis-in-the-core.md | 230 +++++++++++++----- 1 file changed, 171 insertions(+), 59 deletions(-) diff --git a/docs/website/content/blog/platform-apis-in-the-core.md b/docs/website/content/blog/platform-apis-in-the-core.md index f571495325..bf0beeaf91 100644 --- a/docs/website/content/blog/platform-apis-in-the-core.md +++ b/docs/website/content/blog/platform-apis-in-the-core.md @@ -4,16 +4,14 @@ slug: platform-apis-in-the-core url: /blog/platform-apis-in-the-core/ date: '2026-06-01' author: Shai Almog -description: A com.codename1.ai package with LlmClient, ChatView, streaming, tool calls, and a simulator Ollama redirect. A modern OAuth / OIDC stack that runs through the system browser and includes Sign in with Apple, Google, Microsoft, Auth0, Firebase, and WebAuthn passkeys. Built-in WiFi / Bonjour / USB and share-sheet result callbacks alongside. Deep tutorials and a note on why the ML Kit AI features stayed in cn1libs. -feed_html: 'AI, OAuth, And Other Platform APIs In The Core A com.codename1.ai package with LlmClient and ChatView, a modern OAuth / OIDC stack with WebAuthn passkeys, plus built-in WiFi / Bonjour / USB and share-sheet result callbacks. Deep tutorials and a note on why ML Kit stayed in cn1libs.' +description: Deeper AI integration in the framework core, modern authentication via OAuth / OIDC and WebAuthn passkeys driven from the system browser, and a few smaller additions (WiFi / connectivity, share-sheet result callbacks) alongside. +feed_html: 'AI, OAuth, And Other Platform APIs In The Core Deeper AI integration in the framework core, modern authentication via OAuth / OIDC and WebAuthn passkeys driven from the system browser, and a few smaller additions alongside.' --- ![AI, OAuth, And Other Platform APIs In The Core](/blog/platform-apis-in-the-core.jpg) This is the second follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). It covers the platform APIs that moved into the framework core this release. There are two headline pieces (AI / LLM and the modern OAuth / OIDC stack), and two smaller pieces (WiFi / connectivity and share-sheet result callbacks). This continues the direction the previous release set when we moved NFC, biometrics, and cryptography into the framework core. The full background on that earlier set is in [NFC, Crypto, Biometrics, And A New Build Cloud](/blog/nfc-crypto-biometrics-and-build-cloud/). -The order below mirrors how much code each section is likely to change in your app. AI first, OAuth / OIDC second, the smaller items at the end. - ## AI: a first-class LLM client and a ChatView component [PR #5035](https://github.com/codenameone/CodenameOne/pull/5035) lands the `com.codename1.ai` package, the `ChatView` UI component, the speech and TTS additions, and the build-time dependency injection that wires the native pieces in. [PR #5057](https://github.com/codenameone/CodenameOne/pull/5057) lands the developer-guide chapter and the agent-skill addition so any project generated from the [Initializr](/initializr/) inherits the new APIs through its bundled `AGENTS.md`. @@ -41,7 +39,7 @@ client.chat(req).onResult((resp, err) -> { }); ``` -`LlmClient.openai(...)`, `LlmClient.anthropic(...)`, `LlmClient.gemini(...)`, `LlmClient.ollama(...)`, and `LlmClient.openAiCompatible(baseUrl, apiKey)` are the factories. OpenAI has the fully implemented native client today. The same client drives Ollama, vLLM, and llama.cpp because their wire formats are OpenAI-compatible. Anthropic and Gemini compile and register, but their native clients are still in flight; they throw a clear error pointing you at the OpenAI-compat shim until the native ones land. +`LlmClient.openai(...)`, `LlmClient.anthropic(...)`, `LlmClient.gemini(...)`, `LlmClient.ollama(...)`, and `LlmClient.openAiCompatible(baseUrl, apiKey)` are the factories. All five are fully implemented native clients. The OpenAI client also drives Ollama, vLLM, llama.cpp, and any other endpoint that speaks the OpenAI wire format, so most local-model stacks plug in through `LlmClient.openAiCompatible(...)` without a separate driver. ### Streaming chat (what you actually want for chat UIs) @@ -147,90 +145,172 @@ simulator.cn1.ai.simulatorRedirect=auto (The `simulator.` prefix scopes the property to the JavaSE simulator path.) Then run Ollama locally with whichever model your code expects (`ollama run llama3.2` or similar) and your existing `LlmClient.openai(...)` calls go to localhost. -### SecureStorage non-prompting overloads +### How to handle API keys + +A direct word on credentials before any of the above sees production. LLM provider API keys (OpenAI, Anthropic, Gemini, your Auth0 / Firebase configs) are bearer tokens with a budget attached. **They must never be checked into source control, embedded in your app binary, or hard-coded in code.** A leaked key can be extracted from any APK or IPA in minutes and used to drain your account. + +The correct shape is to fetch the key from your own backend over an authenticated request, then store it on the device using the platform's keychain / keystore. The framework provides both pieces: -Small thing that matters more than it sounds: the existing biometric-gated `SecureStorage.get / set / remove(account, options...)` methods got new single-argument overloads that do not prompt for biometrics. The reason is LLM API keys. You read them on every network call; you cannot prompt the user for Face ID every time. The new overloads store the key behind the keychain / keystore protection class without the user-presence gate. Existing biometric-gated calls keep working. +- `com.codename1.crypto.SecureStorage` (from the [previous release](/blog/nfc-crypto-biometrics-and-build-cloud/#cryptography--pr-4994)) is the cross-platform wrapper over iOS Keychain Services and Android `EncryptedSharedPreferences`. Values are encrypted at rest using the platform's hardware-backed protection class where one is available. +- This release adds single-argument `get / set / remove(account, ...)` overloads next to the existing biometric-gated methods. The new overloads store the value without a per-read Face ID / Touch ID prompt, which is what you want for an LLM API key (you read it on every network call; a biometric prompt every time is not workable). The biometric-gated methods are still there for credentials you do want to gate per use. + +A reasonable shape: ```java -SecureStorage.set("openai_api_key", apiKey); // no prompt -String key = SecureStorage.get("openai_api_key"); +private static AsyncResource getOpenAiKey() { + String cached = SecureStorage.get("openai_api_key"); + if (cached != null) { + return AsyncResource.complete(cached); + } + return Rest.get(myServer + "/v1/credentials/openai") + .bearerToken(userSessionToken()) + .fetchAsString() + .onResult((key, err) -> { + if (err == null) { + SecureStorage.set("openai_api_key", key); + } + }); +} ``` +Your server gates the credential request behind the user's session, your app caches the result on the keychain, and the key never sits anywhere a reverse-engineering pass could find it. If your server rotates the key, invalidate the cache and refetch. + +Existing biometric-gated `SecureStorage` calls keep working unchanged. The new overloads are additive. + ### ChatView: a ready-made streaming chat UI `com.codename1.components.ChatView` is the matching UI component. Scrollable message list, `ChatBubble` for the per-message bubble (theme-aware UIIDs so it picks up the iOS Modern / Material 3 native themes consistently), `ChatInput` for the bottom input bar, and a one-line `bindToLlm(...)` that wires the input to a streaming chat request: ```java ChatView view = new ChatView(); -view.bindToLlm(LlmClient.openai(SecureStorage.get("openai_api_key")), - new ChatRequest.Builder() - .model("gpt-4o-mini") - .system("You are a friendly tutor for Codename One developers.") - .build()); +getOpenAiKey().onResult((key, err) -> { + view.bindToLlm(LlmClient.openai(key), + new ChatRequest.Builder() + .model("gpt-4o-mini") + .system("You are a friendly tutor for " + + "Codename One developers.") + .build()); +}); Form f = new Form("Chat", new BorderLayout()); f.add(BorderLayout.CENTER, view); f.show(); ``` -The on-screen shape that comes out of that is the standard messaging layout: +The result is a standard mobile chat layout, picked up from whichever native theme the project uses: +![ChatView running against gpt-4o-mini, showing assistant and user bubbles plus a streaming response and the bottom input bar](/blog/platform-apis-in-the-core/chatview.png) + +If you want more control than `bindToLlm(...)` gives you (custom message styling, a "thinking" placeholder, hand-rolled retry, persistence to your own model class), drive the view by hand: + +```java +ChatView view = new ChatView(); +ConversationStore store = ConversationStore.open("tutor-thread"); +view.setMessages(store.load()); + +LlmClient client = LlmClient.openai(apiKeyFromKeychain); + +view.setInputListener(userText -> { + ChatMessage userMsg = ChatMessage.user(userText); + view.appendMessage(userMsg); + store.append(userMsg); + + ChatMessage assistant = ChatMessage.assistant(""); + view.appendMessage(assistant); + + ChatRequest req = new ChatRequest.Builder() + .model("gpt-4o-mini") + .messages(store.load()) + .build(); + + client.chatStream(req, new ChatStreamListener() { + @Override + public void onDelta(ChatDelta d) { + view.appendToLastMessage(d.contentDelta()); + } + @Override + public void onComplete(ChatResponse fin) { + store.append(ChatMessage.assistant(view.lastMessage().content())); + view.setInputEnabled(true); + } + @Override + public void onError(Throwable t) { + view.appendToLastMessage(" [error: " + t.getMessage() + "]"); + view.setInputEnabled(true); + } + }); +}); ``` -+---------------------------------------+ -| Chat | -+---------------------------------------+ -| | -| +-------------------------+ | -| | Hi! How can I help? | | <- assistant bubble -| +-------------------------+ | -| | -| +----------------------+ | -| | What is a Form? | | <- user bubble -| +----------------------+ | -| | -| +------------------------------+ | -| | A Form is the top-level UI… | | <- streaming -| +------------------------------+ | -| | -+---------------------------------------+ -| Type your message… [ Send ] | <- ChatInput -+---------------------------------------+ -``` -`appendToLastMessage(...)` is the entry point if you want to drive the streaming yourself (the binding above already does it for you). It marshals through `callSerially` so deltas land on the EDT in order. +`appendToLastMessage(...)` is the streaming entry point; it marshals through `callSerially` so deltas land on the EDT in order. `ConversationStore` persists the thread (the default backing is `Storage`; pluggable via a custom implementation if you would rather keep it in SQLite or push it to your server). + +### The AI cn1libs + +A set of opt-in AI cn1libs ships alongside the core LLM stack. Each one packages a specific capability (a Google ML Kit feature, a TensorFlow Lite runtime, a local Whisper transcription engine, an on-device Stable Diffusion model) along with the iOS frameworks, Android Gradle dependencies, plist usage strings, and permissions that capability needs. The full list, with the native dependency each one resolves to, is: -### Speech and TTS +| cn1lib | What it gives you | +|---|---| +| `cn1-ai-mlkit-text` | ML Kit text recognition (OCR from photos or live camera). | +| `cn1-ai-mlkit-barcode` | ML Kit barcode and QR scanning. | +| `cn1-ai-mlkit-face` | ML Kit face detection with landmarks and contours. | +| `cn1-ai-mlkit-labeling` | ML Kit image labelling ("what is in this picture"). | +| `cn1-ai-mlkit-translate` | ML Kit on-device translation between supported languages. | +| `cn1-ai-mlkit-smartreply` | ML Kit Smart Reply suggestions for short messages. | +| `cn1-ai-mlkit-langid` | ML Kit on-device language identification. | +| `cn1-ai-mlkit-pose` | ML Kit pose detection (body landmarks). | +| `cn1-ai-mlkit-segmentation` | ML Kit selfie segmentation (person / background mask). | +| `cn1-ai-mlkit-docscan` | ML Kit Document Scanner; iOS also pulls in `VisionKit`. | +| `cn1-ai-tflite` | TensorFlow Lite interpreter. Bring your own model. | +| `cn1-ai-whisper` | On-device Whisper transcription via a bundled `libwhisper.a`. | +| `cn1-ai-stablediffusion` | On-device Stable Diffusion. Core ML on iOS, ONNX runtime on Android. | -Two new core APIs land alongside the LLM surface: +Adding any of them to a project is the same path as any other cn1lib. From Codename One Preferences → cn1libs, tick the one you want and refresh; the next build links the right native pieces in. The build-time `AiDependencyTable` in the Maven plugin handles the rest: the iOS pod or Swift Package, the Android Gradle dependency, the plist usage strings (`NSCameraUsageDescription` for the vision libraries, `NSSpeechRecognitionUsageDescription` for Whisper, etc.), and the Android permissions (`android.permission.RECORD_AUDIO` for audio capture) are all injected by the scanner the first time it sees the matching class on the classpath. + +A typical use looks like the rest of the framework. Reading a barcode: ```java -TextToSpeech.getInstance().speak("Hello, world."); +new BarcodeScanner() + .scan() + .onResult((barcode, err) -> { + if (err != null) return; + Log.p("Scanned " + barcode.format() + ": " + barcode.value()); + }); +``` -SpeechRecognizer rec = SpeechRecognizer.getInstance(); -if (!rec.isSupported()) { - fallbackToTyping(); - return; -} -rec.recognize().onResult((text, err) -> { - if (err == null) sendChatMessage(text); -}); +Detecting text in an image: + +```java +new TextRecognizer() + .recognize(Image.createFromCapturedPhoto(photoPath)) + .onResult((result, err) -> { + if (err != null) return; + for (TextBlock block : result.blocks()) { + Log.p(block.text()); + } + }); ``` -The platform plan is iOS routed through `SFSpeechRecognizer` and `AVSpeechSynthesizer`, Android through `android.speech.*` and the `TextToSpeech` engine. The native bridges are tracked follow-ups (they need on-device testing before they ship). The simulator already has a best-effort TTS via `say` on macOS, `espeak` on Linux, SAPI on Windows; recognition stays unsupported in the simulator unless you add the `cn1-ai-whisper` cn1lib. +Translating a sentence on-device: + +```java +new Translator(Language.ENGLISH, Language.FRENCH) + .translate("Codename One") + .onResult((translated, err) -> { /* "Codename One" :) */ }); +``` -### Why are the ML Kit features still cn1libs? +### Why are these cn1libs and not part of the core? -A fair question given the opening framing of this post. If "fundamental device APIs should be in the core", why does AI ship with a set of cn1libs (`cn1-ai-mlkit-barcode`, `cn1-ai-mlkit-docscan`, `cn1-ai-mlkit-face`, `cn1-ai-whisper`, `cn1-ai-stablediffusion`) rather than rolling all of those into `com.codename1.ai` too? +A fair question given the opening framing of this post. If "fundamental device APIs should be in the core", why does AI ship with thirteen cn1libs? -The split is intentional. The core gets the things every modern app benefits from: a way to talk to an LLM, a chat UI, speech in / speech out, the storage primitive for API keys. Those are the building blocks the same way `Display` and `Form` are; an app that wants AI features at all wants those. +The split is intentional. The core gets the AI plumbing that almost every app that uses AI at all wants: the LLM client, the streaming, the chat UI, the secure storage primitive for credentials, the simulator Ollama redirect for offline iteration. Those are general-purpose; an app that adopts AI at all picks those up. -The ML Kit cn1libs are specialised verticals. Barcode scanning, document scanning, and face detection are each useful but only for some apps. They each bring a non-trivial native dependency (Google ML Kit on Android is large; the iOS Vision-framework wrappers add their own weight; the on-device Stable Diffusion model is gigabytes), and the cost of carrying every one of them in core would land on every app whether it used them or not. +The cn1libs are specialised verticals. Barcode scanning, document scanning, face detection, smart reply, pose detection, on-device translation, on-device speech transcription, on-device image generation; each is genuinely useful but only for some apps. They also each bring a non-trivial native dependency (Google ML Kit Android frameworks are large; the iOS pods add their own weight; the bundled Whisper static library and the on-device Stable Diffusion model are big), and the cost of carrying every one of them in core would land on every app whether it used the feature or not. -Two of the optional libraries also fall into a "big upload" category that the cloud build server cannot handle within its usual timeouts. `cn1-ai-stablediffusion` bundles a multi-gigabyte model; the `AiDependencyTable` in the Maven plugin flags those with a `cn1.ai.requiresBigUpload` marker and the cloud build aborts pre-upload with a friendly "build this one locally" message. That kind of opt-in does not belong in a framework dependency that every app inherits. +A small subset (the on-device Stable Diffusion model, in particular) is also flagged with a `cn1.ai.requiresBigUpload` marker in `AiDependencyTable`. The cloud build server aborts pre-upload with a friendly "build this one locally" message because the multi-gigabyte payload does not fit inside the usual build-server timeouts. That kind of opt-in does not belong in a dependency that every app inherits. -So the rule we ended up with is: anything that is "AI plumbing" goes in core (the client, the streaming, the chat UI, speech, key storage). Anything that is a "model bundled with native glue for one specific use case" is a cn1lib. The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a publish workflow, so if you have a model that fits an opinion the framework does not ship yet, the path to a published cn1lib is one command. +The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a Maven Central publish workflow, so if you have a model that fits a niche the framework does not yet ship, the path to a published cn1lib is one command. -The corresponding chapter, including the full `LlmClient` API table, the `ChatView` reference, the speech bridges, the `SecureStorage` overloads, the simulator Ollama redirect, and the cn1lib coverage, is at [AI, Chat UI, and Speech](https://www.codenameone.com/developer-guide/#_ai_chat_ui_and_speech) in the developer guide. +The corresponding chapter, including the full `LlmClient` API table, the `ChatView` reference, the `SecureStorage` overloads, the simulator Ollama redirect, and the full cn1lib coverage, is at [AI, Chat UI, and Speech](https://www.codenameone.com/developer-guide/#_ai_chat_ui_and_speech) in the developer guide. ## OAuth and OIDC: the modern identity stack @@ -407,13 +487,45 @@ btn.setShareResultListener(result -> { iOS routes through `UIActivityViewController.completionWithItemsHandler`; Android through `Intent.createChooser` with an `IntentSender` callback (API 22+). The framework normalises the platform values into `SHARED_TO(packageName)`, `DISMISSED`, or `FAILED`. -The same PR also lands an `IOSShareExtensionBuilder` in the Maven plugin that emits a complete `.ios.appext` bundle (Info.plist, App Group entitlements, a minimal `ShareViewController.swift`, the matching `buildSettings.properties`) so apps that want to *receive* shared content from other apps no longer need to bootstrap an extension target in Xcode by hand. +### Appearing in other apps' share menus + +The other half of sharing is the inverse direction: not "let the user share *from* your app", but "let your app *receive* content other apps share". If a user is in Safari, Photos, or Mail and taps the share icon, your app should be able to appear as a target there alongside Messages, WhatsApp, and Instagram. On iOS that requires a separate Share Extension target inside the `.ipa`, with its own bundle, its own `Info.plist`, an App Group string that links it to the host app, and a `ShareViewController` that handles the incoming payload. Historically the recommendation was to bootstrap that target by hand in Xcode, copy the resulting files into the Codename One project under `ios/app_extensions/`, and let the build server's extractor consume them. It worked, but it was a workflow most teams put off because the setup is fiddly. + +The same PR ships an `IOSShareExtensionBuilder` Mojo that does all of that for you. A typical setup is one Maven command and a one-time configuration block: + +```xml + + com.codenameone + codenameone-maven-plugin + + + com.example.myapp.share + MyApp + group.com.example.myapp + + PUBLIC_URL + PUBLIC_IMAGE + PUBLIC_TEXT + + + + +``` + +Run `mvn cn1:generate-ios-share-extension` and the Mojo writes a complete `.ios.appext` bundle into `ios/app_extensions/`: the `Info.plist` with the right `NSExtension` activation rules for the content types you declared, the App Group entitlement, a minimal `ShareViewController.swift` that lands the payload in the App Group's `UserDefaults(suiteName:)`, and the matching `buildSettings.properties`. The result feeds straight into the existing `IPhoneBuilder.extractAppExtensions` pipeline, so apps that already have a hand-rolled extension keep working unchanged. -## What ties this together +On the host-app side you read the payload on launch: -Every API in this batch flips a single per-feature flag (`usesOidc`, `usesAppleSignIn`, `usesWebAuthn`, `usesAi`, plus the existing WiFi / Bonjour / Hotspot defines) that drives framework linking, entitlement injection, plist injection, and Objective-C conditional compilation. Apps that do not reference the new APIs do not pay for them at App Store review time, and the binaries do not even contain the platform calls. That is the same pattern we shipped for NFC and biometrics in the previous release, and it is what makes "these APIs are part of the core" sustainable; the cost only lands on the apps that actually use them. +```java +// Anywhere after Display.init has run +String shared = Storage.getInstance() + .readObject("ios.shareExtension.lastPayload"); +if (shared != null) { + handleSharedPayload(shared); +} +``` -The companion cloud build server changes (BuildDaemon mirrors) ship together so local builds and cloud builds match. +After the next cloud or local build, your app appears in the iOS share sheet for the content types you declared. No Xcode work, no hand-rolled plist, no App Group string typed in three places. The build-time tooling owns it. ## Wrapping up From c9ee822a6cef5c6eb1ad6cbd79537f5a39c98020 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 21:20:22 +0300 Subject: [PATCH 04/13] Platform-APIs post: real ChatView screenshot + per-cn1lib subsections - Replaced the placeholder ChatView image reference with a real screenshot pulled from the ChatViewDevGuideScreenshotTest output (scripts/ios/screenshots-metal/ChatView_light.png), cropped to remove the test-harness caption and downscaled for web payload. - Replaced the AI cn1lib quick-table and the "Why are these cn1libs" paragraph with thirteen per-cn1lib subsections (cn1-ai-mlkit-{ text, barcode, face, labeling, translate, smartreply, langid, pose, segmentation, docscan}, cn1-ai-tflite, cn1-ai-whisper, cn1-ai-stablediffusion). Each subsection covers TL;DR, per- platform native bridge, use cases, and a real working code sample using the actual facade signature (TextRecognizer. recognize / BarcodeScanner.scan / FaceDetector.detect / ... PoseDetector.detect / SelfieSegmenter.segment / ... etc). - Up front: the cn1libs aren't in the CN1 Preferences picker yet, so the manual pom.xml dependency snippet is the supported path; the shared pattern is given once with just the artifactId changing per cn1lib. - Dropped the mention of scripts/create-ai-cn1lib.sh as an implementation detail. --- .../content/blog/platform-apis-in-the-core.md | 254 ++++++++++++++---- .../platform-apis-in-the-core/chatview.png | Bin 0 -> 85713 bytes 2 files changed, 209 insertions(+), 45 deletions(-) create mode 100644 docs/website/static/blog/platform-apis-in-the-core/chatview.png diff --git a/docs/website/content/blog/platform-apis-in-the-core.md b/docs/website/content/blog/platform-apis-in-the-core.md index bf0beeaf91..520186601a 100644 --- a/docs/website/content/blog/platform-apis-in-the-core.md +++ b/docs/website/content/blog/platform-apis-in-the-core.md @@ -246,69 +246,233 @@ view.setInputListener(userText -> { ### The AI cn1libs -A set of opt-in AI cn1libs ships alongside the core LLM stack. Each one packages a specific capability (a Google ML Kit feature, a TensorFlow Lite runtime, a local Whisper transcription engine, an on-device Stable Diffusion model) along with the iOS frameworks, Android Gradle dependencies, plist usage strings, and permissions that capability needs. The full list, with the native dependency each one resolves to, is: - -| cn1lib | What it gives you | -|---|---| -| `cn1-ai-mlkit-text` | ML Kit text recognition (OCR from photos or live camera). | -| `cn1-ai-mlkit-barcode` | ML Kit barcode and QR scanning. | -| `cn1-ai-mlkit-face` | ML Kit face detection with landmarks and contours. | -| `cn1-ai-mlkit-labeling` | ML Kit image labelling ("what is in this picture"). | -| `cn1-ai-mlkit-translate` | ML Kit on-device translation between supported languages. | -| `cn1-ai-mlkit-smartreply` | ML Kit Smart Reply suggestions for short messages. | -| `cn1-ai-mlkit-langid` | ML Kit on-device language identification. | -| `cn1-ai-mlkit-pose` | ML Kit pose detection (body landmarks). | -| `cn1-ai-mlkit-segmentation` | ML Kit selfie segmentation (person / background mask). | -| `cn1-ai-mlkit-docscan` | ML Kit Document Scanner; iOS also pulls in `VisionKit`. | -| `cn1-ai-tflite` | TensorFlow Lite interpreter. Bring your own model. | -| `cn1-ai-whisper` | On-device Whisper transcription via a bundled `libwhisper.a`. | -| `cn1-ai-stablediffusion` | On-device Stable Diffusion. Core ML on iOS, ONNX runtime on Android. | - -Adding any of them to a project is the same path as any other cn1lib. From Codename One Preferences → cn1libs, tick the one you want and refresh; the next build links the right native pieces in. The build-time `AiDependencyTable` in the Maven plugin handles the rest: the iOS pod or Swift Package, the Android Gradle dependency, the plist usage strings (`NSCameraUsageDescription` for the vision libraries, `NSSpeechRecognitionUsageDescription` for Whisper, etc.), and the Android permissions (`android.permission.RECORD_AUDIO` for audio capture) are all injected by the scanner the first time it sees the matching class on the classpath. - -A typical use looks like the rest of the framework. Reading a barcode: +The core LLM stack is paired with a set of opt-in cn1libs that wrap specific on-device capabilities: Google ML Kit features, the TensorFlow Lite runtime, a local Whisper transcription engine, and an on-device Stable Diffusion model. Thirteen new cn1libs ship this release. + +These cn1libs are not yet listed in the Codename One Preferences cn1lib picker, so for the moment they are added by hand. Drop the matching dependency block into your project's `common/pom.xml` and rebuild. The build-time scanner does the rest: the iOS pod or Swift Package, the Android Gradle dependency, the plist usage strings (`NSCameraUsageDescription` for the vision libraries, `NSSpeechRecognitionUsageDescription` for Whisper, etc.), and the Android permissions (`android.permission.RECORD_AUDIO` for audio capture) are all injected automatically the first time the scanner sees the matching class on the classpath. + +For each cn1lib below, the dependency block is identical in shape; only the `` changes. The shared pattern is: + +```xml + + com.codenameone + + ${cn1.version} + +``` + +#### `cn1-ai-mlkit-text`: text recognition (OCR) + +**TL;DR.** Pull printed or handwritten text out of an image (a photo of a page, a sign, a receipt) entirely on-device. + +**Platforms.** iOS bridges to `GoogleMLKit/TextRecognition`. Android bridges to `com.google.mlkit:text-recognition`. The JavaSE simulator returns an unsupported error. + +**Use cases.** Receipt scanning, sign translation pipelines (combine with `cn1-ai-mlkit-translate`), accessibility tools that read printed text aloud, automated form ingestion. ```java -new BarcodeScanner() - .scan() - .onResult((barcode, err) -> { - if (err != null) return; - Log.p("Scanned " + barcode.format() + ": " + barcode.value()); - }); +byte[] jpeg = capturePhotoBytes(); +TextRecognizer.recognize(jpeg).onResult((text, err) -> { + if (err == null) Log.p("OCR: " + text); +}); ``` -Detecting text in an image: +#### `cn1-ai-mlkit-barcode`: barcode and QR scanning + +**TL;DR.** Decodes QR, EAN, UPC, Data Matrix, PDF417, and the rest of the common 1D / 2D code families from a captured image. + +**Platforms.** iOS bridges to `MLKitBarcodeScanning`. Android bridges to `com.google.mlkit:barcode-scanning`. The JavaSE simulator returns an unsupported error. + +**Use cases.** Inventory scanning, ticket / boarding-pass readers, QR-driven onboarding flows, retail loyalty cards. ```java -new TextRecognizer() - .recognize(Image.createFromCapturedPhoto(photoPath)) - .onResult((result, err) -> { - if (err != null) return; - for (TextBlock block : result.blocks()) { - Log.p(block.text()); - } +byte[] jpeg = capturePhotoBytes(); +BarcodeScanner.scan(jpeg).onResult((codes, err) -> { + if (err == null) { + for (String code : codes) Log.p("Found: " + code); + } +}); +``` + +#### `cn1-ai-mlkit-face`: face detection + +**TL;DR.** Returns bounding boxes for human faces detected in an image. Each face is reported as a packed `int[4]` (`x`, `y`, `width`, `height`). + +**Platforms.** iOS bridges to `MLKitFaceDetection`. Android bridges to `com.google.mlkit:face-detection`. + +**Use cases.** Auto-crop a contact photo, mosaic / blur bystanders in a group shot, drive a face-tracked overlay for AR-lite filters. + +```java +FaceDetector.detect(jpeg).onResult((boxes, err) -> { + if (err != null) return; + for (int i = 0; i < boxes.length; i += 4) { + Log.p("face at " + boxes[i] + "," + boxes[i + 1] + " " + + boxes[i + 2] + "x" + boxes[i + 3]); + } +}); +``` + +#### `cn1-ai-mlkit-labeling`: image labelling + +**TL;DR.** "What is in this picture." Returns a list of descriptive labels for the image content. + +**Platforms.** iOS bridges to `MLKitImageLabeling`. Android bridges to `com.google.mlkit:image-labeling`. + +**Use cases.** Auto-tagging uploaded photos, content moderation pre-filters, content-based image search. + +```java +ImageLabeler.label(jpeg).onResult((labels, err) -> { + if (err == null) Log.p("labels: " + String.join(", ", labels)); +}); +``` + +#### `cn1-ai-mlkit-translate`: on-device translation + +**TL;DR.** Translate short text between supported language pairs entirely on-device; no server round-trip, no API key, works offline. + +**Platforms.** iOS bridges to `MLKitTranslate`. Android bridges to `com.google.mlkit:translate`. Languages are identified by their ISO 639-1 codes (`en`, `fr`, `es`, ...). + +**Use cases.** Offline travel assistants, chat translation, accessibility readers for foreign signage (combine with `cn1-ai-mlkit-text`). + +```java +Translator.translate("Where is the train station?", "en", "fr") + .onResult((fr, err) -> { + if (err == null) Log.p(fr); // "Où est la gare ?" }); ``` -Translating a sentence on-device: +#### `cn1-ai-mlkit-smartreply`: short reply suggestions + +**TL;DR.** Generates short suggested replies for chat conversations, similar to Gmail's Smart Reply chips. + +**Platforms.** iOS bridges to `MLKitSmartReply`. Android bridges to `com.google.mlkit:smart-reply`. The input is a JSON array of `{role, message, timestamp, userId}` objects. + +**Use cases.** A "quick reply" row above the keyboard in your in-app chat, response suggestions in a CRM inbox. ```java -new Translator(Language.ENGLISH, Language.FRENCH) - .translate("Codename One") - .onResult((translated, err) -> { /* "Codename One" :) */ }); +String thread = "[{\"role\":\"remote\",\"message\":\"See you at 6?\"," + + "\"timestamp\":" + System.currentTimeMillis() + "," + + "\"userId\":\"u42\"}]"; + +SmartReply.suggest(thread).onResult((suggestions, err) -> { + if (err == null) { + for (String s : suggestions) Log.p("suggestion: " + s); + } +}); ``` -### Why are these cn1libs and not part of the core? +#### `cn1-ai-mlkit-langid`: language identification + +**TL;DR.** Returns the most likely ISO 639-1 code for a given text, or `und` (undetermined) when the input is too short or ambiguous. + +**Platforms.** iOS bridges to `MLKitLanguageID`. Android bridges to `com.google.mlkit:language-id`. -A fair question given the opening framing of this post. If "fundamental device APIs should be in the core", why does AI ship with thirteen cn1libs? +**Use cases.** Auto-route a customer-support message to the right team, pick the correct TTS voice for an arbitrary string, pre-screen input before running an expensive translate. + +```java +LanguageIdentifier.identify("Bonjour le monde").onResult((code, err) -> { + if (err == null) Log.p(code); // "fr" +}); +``` + +#### `cn1-ai-mlkit-pose`: pose detection + +**TL;DR.** Returns 33 skeletal landmarks per detected pose as a packed `float[3 * 33]` (`x`, `y`, `confidence` triples). + +**Platforms.** iOS bridges to `MLKitPoseDetection`. Android bridges to `com.google.mlkit:pose-detection`. + +**Use cases.** Fitness apps with form correction, dance / yoga timing analysis, gesture-driven controls. + +```java +PoseDetector.detect(jpeg).onResult((landmarks, err) -> { + if (err != null || landmarks.length < 99) return; + float noseX = landmarks[0], noseY = landmarks[1], noseConf = landmarks[2]; + Log.p("nose at (" + noseX + ", " + noseY + ") conf=" + noseConf); +}); +``` + +#### `cn1-ai-mlkit-segmentation`: selfie segmentation + +**TL;DR.** Returns a per-pixel mask separating the person in the foreground from the background as `byte[width * height]` (`0` = background, `255` = foreground). + +**Platforms.** iOS bridges to `MLKitSegmentationSelfie`. Android bridges to `com.google.mlkit:segmentation-selfie`. + +**Use cases.** Background replacement for video calls, sticker / portrait-mode effects, blur-the-background privacy filters. + +```java +SelfieSegmenter.segment(jpeg).onResult((mask, err) -> { + if (err == null) applyBackgroundReplacement(mask); +}); +``` + +#### `cn1-ai-mlkit-docscan`: document scanner + +**TL;DR.** Detects a rectangular document in a photo, perspective-corrects it, and writes the cropped JPEG to a temporary file. Returns the file path. + +**Platforms.** iOS uses Apple's VisionKit + Core Image rectangle detection (no extra pod). Android uses `com.google.android.gms:play-services-mlkit-document-scanner`. + +**Use cases.** "Scan to PDF" flows, expense apps that capture receipts, contract signing flows, ID-document capture. + +```java +DocumentScanner.scanToFile(jpeg).onResult((path, err) -> { + if (err == null) uploadDocument(path); +}); +``` + +#### `cn1-ai-tflite`: TensorFlow Lite interpreter + +**TL;DR.** A general-purpose on-device inference engine. Bring your own `.tflite` model and run it against a float32 input tensor. + +**Platforms.** iOS uses `TensorFlowLiteSwift` (Pods or Swift Package). Android uses `org.tensorflow:tensorflow-lite` + `tensorflow-lite-support`. + +**Use cases.** Any custom on-device ML model your team trains or pulls from TF Hub. Image classification, simple regression, recommendation pre-filters. + +```java +byte[] modelBytes = Util.readFully(Display.getInstance().getResourceAsStream(null, "/model.tflite")); +float[] input = featureVector(); +Interpreter.run(modelBytes, input).onResult((output, err) -> { + if (err == null) Log.p("model returned " + output.length + " values"); +}); +``` + +#### `cn1-ai-whisper`: speech-to-text via whisper.cpp + +**TL;DR.** On-device transcription of a 16 kHz mono WAV file using a `ggml`-format Whisper model. The cn1lib bundles `libwhisper.a`. + +**Platforms.** iOS uses the Accelerate framework; Android uses a JNI build of the same `whisper.cpp` core. Models (e.g. `ggml-base.bin`) are not bundled; ship the one your app expects under the app's resources or download on first launch. + +**Use cases.** Voice notes, accessibility transcription, offline dictation, podcast indexing. + +```java +String modelPath = SecureStorage.getFilePath("ggml-base.bin"); +String audioPath = recordWavToFile(); +WhisperRecognizer.transcribe(modelPath, audioPath) + .onResult((text, err) -> { + if (err == null) Log.p("heard: " + text); + }); +``` + +#### `cn1-ai-stablediffusion`: on-device image generation + +**TL;DR.** Generates a JPEG from a text prompt using a bundled Stable Diffusion model. Multi-gigabyte payload, **local build only**. + +**Platforms.** iOS uses Core ML pipelines compiled from the bundled model. Android uses ONNX Runtime. Both configurations exceed the cloud build server's 2 GB upload limit, so this cn1lib triggers the `cn1.ai.requiresBigUpload` guard and the cloud build aborts with a "build this one locally" message. Add it to a project you build via `mvn cn1:buildAndroid` / `mvn cn1:buildIosXcodeProject` on the developer machine. + +**Use cases.** Avatar generation in apps where shipping to a cloud API is undesirable (offline-first apps, regulated industries, privacy-sensitive products). + +```java +StableDiffusion.generate("a teal hot-air balloon over Lisbon, watercolour", + 512, 512, /* steps */ 25) + .onResult((jpeg, err) -> { + if (err == null) display(Image.createImage(jpeg, 0, jpeg.length)); + }); +``` -The split is intentional. The core gets the AI plumbing that almost every app that uses AI at all wants: the LLM client, the streaming, the chat UI, the secure storage primitive for credentials, the simulator Ollama redirect for offline iteration. Those are general-purpose; an app that adopts AI at all picks those up. +### Why these are cn1libs and not part of the core -The cn1libs are specialised verticals. Barcode scanning, document scanning, face detection, smart reply, pose detection, on-device translation, on-device speech transcription, on-device image generation; each is genuinely useful but only for some apps. They also each bring a non-trivial native dependency (Google ML Kit Android frameworks are large; the iOS pods add their own weight; the bundled Whisper static library and the on-device Stable Diffusion model are big), and the cost of carrying every one of them in core would land on every app whether it used the feature or not. +The core gets the AI plumbing every app that adopts AI at all wants: the LLM client, streaming, the chat UI, the secure storage primitive for credentials, the simulator Ollama redirect for offline iteration. -A small subset (the on-device Stable Diffusion model, in particular) is also flagged with a `cn1.ai.requiresBigUpload` marker in `AiDependencyTable`. The cloud build server aborts pre-upload with a friendly "build this one locally" message because the multi-gigabyte payload does not fit inside the usual build-server timeouts. That kind of opt-in does not belong in a dependency that every app inherits. +The cn1libs above are specialised verticals. Barcode scanning, document scanning, face detection, smart reply, pose detection, on-device translation, transcription, on-device image generation, each is genuinely useful, but only for some apps. They also each bring a non-trivial native dependency. The Google ML Kit Android frameworks are large; the iOS pods carry their own weight; the bundled `libwhisper.a` and the Stable Diffusion model are big. Pulling all of them into core would tax every app whether the feature is used or not. -The bootstrapping script `scripts/create-ai-cn1lib.sh` generates a new AI cn1lib repo from the archetype with a Maven Central publish workflow, so if you have a model that fits a niche the framework does not yet ship, the path to a published cn1lib is one command. +The Stable Diffusion cn1lib in particular is large enough that the cloud build server cannot accept the upload at all (it trips the 2 GB pre-upload guard). That kind of opt-in does not belong in a dependency every app inherits. The corresponding chapter, including the full `LlmClient` API table, the `ChatView` reference, the `SecureStorage` overloads, the simulator Ollama redirect, and the full cn1lib coverage, is at [AI, Chat UI, and Speech](https://www.codenameone.com/developer-guide/#_ai_chat_ui_and_speech) in the developer guide. diff --git a/docs/website/static/blog/platform-apis-in-the-core/chatview.png b/docs/website/static/blog/platform-apis-in-the-core/chatview.png new file mode 100644 index 0000000000000000000000000000000000000000..8afd38f3d2a8f2756ac8e2d295b44f846a859a36 GIT binary patch literal 85713 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 Date: Sat, 30 May 2026 08:57:00 +0300 Subject: [PATCH 05/13] Platform-APIs post: shift to 2026-05-31, retime forward links Tighter daily cadence: the platform-APIs post moves from Monday 2026-06-01 to Sunday 2026-05-31, and the codegen post (PR #5088) moves from Wednesday 2026-06-03 to Monday 2026-06-01. - Front-matter date: 2026-06-01 -> 2026-05-31. - Wrap-up: "The next post is on Wednesday" -> "Tomorrow's post" with the link to /blog/build-time-codegen/. - In-text reference: "Wednesday's post" (in the embeddings paragraph that mentions the upcoming ORM) -> "tomorrow's post". - Developer-workflow post forward link (added on top of the merged base by this PR): "Monday's post" -> "Tomorrow's post" to match the new platform-APIs date. --- .../content/blog/developer-workflow-debug-and-junit.md | 2 +- docs/website/content/blog/platform-apis-in-the-core.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/website/content/blog/developer-workflow-debug-and-junit.md b/docs/website/content/blog/developer-workflow-debug-and-junit.md index bbcecde1e4..d55449e2d2 100644 --- a/docs/website/content/blog/developer-workflow-debug-and-junit.md +++ b/docs/website/content/blog/developer-workflow-debug-and-junit.md @@ -279,7 +279,7 @@ The full reference, including the dependency-block YAML for `common/pom.xml` and ## Wrapping up -That is the workflow half of this release. [Monday's post](/blog/platform-apis-in-the-core/) covers the new platform APIs that moved into the core this week: AI and OAuth / OIDC are the headline pieces, with WiFi / connectivity and a few smaller items alongside them. +That is the workflow half of this release. [Tomorrow's post](/blog/platform-apis-in-the-core/) covers the new platform APIs that moved into the core this week: AI and OAuth / OIDC are the headline pieces, with WiFi / connectivity and a few smaller items alongside them. Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). diff --git a/docs/website/content/blog/platform-apis-in-the-core.md b/docs/website/content/blog/platform-apis-in-the-core.md index 520186601a..35a18d406a 100644 --- a/docs/website/content/blog/platform-apis-in-the-core.md +++ b/docs/website/content/blog/platform-apis-in-the-core.md @@ -2,7 +2,7 @@ title: AI, OAuth, And Other Platform APIs In The Core slug: platform-apis-in-the-core url: /blog/platform-apis-in-the-core/ -date: '2026-06-01' +date: '2026-05-31' author: Shai Almog description: Deeper AI integration in the framework core, modern authentication via OAuth / OIDC and WebAuthn passkeys driven from the system browser, and a few smaller additions (WiFi / connectivity, share-sheet result callbacks) alongside. feed_html: 'AI, OAuth, And Other Platform APIs In The Core Deeper AI integration in the framework core, modern authentication via OAuth / OIDC and WebAuthn passkeys driven from the system browser, and a few smaller additions alongside.' @@ -105,7 +105,7 @@ The shape mirrors the OpenAI function-calling contract one for one, so anything ### Embeddings -`LlmClient.embed(...)` returns a vector for any input string. Useful for similarity search against a local SQLite store ([Wednesday's post](/blog/build-time-codegen/) will cover the new ORM that pairs with this): +`LlmClient.embed(...)` returns a vector for any input string. Useful for similarity search against a local SQLite store ([tomorrow's post](/blog/build-time-codegen/) will cover the new ORM that pairs with this): ```java EmbeddingRequest er = new EmbeddingRequest.Builder() @@ -693,7 +693,7 @@ After the next cloud or local build, your app appears in the iOS share sheet for ## Wrapping up -The next post is on Wednesday and covers the architectural change in this release: a build-time bytecode annotation framework, the declarative router that is its first consumer, the SQLite ORM and JSON / XML mappers and component binder built on the same SPI, and the build-time SVG / Lottie transcoder that ships in the same release for related reasons. +[Tomorrow's post](/blog/build-time-codegen/) covers the architectural change in this release: a build-time bytecode annotation framework, the declarative router that is its first consumer, the SQLite ORM and JSON / XML mappers and component binder built on the same SPI, and the build-time SVG / Lottie transcoder that ships in the same release for related reasons. Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). From 5b6efd9e612dfa5ebb0a4aeeb14257e84c6bbba5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 11:17:32 +0300 Subject: [PATCH 06/13] Blog: build-time codegen (router, ORM, mappers, binder, SVG/Lottie) Final consolidated follow-up to the May 29 weekly index. Pulls together six PRs that share the same architectural shape: emit Java at build time, validate at build time, fail fast, and let R8 / ParparVM rename the generated code together with the rest of the app. - PR #5037: bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer (@Route with guards, redirects, per-tab navigation shell, location listeners), the unified cold + warm DeepLink API, iOS Universal Links / Android App Links JSON generators, and the JavaScript-port window.history bridge. - PR #5047: three more processors on the same SPI -- SQLite ORM (@Entity / @Id / @Column), JSON / XML mapping (@Mapped / @JsonProperty / @XmlElement), and component binding (@Bindable / @Bind with the new BindAttr enum). - PR #5062: validation annotations (@Required, @Length, @Regex, @Email, @Url, @Numeric, @ExistIn, @Validate) that compose with @Bind and surface through Binding.getValidator(). - PR #5055: the Immich-port baseline -- Map default methods, BiFunction, atomics, Rest.fetchAsJsonList / fetchAsMapped(List), URLImage.RequestDecorator / setDefaultBearerToken, JSONWriter, modern animated tab indicator + arc-spinner pull-to-refresh, MorphTransition.snapshotMode, WebSocket in core, and the new cn1:generate-openapi-client mojo. - PR #5042: build-time SVG transcoder that lowers SVG (and SMIL animations) into Codename One Image subclasses via the shape API. - PR #5066: Lottie / Bodymovin transcoder reusing the same SVGDocument model, JavaCodeGenerator, and SVGRegistry. - PR #5049: the iOS Metal stencil-clip + drawString and Android LinearGradientPaint fixes the SVG screenshot tests exposed. Calls out the Metal-only caveat on iOS for SVG / Lottie (the GL ES 2 path does not have the shape coverage) -- a non-issue on most apps now that Metal is the default. --- .../content/blog/build-time-codegen.md | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 docs/website/content/blog/build-time-codegen.md diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md new file mode 100644 index 0000000000..a76299e5f6 --- /dev/null +++ b/docs/website/content/blog/build-time-codegen.md @@ -0,0 +1,211 @@ +--- +title: Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie +slug: build-time-codegen +url: /blog/build-time-codegen/ +date: '2026-06-03' +author: Shai Almog +description: A reusable bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer, a SQLite ORM, JSON / XML mappers, a component binder with validation, the Immich-port baseline that drove the ORM design, and a build-time SVG / Lottie transcoder that emits Codename One Image subclasses for every asset. +feed_html: 'Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie A reusable bytecode AnnotationProcessor SPI, the declarative router that is its first consumer, ORM + mappers + binder + validation, the Immich-port baseline, and a build-time SVG / Lottie transcoder.' +--- + +![Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie](/blog/build-time-codegen.jpg) + +This is the architectural post for the week. The Saturday post was about how you iterate; Monday's post was about new platform APIs; today's post is about a shape of code that several PRs in this release share, that explains why a lot of the new APIs work the way they do, and that I think will shape how Codename One projects look over the next few years. + +The shape is **build-time codegen**. A reusable bytecode `AnnotationProcessor` SPI in the Maven plugin, the declarative router that is its first concrete consumer, then a SQLite ORM, JSON / XML mappers, and a component binder (all built on the same SPI), plus the build-time SVG / Lottie transcoders that ship in the same release for related reasons. The grab-bag PR from the Immich Flutter port lives here too because the ORM and mapping work share the porting exercise that drove it. + +Six PRs make up this post: [#5037](https://github.com/codenameone/CodenameOne/pull/5037) (router + annotation SPI), [#5047](https://github.com/codenameone/CodenameOne/pull/5047) (ORM + mappers + binder), [#5062](https://github.com/codenameone/CodenameOne/pull/5062) (validation), [#5055](https://github.com/codenameone/CodenameOne/pull/5055) (Immich-port baseline), [#5042](https://github.com/codenameone/CodenameOne/pull/5042) and [#5066](https://github.com/codenameone/CodenameOne/pull/5066) (SVG / Lottie), plus [#5049](https://github.com/codenameone/CodenameOne/pull/5049) (Metal / Android rendering fixes that fell out of the SVG screenshot tests). + +## Bytecode codegen, not source-text codegen + +The Maven plugin now has an `AnnotationProcessor` SPI and two new Mojos: `cn1:generate-annotation-stubs` (in `generate-sources`) and `cn1:process-annotations` (in `process-classes`). The orchestrator ASM-scans `target/classes`, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny `Index` class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into `META-INF/services` with no orchestrator changes. + +The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (`extends Form` is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated `.class` lands on disk. The infrastructure shares the ASM passes that the `BytecodeComplianceMojo`'s existing String rewrites already use. + +A small stub source is emitted under `target/generated-sources/cn1-annotations/` during `generate-sources` so application code that references the generated registry resolves at compile time. The real `.class` overwrites the stub later in `process-classes`. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split. + +Three non-negotiable rules across every processor in this batch: **no `Class.forName`, no service loader, no field reflection**. Every read and write in the generated code is a direct symbol reference that ParparVM rename and R8 obfuscation rewrite together with the class they target. Anything that worked in the simulator and broke in production because R8 renamed a class or a field; that whole shape of bug is structurally absent. + +cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging. + +## The router + +The first concrete consumer is the declarative router in `com.codename1.router`. The API is opt-in; the existing `Form.show()` / `Form.showBack()` flow keeps working unchanged. + +```java +@Route("/") +public class HomeForm extends Form { /* ... */ } + +@Route("/users/:id") +public class UserDetailForm extends Form { + public UserDetailForm(RouteMatch match) { + String id = match.param("id"); + // build UI for user `id` + } +} +``` + +`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The build-time processor validates that the annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, that there are no duplicate patterns. Anything off the rails fails the build with a class name and a reason, not at runtime with a stack trace. + +The rest of the router surface is what has become table stakes in modern client routing: route guards (run before navigation completes; can cancel or redirect), redirects, per-tab navigation stacks (`TabsForm`, where each tab keeps its own back stack), location listeners (anything in the app can subscribe to "the route changed"), and a `Form.setPopGuard(PopGuard)` analogue to Flutter's `PopScope` (intercept hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?"). `Sheet.showForResult()` returns an `AsyncResource` that auto-cancels with `null` if the user dismisses the sheet. + +### Deep links + +`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query, fragment). The same handler is invoked for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those. + +For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON, and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, `intent-filter`, the `.well-known/` upload) is at [Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Routing-And-Deep-Links.asciidoc), with an end-to-end tutorial at [Tutorial-Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Tutorial-Routing-And-Deep-Links.asciidoc). + +The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser history. Back and forward buttons in the browser drive the router; reloading the page lands at the deep-link URL. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console all benefit. + +## ORM, mappers, and binder (with validation) + +[PR #5047](https://github.com/codenameone/CodenameOne/pull/5047) lands three more processors on the same SPI. [PR #5062](https://github.com/codenameone/CodenameOne/pull/5062) layers validation on the binder. + +### SQLite ORM + +`@Entity`, `@Id`, `@Column`, `@DbTransient` for the schema; `EntityManager` and `Dao` for the runtime: + +```java +@Entity +public class TodoItem { + @Id @Column long id; + @Column String title; + @Column(name = "completed_at") Date completedAt; + @DbTransient Object cachedView; +} + +Dao dao = EntityManager.open("todos.db").dao(TodoItem.class); +dao.createTable(); +dao.insert(new TodoItem(0, "Read the post", null)); +List open = dao.find("completed_at IS NULL", new Object[] {}); +``` + +The generated DAO does the typed work underneath. No reflection in `insert`; the generated code calls `setString(1, e.title)` and `setLong(2, e.id)` directly. Validation at build time catches missing `@Id`, fields that look like relationships but are not yet supported, abstract entity classes. + +### JSON / XML mapping + +`@Mapped` is the entry point; `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format: + +```java +@Mapped +public class User { + @JsonProperty("user_id") long id; + @JsonProperty String name; + @JsonProperty("created_at") + Date createdAt; + @JsonIgnore String passwordHash; +} + +String json = Mappers.toJson(user); +User back = Mappers.fromJson(json, User.class); +``` + +The same `@Mapped` POJO is the type the `Rest` helpers from PR #5055 will accept (`Rest.get(url).fetchAsMapped(User.class)`, `fetchAsMappedList(User.class)`). + +### Component binding with validation + +`@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on the form by its name. The build-time binder reads the field types and the components, generates the bidirectional wiring at compile time: + +```java +@Bindable +public class SignupModel { + @Bind(name = "userField") @Required @Length(min = 3) private String user; + @Bind(name = "emailField") @Required @Email private String email; + @Bind(name = "ageField") @Numeric(min = 13, max = 120) private String age; + @Bind(name = "roleField") @ExistIn({ "admin", "editor", + "viewer" }) private String role; +} + +Binding b = Binders.bind(model, form); +b.getValidator().addSubmitButtons(submitBtn); +``` + +`Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via the existing `Validator.addConstraint(Component, Constraint...)` varargs (`GroupConstraint`, first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`. + +The new `BindAttr` enum lets `@Bind` target a specific attribute of the component (`TEXT`, `UIID`, `SELECTED`, ...) when the default is not what you want. The annotation framework reads it at build time and generates the matching `Component#setUiid(...)` / `Component#setSelected(...)` call. + +Three new dev-guide chapters: [Annotation-JSON-XML-Mapping.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc), [Annotation-Component-Binding.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-Component-Binding.asciidoc), and [Annotation-SQLite-ORM.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-SQLite-ORM.asciidoc). + +## The Immich-port baseline + +[PR #5055](https://github.com/codenameone/CodenameOne/pull/5055) ships as "Improvements to baseline based on porting exercise" and the porting exercise was real: Immich, the Flutter mobile client, into Codename One. The port is still compiling cleanly through every change in that PR; the additions worth pulling out: + +**Java subset additions.** Eleven Java 8 default methods on `Map` (`getOrDefault`, `putIfAbsent`, `remove(K, V)`, `replace(K, V)` / `replace(K, V, V)`, `forEach`, `replaceAll`, `computeIfAbsent`, `computeIfPresent`, `compute`, `merge`); `BiFunction`; `Iterable.forEach(Consumer)`, `Collection.removeIf(Predicate)`, `List.replaceAll(UnaryOperator)`, `List.sort(Comparator)`. All four primitive atomics: `AtomicReference`, `AtomicInteger`, `AtomicLong`, `AtomicBoolean`. Standard Java 8 surface that was simply missing. + +**`Rest` typed helpers.** `Rest.fetchAsJsonList`, `Rest.fetchAsMapped(Class)`, `Rest.fetchAsMappedList(Class)` (these are the surface the mapper post above mentioned). `Rest.fetchAsJsonList` closes a long-standing rough edge: top-level JSON arrays no longer need the historical `{"root":[...]}` envelope trick. + +**`URLImage.RequestDecorator`** plus `setDefaultRequestDecorator`, `setDefaultBearerToken`, and a per-call decorator overload on `createToStorage`. Authenticated image endpoints no longer require working around the "URLImage does not pass headers" gap; set a bearer token once and the image cache and the `URLImage` machinery use it everywhere. + +**`JSONWriter`** is the complement of `JSONParser`. `JSONWriter.toJson(Object)` for one-shot, fluent `JSONWriter.object().put(...).toJson()` and `JSONWriter.array()` builders, streaming variants for `Writer` and `OutputStream`. + +**`Tabs.setAnimatedIndicator(boolean)`** enables a Material 3 / iOS 26 sliding-underline indicator (gated by `tabsAnimatedIndicatorBool` plus duration / thickness constants and a new `TabIndicator` UIID). Off in the framework defaults; **on by default in the modern native themes** that we landed two weeks ago. + +**`DefaultLookAndFeel.drawModernPullToRefresh`** is a Material 3 arc-spinner painted via `Graphics.drawArc`; sweep grows 0° to 330° as the user pulls, then spins while the task runs. Gated by `pullToRefreshModernBool`; on by default in the modern themes. + +**`MorphTransition.snapshotMode(boolean)`** is the fix for an edge case that has been around forever: a morph from a source inside a scrolling container leaked off-viewport because the source was repainted live. The opt-in path captures source and destination as clipped `Image` snapshots at `initTransition()` and tweens those. Default live-paint path unchanged. + +**`com.codename1.io.websocket`** moves into the core. Per-platform native impls remain in the cn1lib for now. + +**`cn1:generate-openapi-client`** mojo reads an OpenAPI 3.x JSON spec and emits one `@Mapped` POJO per `components.schemas` entry plus one `Api.java` per tag. The Petstore reference spec runs end-to-end. Dev-guide page: [appendix_goal_generate_openapi_client.adoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/appendix_goal_generate_openapi_client.adoc). + +A natural question on this PR is "why is all of that in one PR rather than ten". The honest answer is that the porting exercise was the regression fixture for every single one of those additions, and breaking it up would have meant maintaining a long-lived branch where each split-out item had to be re-verified against the port independently. + +## SVG and Lottie at build time + +The last two PRs in this batch sit on the same "emit Java from declarative input at build time" pattern. [PR #5042](https://github.com/codenameone/CodenameOne/pull/5042) is the SVG transcoder; [PR #5066](https://github.com/codenameone/CodenameOne/pull/5066) is the Lottie / Bodymovin transcoder that reuses the SVG pipeline; [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) is the small set of iOS Metal and Android rendering fixes the SVG screenshot tests exposed. They share one chapter at [SVG-Transcoder.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/SVG-Transcoder.asciidoc). + +**Important caveat before the details:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick up the transcoders automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build. + +The shape: + +``` +src/main/svg/ + home.svg + settings.svg + profile.svg +src/main/lottie/ + spinner.json + pulse.json +``` + +After the next build: + +```java +Image home = Resources.getGlobalResources().getImage("home"); +Image spinner = Resources.getGlobalResources().getImage("spinner"); +form.add(home).add(spinner); +``` + +The SVG transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX (no Batik, no Flamingo, no external deps) and emits a Codename One `Image` subclass rendering through the `Graphics` shape API. SVG coverage covers what real-world icon SVGs use: rect (rounded corners), circle, ellipse, line, polyline, polygon, the full `path` grammar (M / L / H / V / C / S / Q / T / A / Z plus relative-coordinate and smooth-curve reflection), groups with affine transforms, linear gradients, fill, stroke, opacity. SMIL animations are supported: ``, `` (translate / scale / rotate), ``. Time values interpolate against wall-clock time on every paint. + +The Lottie pipeline reuses everything: each Bodymovin file is parsed into the same `SVGDocument` model the SVG path uses, the same `JavaCodeGenerator` emits the same `GeneratedSVGImage` subclass, the same `SVGRegistry` registers it. No new `Image` base class, no per-port wiring. v1 covers shape layers (`rc` / `el` / `sh`) with solid fills and strokes, layer transforms (anchor / position / scale / rotation / opacity), animated rotation / position / scale collapsed to a 2-keyframe loop, solid-color layers as filled rects. + +Why Java at build time rather than parse SVG at runtime: parsing requires `javax.xml`, which is JVM-only by design; the generated code is allocation-light and deterministic (a path's commands become inlined `g.fillShape(new GeneralPath()...)` calls; an animation becomes a `currentTransform.translate(...)` against a wall-clock variable); and R8 / ParparVM rename and dead-code eliminate the generated code as freely as any other class, so SVGs you do not actually `getImage(...)` get dropped from the final binary. + +### The Metal / Android rendering fixes + +The SVG screenshot tests exercised the shape API harder than anything we had thrown at it before, and three rendering bugs surfaced; the fixes in [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) affect any code path that uses `setClip(GeneralPath)`, gradient paint, or text under a transform, not just the SVG pipeline: + +1. **iOS Metal `setClip(GeneralPath)` triangle.** Metal's stencil clip's triangle fan was treating every Bezier control point as a polygon vertex. Non-rect `ClipShape`s are now midpoint-flattened into a polyline before reaching native. +2. **iOS Metal `drawString` skips the affine scale.** Text under a viewBox scale was rasterised at `font.pointSize` and stretched on the GPU. `CN1MetalDrawString` now reads the effective scale from `currentTransform`, picks an atlas font at `pointSize * scale`, and divides glyph metrics back into caller-side coords. +3. **Android and iOS Metal `gradient_circle.svg` double-circle.** `LinearGradientPaint.paint` was baking `getTranslateX/Y()` into a translate that sat before the SVG scale, sending the cell offset through the scale twice. The "translate dance" is dropped. + +If your app uses `setClip(GeneralPath)` or paints text under a non-uniform transform anywhere, you pick these fixes up on next rebuild. + +## What ties this together + +The thread across all six PRs is the same pattern: **emit Java at build time, validate at build time, fail fast with a class name and a reason, R8 / ParparVM rename the generated code together with the rest of the app**. The router uses it to register `@Route` classes. The ORM uses it to generate typed DAOs. The mappers use it to generate typed JSON / XML readers and writers. The binder uses it to wire fields to components. The SVG and Lottie transcoders use it to turn declarative graphics into Java classes that render through the shape API. + +The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape I think Codename One projects are going to look more and more like over the next year. + +## Wrapping up + +That closes out the post series for this release cycle. The May 29 weekly index is [here](/blog/metal-default-new-build-cloud-and-a-new-format/); the next weekly index is the one I will publish on Friday in the same short format. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} From b50152c9813c4c1a8d1a37a95abf4bacfe8a761c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 11:42:14 +0300 Subject: [PATCH 07/13] Build-time-codegen post: style pass - "I" voice replaced with "we" / team-flavoured phrasing. - Front matter description and feed_html no longer name the Immich Flutter port; the porting exercise is described generically as "a substantial mobile client app". - Section heading renamed from "The Immich-port baseline" to "The porting-exercise baseline". - Body prose no longer names the Flutter source; the porting exercise is described in generic terms ("a substantial third-party mobile client onto Codename One"). - "Form.setPopGuard analogue to Flutter's PopScope" rephrased so the Flutter analogy is gone and the hook is described directly. - Wrap-up rephrased so the "next index lands on Friday" line reads as a fact rather than a personal commitment. --- .../content/blog/build-time-codegen.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index a76299e5f6..ee89444641 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -4,17 +4,17 @@ slug: build-time-codegen url: /blog/build-time-codegen/ date: '2026-06-03' author: Shai Almog -description: A reusable bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer, a SQLite ORM, JSON / XML mappers, a component binder with validation, the Immich-port baseline that drove the ORM design, and a build-time SVG / Lottie transcoder that emits Codename One Image subclasses for every asset. -feed_html: 'Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie A reusable bytecode AnnotationProcessor SPI, the declarative router that is its first consumer, ORM + mappers + binder + validation, the Immich-port baseline, and a build-time SVG / Lottie transcoder.' +description: A reusable bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer, a SQLite ORM, JSON / XML mappers, a component binder with validation, the baseline additions surfaced by porting a substantial mobile client app onto Codename One, and a build-time SVG / Lottie transcoder that emits Codename One Image subclasses for every asset. +feed_html: 'Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie A reusable bytecode AnnotationProcessor SPI, the declarative router that is its first consumer, ORM + mappers + binder + validation, the porting-exercise baseline additions, and a build-time SVG / Lottie transcoder.' --- ![Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie](/blog/build-time-codegen.jpg) -This is the architectural post for the week. The Saturday post was about how you iterate; Monday's post was about new platform APIs; today's post is about a shape of code that several PRs in this release share, that explains why a lot of the new APIs work the way they do, and that I think will shape how Codename One projects look over the next few years. +This is the architectural post for the week. The Saturday post was about how you iterate; Monday's post was about new platform APIs; today's post is about a shape of code that several PRs in this release share, that explains why a lot of the new APIs work the way they do, and that should shape how Codename One projects look over the next few years. -The shape is **build-time codegen**. A reusable bytecode `AnnotationProcessor` SPI in the Maven plugin, the declarative router that is its first concrete consumer, then a SQLite ORM, JSON / XML mappers, and a component binder (all built on the same SPI), plus the build-time SVG / Lottie transcoders that ship in the same release for related reasons. The grab-bag PR from the Immich Flutter port lives here too because the ORM and mapping work share the porting exercise that drove it. +The shape is **build-time codegen**. A reusable bytecode `AnnotationProcessor` SPI in the Maven plugin, the declarative router that is its first concrete consumer, then a SQLite ORM, JSON / XML mappers, and a component binder (all built on the same SPI), plus the build-time SVG / Lottie transcoders that ship in the same release for related reasons. The grab-bag PR from a recent porting exercise (a substantial mobile client app ported onto Codename One) lives here too because the ORM and mapping work share the porting exercise that drove it. -Six PRs make up this post: [#5037](https://github.com/codenameone/CodenameOne/pull/5037) (router + annotation SPI), [#5047](https://github.com/codenameone/CodenameOne/pull/5047) (ORM + mappers + binder), [#5062](https://github.com/codenameone/CodenameOne/pull/5062) (validation), [#5055](https://github.com/codenameone/CodenameOne/pull/5055) (Immich-port baseline), [#5042](https://github.com/codenameone/CodenameOne/pull/5042) and [#5066](https://github.com/codenameone/CodenameOne/pull/5066) (SVG / Lottie), plus [#5049](https://github.com/codenameone/CodenameOne/pull/5049) (Metal / Android rendering fixes that fell out of the SVG screenshot tests). +Six PRs make up this post: [#5037](https://github.com/codenameone/CodenameOne/pull/5037) (router + annotation SPI), [#5047](https://github.com/codenameone/CodenameOne/pull/5047) (ORM + mappers + binder), [#5062](https://github.com/codenameone/CodenameOne/pull/5062) (validation), [#5055](https://github.com/codenameone/CodenameOne/pull/5055) (porting-exercise baseline additions), [#5042](https://github.com/codenameone/CodenameOne/pull/5042) and [#5066](https://github.com/codenameone/CodenameOne/pull/5066) (SVG / Lottie), plus [#5049](https://github.com/codenameone/CodenameOne/pull/5049) (Metal / Android rendering fixes that fell out of the SVG screenshot tests). ## Bytecode codegen, not source-text codegen @@ -47,7 +47,7 @@ public class UserDetailForm extends Form { `Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The build-time processor validates that the annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, that there are no duplicate patterns. Anything off the rails fails the build with a class name and a reason, not at runtime with a stack trace. -The rest of the router surface is what has become table stakes in modern client routing: route guards (run before navigation completes; can cancel or redirect), redirects, per-tab navigation stacks (`TabsForm`, where each tab keeps its own back stack), location listeners (anything in the app can subscribe to "the route changed"), and a `Form.setPopGuard(PopGuard)` analogue to Flutter's `PopScope` (intercept hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?"). `Sheet.showForResult()` returns an `AsyncResource` that auto-cancels with `null` if the user dismisses the sheet. +The rest of the router surface is the kind of thing that has become table stakes in modern client routing: route guards (run before navigation completes; can cancel or redirect), redirects, per-tab navigation stacks (`TabsForm`, where each tab keeps its own back stack), location listeners (anything in the app can subscribe to "the route changed"), and a `Form.setPopGuard(PopGuard)` hook to intercept hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". `Sheet.showForResult()` returns an `AsyncResource` that auto-cancels with `null` if the user dismisses the sheet. ### Deep links @@ -126,9 +126,9 @@ The new `BindAttr` enum lets `@Bind` target a specific attribute of the componen Three new dev-guide chapters: [Annotation-JSON-XML-Mapping.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc), [Annotation-Component-Binding.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-Component-Binding.asciidoc), and [Annotation-SQLite-ORM.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-SQLite-ORM.asciidoc). -## The Immich-port baseline +## The porting-exercise baseline -[PR #5055](https://github.com/codenameone/CodenameOne/pull/5055) ships as "Improvements to baseline based on porting exercise" and the porting exercise was real: Immich, the Flutter mobile client, into Codename One. The port is still compiling cleanly through every change in that PR; the additions worth pulling out: +[PR #5055](https://github.com/codenameone/CodenameOne/pull/5055) ships as "Improvements to baseline based on porting exercise". The porting exercise was real: a substantial third-party mobile client onto Codename One. The port is still compiling cleanly through every change in that PR; the additions worth pulling out: **Java subset additions.** Eleven Java 8 default methods on `Map` (`getOrDefault`, `putIfAbsent`, `remove(K, V)`, `replace(K, V)` / `replace(K, V, V)`, `forEach`, `replaceAll`, `computeIfAbsent`, `computeIfPresent`, `compute`, `merge`); `BiFunction`; `Iterable.forEach(Consumer)`, `Collection.removeIf(Predicate)`, `List.replaceAll(UnaryOperator)`, `List.sort(Comparator)`. All four primitive atomics: `AtomicReference`, `AtomicInteger`, `AtomicLong`, `AtomicBoolean`. Standard Java 8 surface that was simply missing. @@ -196,11 +196,11 @@ If your app uses `setClip(GeneralPath)` or paints text under a non-uniform trans The thread across all six PRs is the same pattern: **emit Java at build time, validate at build time, fail fast with a class name and a reason, R8 / ParparVM rename the generated code together with the rest of the app**. The router uses it to register `@Route` classes. The ORM uses it to generate typed DAOs. The mappers use it to generate typed JSON / XML readers and writers. The binder uses it to wire fields to components. The SVG and Lottie transcoders use it to turn declarative graphics into Java classes that render through the shape API. -The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape I think Codename One projects are going to look more and more like over the next year. +The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year. ## Wrapping up -That closes out the post series for this release cycle. The May 29 weekly index is [here](/blog/metal-default-new-build-cloud-and-a-new-format/); the next weekly index is the one I will publish on Friday in the same short format. +That closes out the post series for this release cycle. The May 29 weekly index is [here](/blog/metal-default-new-build-cloud-and-a-new-format/); the next weekly index lands on Friday in the same short format. --- From 5fba280babb170e500ea78402de0ba79816ca96c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 14:34:51 +0300 Subject: [PATCH 08/13] Build-time-codegen post: hero image + cross-post linking - Hero image lands at /blog/build-time-codegen.jpg. - Wrap-up reworded so the "back to the weekly index" link uses the same shape as the workflow and platform-APIs posts. - Platform-APIs post (rebased on top) updated so its wrap-up forward-links to this post by URL rather than by date prose. --- docs/website/content/blog/build-time-codegen.md | 4 +++- docs/website/static/blog/build-time-codegen.jpg | Bin 0 -> 27693 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/website/static/blog/build-time-codegen.jpg diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index ee89444641..6ebc30e9a9 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -200,7 +200,9 @@ The practical effect is that the kind of code that historically required reflect ## Wrapping up -That closes out the post series for this release cycle. The May 29 weekly index is [here](/blog/metal-default-new-build-cloud-and-a-new-format/); the next weekly index lands on Friday in the same short format. +That closes out the post series for this release cycle. The next weekly index lands on Friday in the same short format. + +Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). --- diff --git a/docs/website/static/blog/build-time-codegen.jpg b/docs/website/static/blog/build-time-codegen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e1be3b3b813dae06e735d01f881d6b72247d0c4 GIT binary patch literal 27693 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 F{{hGnCrAJQ literal 0 HcmV?d00001 From 9028daa27b092744fe487d54482664a6e633af86 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 18:54:00 +0300 Subject: [PATCH 09/13] Build-time-codegen post: quote YAML title to dodge colon parse error The post failed CI with: error building site: assemble: failed to create page from pageMetaSource /blog/build-time-codegen: ...build-time-codegen.md:2:8": [1:8] mapping value is not allowed in this context The title contains an unquoted colon ("Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie"), which YAML reads as a key / value separator at column 8 and trips the parser. Wrapping the value in double quotes makes it a plain string. Verified locally with `hugo --buildFuture`: the post renders and the rendered shows the colon intact. --- docs/website/content/blog/build-time-codegen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index 6ebc30e9a9..3185a73469 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -1,5 +1,5 @@ --- -title: Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie +title: "Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie" slug: build-time-codegen url: /blog/build-time-codegen/ date: '2026-06-03' From dd6e13a16be30f1fb6781aec664f9cdf63d0d623 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 21:24:36 +0300 Subject: [PATCH 10/13] Build-time-codegen post: major restructure per review - Retitled "Routing, ORM, OpenAPI, And Build-Time SVG / Lottie". The headline pieces (router, ORM, OpenAPI, SVG, Lottie) lead; the codegen plumbing is the "How it works" section at the end. - Front-matter description rewritten around the headline pieces and the JPA / JAXB familiarity callouts. - Routing section now leads with the deep-link motivation: what problem deep links create (URLs arriving from many sources), why a single Display.setDeepLinkHandler scales badly, and how @Route(...) collapses the if/else handler into one annotation per form. Spring developers get an explicit "@Route ~ @RequestMapping, :id ~ {id}, RouteMatch.param ~ @PathVariable" callout. React/Vue/Angular Router familiarity also called out. - ORM section gets a JPA / Hibernate familiarity callout (@Entity, @Id, @Column, EntityManager, Dao#findById/findAll/find), plus the explicit "renamed @Transient to @DbTransient to avoid java.beans.Transient" note. - JSON / XML section gets a JAXB familiarity callout (@XmlRoot, @XmlElement, @XmlAttribute, @XmlTransient direct port; the Jackson convention for the JSON side). - New OpenAPI section as a headline feature. Walks through the cn1:generate-openapi-client Mojo configuration in pom.xml, the Petstore reference spec output (6 models + 3 Api classes), and a concrete PetApi usage example (getPetById, findPetsByStatus, addPet). Frames the practical effect for teams whose backends already publish OpenAPI specs. - Component-binding section preserved with its validation annotations, BindAttr, GroupConstraint composition. - SVG section gets its own heading with a real static-fixture screenshot (cropped from scripts/ios/screenshots-metal/ SVGStatic.png). - Lottie section gets its own heading with an animated GIF composed from the 6-frame LottieAnimatedScreenshotTest grid (cropped, labels masked, frames stitched at 200ms each, looped). - "How it works" section at the end explains the bytecode AnnotationProcessor SPI, the generate-sources / process-classes Mojo split, the stub-then-overwrite pattern, and the three non-negotiable rules (no Class.forName, no service loader, no field reflection). - REMOVED: "The porting-exercise baseline" section and any other mention of how decisions were arrived at. - REMOVED: "The Metal / Android rendering fixes" subsection. The three Metal / Android bug fixes are not user-facing features of this release and do not belong in this post. - REMOVED: "What ties this together" section. - Front-matter title now quoted to handle the colon (carried over from the previous CI fix). Hero image and the lottie-pulse-spinner.gif / svg-static.png fixtures committed alongside. --- .../content/blog/build-time-codegen.md | 287 ++++++++++++------ .../lottie-pulse-spinner.gif | Bin 0 -> 15739 bytes .../blog/build-time-codegen/svg-static.png | Bin 0 -> 51992 bytes 3 files changed, 199 insertions(+), 88 deletions(-) create mode 100644 docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif create mode 100644 docs/website/static/blog/build-time-codegen/svg-static.png diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index 3185a73469..6d5a6d5db7 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -1,36 +1,38 @@ --- -title: "Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie" +title: "Routing, ORM, OpenAPI, And Build-Time SVG / Lottie" slug: build-time-codegen url: /blog/build-time-codegen/ date: '2026-06-03' author: Shai Almog -description: A reusable bytecode AnnotationProcessor SPI in the Maven plugin, the declarative router that is its first consumer, a SQLite ORM, JSON / XML mappers, a component binder with validation, the baseline additions surfaced by porting a substantial mobile client app onto Codename One, and a build-time SVG / Lottie transcoder that emits Codename One Image subclasses for every asset. -feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" alt="Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie" /> A reusable bytecode AnnotationProcessor SPI, the declarative router that is its first consumer, ORM + mappers + binder + validation, the porting-exercise baseline additions, and a build-time SVG / Lottie transcoder.' +description: A declarative router and a unified deep-link API; a JPA-shaped SQLite ORM; JAXB-shaped JSON / XML mappers; an OpenAPI 3.x client generator that turns a spec into typed Codename One code; SVGs and Lottie animations transcoded to Java Image subclasses at build time. All four pieces sit on the same build-time codegen pipeline; the details of that pipeline are at the end. +feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" alt="Routing, ORM, OpenAPI, And Build-Time SVG / Lottie" /> A declarative router with deep links, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, an OpenAPI client generator, and build-time SVG / Lottie transcoders.' --- -![Build-Time Codegen: Router, ORM, Mappers, Binder, SVG / Lottie](/blog/build-time-codegen.jpg) +![Routing, ORM, OpenAPI, And Build-Time SVG / Lottie](/blog/build-time-codegen.jpg) -This is the architectural post for the week. The Saturday post was about how you iterate; Monday's post was about new platform APIs; today's post is about a shape of code that several PRs in this release share, that explains why a lot of the new APIs work the way they do, and that should shape how Codename One projects look over the next few years. +This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; Monday's was about new platform APIs in the core; today's is about four pieces that change how you write the structural parts of an app. -The shape is **build-time codegen**. A reusable bytecode `AnnotationProcessor` SPI in the Maven plugin, the declarative router that is its first concrete consumer, then a SQLite ORM, JSON / XML mappers, and a component binder (all built on the same SPI), plus the build-time SVG / Lottie transcoders that ship in the same release for related reasons. The grab-bag PR from a recent porting exercise (a substantial mobile client app ported onto Codename One) lives here too because the ORM and mapping work share the porting exercise that drove it. +The four are routing, persistence, network bindings, and graphics. All four use **build-time codegen** under the hood: a Maven-plugin pass that reads annotations or declarative source files at build time and emits typed Java that compiles into your binary. No reflection, no service loader, no `Class.forName`. The "How it works" section at the end of this post is the place to read about the codegen plumbing once you have seen what it powers. The earlier sections focus on the features themselves. -Six PRs make up this post: [#5037](https://github.com/codenameone/CodenameOne/pull/5037) (router + annotation SPI), [#5047](https://github.com/codenameone/CodenameOne/pull/5047) (ORM + mappers + binder), [#5062](https://github.com/codenameone/CodenameOne/pull/5062) (validation), [#5055](https://github.com/codenameone/CodenameOne/pull/5055) (porting-exercise baseline additions), [#5042](https://github.com/codenameone/CodenameOne/pull/5042) and [#5066](https://github.com/codenameone/CodenameOne/pull/5066) (SVG / Lottie), plus [#5049](https://github.com/codenameone/CodenameOne/pull/5049) (Metal / Android rendering fixes that fell out of the SVG screenshot tests). +## Deep links and routing -## Bytecode codegen, not source-text codegen +The piece that motivates everything else in this section is deep links. Modern mobile apps need to handle URLs from a wide variety of sources: a notification that wants to land on a specific screen, a marketing email with a link into the app, a "share" sheet that hands the user a URL to a particular item, an associated-domains rule that opens the app when a friend taps `https://yourapp.com/users/42` in Safari. iOS treats these through Universal Links; Android treats them through App Links; the framework collapses both into a single in-app concept. -The Maven plugin now has an `AnnotationProcessor` SPI and two new Mojos: `cn1:generate-annotation-stubs` (in `generate-sources`) and `cn1:process-annotations` (in `process-classes`). The orchestrator ASM-scans `target/classes`, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny `Index` class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into `META-INF/services` with no orchestrator changes. +`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query map, fragment). The same handler fires for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those. -The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (`extends Form` is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated `.class` lands on disk. The infrastructure shares the ASM passes that the `BytecodeComplianceMojo`'s existing String rewrites already use. - -A small stub source is emitted under `target/generated-sources/cn1-annotations/` during `generate-sources` so application code that references the generated registry resolves at compile time. The real `.class` overwrites the stub later in `process-classes`. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split. - -Three non-negotiable rules across every processor in this batch: **no `Class.forName`, no service loader, no field reflection**. Every read and write in the generated code is a direct symbol reference that ParparVM rename and R8 obfuscation rewrite together with the class they target. Anything that worked in the simulator and broke in production because R8 renamed a class or a field; that whole shape of bug is structurally absent. - -cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging. +```java +Display.getInstance().setDeepLinkHandler(link -> { + if ("/users".equals(link.path()) && link.segments().size() == 2) { + showUserDetailForm(link.segments().get(1)); + return true; + } + return false; +}); +``` -## The router +That works, but as the surface grows the if/else chain in the handler becomes a real maintenance burden. Five URL patterns, ten patterns, twenty patterns; the handler grows arms and legs, the routing decisions creep into the form constructors that need to read query parameters, and the "what screens does this app have, and at what paths" question is answered by reading through hundreds of lines of switch statements scattered across files. -The first concrete consumer is the declarative router in `com.codename1.router`. The API is opt-in; the existing `Form.show()` / `Form.showBack()` flow keeps working unchanged. +The declarative router in `com.codename1.router` is what we built to keep that question tractable. Each form declares its own path with a `@Route` annotation: ```java @Route("/") @@ -39,52 +41,67 @@ public class HomeForm extends Form { /* ... */ } @Route("/users/:id") public class UserDetailForm extends Form { public UserDetailForm(RouteMatch match) { - String id = match.param("id"); - // build UI for user `id` + String userId = match.param("id"); + // build UI for user `userId` } } + +@Route("/about") +public class AboutForm extends Form { /* ... */ } ``` -`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The build-time processor validates that the annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, that there are no duplicate patterns. Anything off the rails fails the build with a class name and a reason, not at runtime with a stack trace. +`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The deep-link handler then collapses to a single line: `Display.getInstance().setDeepLinkHandler(link -> Router.navigate(link.path()))`. Each form owns its own routing rule; adding or moving a screen is a one-class change. -The rest of the router surface is the kind of thing that has become table stakes in modern client routing: route guards (run before navigation completes; can cancel or redirect), redirects, per-tab navigation stacks (`TabsForm`, where each tab keeps its own back stack), location listeners (anything in the app can subscribe to "the route changed"), and a `Form.setPopGuard(PopGuard)` hook to intercept hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". `Sheet.showForResult()` returns an `AsyncResource<T>` that auto-cancels with `null` if the user dismisses the sheet. +**For Spring developers,** the shape is familiar by design. `@Route` plays the same role as Spring MVC's `@RequestMapping`: a class-level declaration that announces "this controller handles URLs of this shape". The `:id` parameter syntax mirrors Spring's `{id}` path-variable syntax; `RouteMatch.param("id")` is the same kind of accessor as Spring's `@PathVariable`. The mental model carries over from server-side Java with almost no friction. The same recognition is available to anyone with React Router, Vue Router, or Angular Router experience; the `:param` convention is the cross-framework default. -### Deep links +The build-time processor validates that each annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, and that there are no duplicate patterns. Any rule violation fails the build with a class name and a reason, not at runtime with a stack trace. -`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query, fragment). The same handler is invoked for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those. +The rest of the router surface covers the kind of thing that has become table stakes in modern client routing: -For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON, and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, `intent-filter`, the `.well-known/` upload) is at [Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Routing-And-Deep-Links.asciidoc), with an end-to-end tutorial at [Tutorial-Routing-And-Deep-Links.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Tutorial-Routing-And-Deep-Links.asciidoc). +- **Route guards** run before navigation completes and can cancel or redirect. +- **Per-tab navigation stacks** via `TabsForm`, where each tab keeps its own back stack. +- **Location listeners** so anything in the app can subscribe to "the route changed". +- **`Form.setPopGuard(PopGuard)`** intercepts hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". +- **`Sheet.showForResult()`** returns an `AsyncResource<T>` that auto-cancels with `null` if the user dismisses the sheet. -The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser history. Back and forward buttons in the browser drive the router; reloading the page lands at the deep-link URL. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console all benefit. +The API is opt-in. Apps that prefer the existing `Form.show()` / `Form.showBack()` flow keep using that; nothing changes. -## ORM, mappers, and binder (with validation) +For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, the Android `intent-filter`, the `.well-known/` upload on your origin server) is at [Routing and Deep Links](https://www.codenameone.com/developer-guide/#_routing_and_deep_links) in the developer guide. -[PR #5047](https://github.com/codenameone/CodenameOne/pull/5047) lands three more processors on the same SPI. [PR #5062](https://github.com/codenameone/CodenameOne/pull/5062) layers validation on the binder. +The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser's session history. Back and forward in the browser drive the router; reloading the page lands at the deep-link URL; sharing the URL out of the address bar takes a colleague to the same in-app location. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console are all working examples. -### SQLite ORM +## SQLite ORM -`@Entity`, `@Id`, `@Column`, `@DbTransient` for the schema; `EntityManager` and `Dao` for the runtime: +The second piece is a SQLite ORM, also driven by annotations. `@Entity` marks the class; `@Id` and `@Column` shape the schema; `@DbTransient` opts a field out: ```java @Entity public class TodoItem { - @Id @Column long id; - @Column String title; - @Column(name = "completed_at") Date completedAt; - @DbTransient Object cachedView; + @Id @Column long id; + @Column String title; + @Column(name = "completed_at") + Date completedAt; + @DbTransient Object cachedView; } Dao<TodoItem> dao = EntityManager.open("todos.db").dao(TodoItem.class); dao.createTable(); dao.insert(new TodoItem(0, "Read the post", null)); + List<TodoItem> open = dao.find("completed_at IS NULL", new Object[] {}); +TodoItem byId = dao.findById(42); +dao.delete(byId); ``` -The generated DAO does the typed work underneath. No reflection in `insert`; the generated code calls `setString(1, e.title)` and `setLong(2, e.id)` directly. Validation at build time catches missing `@Id`, fields that look like relationships but are not yet supported, abstract entity classes. +The generated DAO does the typed work underneath. No reflection in `insert`; the generated code calls `setString(1, e.title)` and `setLong(2, e.id)` directly against the SQLite `PreparedStatement`. Validation at build time catches missing `@Id`, fields that look like relationships but are not yet supported, and abstract entity classes; the build fails with a class name and a reason. + +**For JPA / Hibernate developers,** the API is intentionally familiar. `@Entity`, `@Id`, `@Column`, and `@Transient` (here renamed `@DbTransient` to avoid colliding with `java.beans.Transient`) carry the same meaning they do under `javax.persistence` / `jakarta.persistence`. The `EntityManager` name is the same. `Dao#findById`, `Dao#findAll`, `Dao#find(where, params)`, `Dao#insert`, `Dao#update`, `Dao#delete` line up with the basic JPA repository contract. The query language is plain SQL (there is no JPQL or Criteria DSL) but the annotation surface, the lifecycle, and the runtime methods will feel like a long-lost friend to anyone with server-side Java persistence experience. -### JSON / XML mapping +Three rules the design enforces: no `Class.forName`, no service loader, no field reflection. The "this code only breaks in production because R8 renamed a field" shape of JPA-on-Android bug is structurally absent. -`@Mapped` is the entry point; `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format: +## JSON / XML mapping + +`@Mapped` marks a class as a transferable POJO. `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format. The runtime entry points are `Mappers.toJson(...)`, `Mappers.fromJson(...)`, `Mappers.toXml(...)`, `Mappers.fromXml(...)`: ```java @Mapped @@ -100,107 +117,201 @@ String json = Mappers.toJson(user); User back = Mappers.fromJson(json, User.class); ``` -The same `@Mapped` POJO is the type the `Rest` helpers from PR #5055 will accept (`Rest.get(url).fetchAsMapped(User.class)`, `fetchAsMappedList(User.class)`). +The same `@Mapped` POJO is the type the typed `Rest` helpers accept: + +```java +Rest.get("https://api.example.com/users/42") + .fetchAsMapped(User.class) + .onResult((user, err) -> { /* ... */ }); + +Rest.get("https://api.example.com/users") + .fetchAsMappedList(User.class) + .onResult((users, err) -> { /* ... */ }); +``` + +`Rest.fetchAsJsonList` (top-level JSON arrays, no `{"root":[...]}` envelope trick), `JSONWriter` (the complement of `JSONParser`, with fluent builders and streaming variants for `Writer` and `OutputStream`), and `URLImage.setDefaultBearerToken` (auth headers on image fetches) all ship alongside. + +**For JAXB developers,** the XML surface (`@XmlRoot`, `@XmlElement`, `@XmlAttribute`, `@XmlTransient`) is a direct port of the long-established `javax.xml.bind.annotation` surface. The same model class can be both `@XmlRoot`-decorated and `@JsonProperty`-decorated, which gives you a single source of truth for both wire formats. The JSON surface adopts the Jackson convention (`@JsonProperty`, `@JsonIgnore`) that nearly every modern JVM JSON binding (Jackson, Moshi, kotlinx-serialization) inherited. + +## OpenAPI client generation + +This is the headline of the codegen post and arguably the most useful single feature in this release for any team that talks to a backend. + +A new `cn1:generate-openapi-client` Mojo reads an OpenAPI 3.x JSON spec (a URL or a local file) and writes typed Codename One client code that compiles into your app: + +- One `@Mapped` POJO per `components.schemas` entry. +- One `<Tag>Api.java` class per OpenAPI tag, with one fluent method per operation. +- Every method routes through `Rest.<verb>` + `Mappers.toJson` + `fetchAsMapped` / `fetchAsMappedList`, so the generated surface integrates with the rest of the framework instead of dragging in a separate HTTP stack. + +Wire it into the project's `pom.xml`: -### Component binding with validation +```xml +<plugin> + <groupId>com.codenameone</groupId> + <artifactId>codenameone-maven-plugin</artifactId> + <executions> + <execution> + <id>petstore-client</id> + <goals><goal>generate-openapi-client</goal></goals> + <configuration> + <specUrl>https://petstore3.swagger.io/api/v3/openapi.json</specUrl> + <basePackage>com.example.petstore</basePackage> + </configuration> + </execution> + </executions> +</plugin> +``` + +`mvn generate-sources` picks the spec up, downloads it, and writes one file per schema and one per tag under `target/generated-sources/`. The Petstore reference spec exercised end-to-end produces six model classes (`Pet`, `Order`, `Customer`, `Tag`, `Category`, `User`) and three Api classes (`PetApi`, `StoreApi`, `UserApi`), and the nine generated `.class` files compile cleanly against `codenameone-core`. Documented at [the OpenAPI codegen Maven goal](https://www.codenameone.com/developer-guide/#_appendix_goal_generate_openapi_client). -`@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on the form by its name. The build-time binder reads the field types and the components, generates the bidirectional wiring at compile time: +In application code you call the generated `Api` class the same way you would call any other Java method: + +```java +PetApi pets = new PetApi(); + +// Returns AsyncResource<Pet>; resolves with the deserialised object. +pets.getPetById(42).onResult((pet, err) -> { + if (err == null) Log.p("Got " + pet.getName()); +}); + +// Returns AsyncResource<List<Pet>>. +pets.findPetsByStatus("available").onResult((list, err) -> { + if (err == null) { + for (Pet p : list) Log.p(p.getName()); + } +}); + +// POST with a request body. addPet takes a Pet, returns a Pet. +Pet candidate = new Pet(); +candidate.setName("Mittens"); +candidate.setStatus("available"); +pets.addPet(candidate).onResult((created, err) -> { /* ... */ }); +``` + +There is no hand-rolled `ConnectionRequest` setup, no manual JSON parsing, no string-typed request bodies. The generated client takes a typed `Pet`, serialises it with `Mappers.toJson(...)`, fires the right HTTP verb, deserialises the response with `Mappers.fromJson(...)`, and surfaces the result through the framework's `AsyncResource` so your callback fires on the EDT. + +For teams who already publish an OpenAPI spec as part of their backend (most modern backend frameworks do this automatically; FastAPI, Spring's `springdoc-openapi`, NestJS, ASP.NET Core, Go's `gnostic`), the practical effect is that the mobile client's bindings stay in sync with the backend without anyone hand-writing a single network call. Update the spec, re-run `mvn generate-sources`, and the new and changed endpoints land in your app as typed Java the IDE picks up immediately. + +It is the kind of change that is most useful when you do not know you have it: pull a fresh spec, rebuild, and your IDE highlights every place in the codebase that called a renamed endpoint or passed the wrong type to a parameter. + +## Component binding with validation + +The fourth annotation processor on the same pipeline is the component binder. `@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on a form by the component's `name`. Field-level validation annotations compose with `@Bind` on the same field: ```java @Bindable public class SignupModel { - @Bind(name = "userField") @Required @Length(min = 3) private String user; - @Bind(name = "emailField") @Required @Email private String email; - @Bind(name = "ageField") @Numeric(min = 13, max = 120) private String age; - @Bind(name = "roleField") @ExistIn({ "admin", "editor", - "viewer" }) private String role; + @Bind(name = "userField") @Required @Length(min = 3) + private String user; + + @Bind(name = "emailField") @Required @Email + private String email; + + @Bind(name = "ageField") @Numeric(min = 13, max = 120) + private String age; + + @Bind(name = "roleField") @ExistIn({ "admin", "editor", "viewer" }) + private String role; } +``` +The runtime side is two lines: + +```java Binding b = Binders.bind(model, form); b.getValidator().addSubmitButtons(submitBtn); ``` -`Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via the existing `Validator.addConstraint(Component, Constraint...)` varargs (`GroupConstraint`, first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`. +`Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via `Validator.addConstraint(Component, Constraint...)` and `GroupConstraint` (first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`. -The new `BindAttr` enum lets `@Bind` target a specific attribute of the component (`TEXT`, `UIID`, `SELECTED`, ...) when the default is not what you want. The annotation framework reads it at build time and generates the matching `Component#setUiid(...)` / `Component#setSelected(...)` call. +The new `BindAttr` enum lets `@Bind` target a specific attribute of the component (`TEXT`, `UIID`, `SELECTED`, ...) when the default ("write a `String` field into the component's text") is not what you want. -Three new dev-guide chapters: [Annotation-JSON-XML-Mapping.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc), [Annotation-Component-Binding.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-Component-Binding.asciidoc), and [Annotation-SQLite-ORM.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/Annotation-SQLite-ORM.asciidoc). +## SVG at build time -## The porting-exercise baseline +SVG and Lottie use the same codegen pipeline, but emit different output: instead of reading annotations from your Java, they read declarative graphics files from `src/main/svg/` and `src/main/lottie/` and emit Codename One `Image` subclasses that render through the `Graphics` shape API on every platform. -[PR #5055](https://github.com/codenameone/CodenameOne/pull/5055) ships as "Improvements to baseline based on porting exercise". The porting exercise was real: a substantial third-party mobile client onto Codename One. The port is still compiling cleanly through every change in that PR; the additions worth pulling out: +Drop an SVG into `src/main/svg/`: -**Java subset additions.** Eleven Java 8 default methods on `Map` (`getOrDefault`, `putIfAbsent`, `remove(K, V)`, `replace(K, V)` / `replace(K, V, V)`, `forEach`, `replaceAll`, `computeIfAbsent`, `computeIfPresent`, `compute`, `merge`); `BiFunction`; `Iterable.forEach(Consumer)`, `Collection.removeIf(Predicate)`, `List.replaceAll(UnaryOperator)`, `List.sort(Comparator)`. All four primitive atomics: `AtomicReference`, `AtomicInteger`, `AtomicLong`, `AtomicBoolean`. Standard Java 8 surface that was simply missing. - -**`Rest` typed helpers.** `Rest.fetchAsJsonList`, `Rest.fetchAsMapped(Class<T>)`, `Rest.fetchAsMappedList(Class<T>)` (these are the surface the mapper post above mentioned). `Rest.fetchAsJsonList` closes a long-standing rough edge: top-level JSON arrays no longer need the historical `{"root":[...]}` envelope trick. - -**`URLImage.RequestDecorator`** plus `setDefaultRequestDecorator`, `setDefaultBearerToken`, and a per-call decorator overload on `createToStorage`. Authenticated image endpoints no longer require working around the "URLImage does not pass headers" gap; set a bearer token once and the image cache and the `URLImage` machinery use it everywhere. - -**`JSONWriter`** is the complement of `JSONParser`. `JSONWriter.toJson(Object)` for one-shot, fluent `JSONWriter.object().put(...).toJson()` and `JSONWriter.array()` builders, streaming variants for `Writer` and `OutputStream`. +``` +src/main/svg/ + star.svg + gradient_circle.svg + path_arrow.svg + rounded_button.svg + wave.svg + pro_badge.svg + clipped_badge.svg +``` -**`Tabs.setAnimatedIndicator(boolean)`** enables a Material 3 / iOS 26 sliding-underline indicator (gated by `tabsAnimatedIndicatorBool` plus duration / thickness constants and a new `TabIndicator` UIID). Off in the framework defaults; **on by default in the modern native themes** that we landed two weeks ago. +After the next build: -**`DefaultLookAndFeel.drawModernPullToRefresh`** is a Material 3 arc-spinner painted via `Graphics.drawArc`; sweep grows 0° to 330° as the user pulls, then spins while the task runs. Gated by `pullToRefreshModernBool`; on by default in the modern themes. +```java +Image star = Resources.getGlobalResources().getImage("star.svg"); +Image star2 = Resources.getGlobalResources().getImage("star"); // either form +form.add(star); +``` -**`MorphTransition.snapshotMode(boolean)`** is the fix for an edge case that has been around forever: a morph from a source inside a scrolling container leaked off-viewport because the source was repainted live. The opt-in path captures source and destination as clipped `Image` snapshots at `initTransition()` and tweens those. Default live-paint path unchanged. +A grid of the static SVGs from the hellocodenameone fixture, rendered through the new pipeline: -**`com.codename1.io.websocket`** moves into the core. Per-platform native impls remain in the cn1lib for now. +![Static SVGs rendered by the build-time transcoder on iOS Metal: filled star, gradient-filled circle, path arrow, rounded button, two stroked wave paths, gradient-filled PRO badge, clipped badge](/blog/build-time-codegen/svg-static.png) -**`cn1:generate-openapi-client`** mojo reads an OpenAPI 3.x JSON spec and emits one `@Mapped` POJO per `components.schemas` entry plus one `<Tag>Api.java` per tag. The Petstore reference spec runs end-to-end. Dev-guide page: [appendix_goal_generate_openapi_client.adoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/appendix_goal_generate_openapi_client.adoc). +The transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX. No Batik, no Flamingo, no external dependencies. Coverage targets what real-world icon SVGs use: `rect` (rounded corners included), `circle`, `ellipse`, `line`, `polyline`, `polygon`, the full `path` grammar (`M` / `L` / `H` / `V` / `C` / `S` / `Q` / `T` / `A` / `Z` plus relative-coordinate and smooth-curve reflection), groups with affine transforms (`translate`, `scale`, `rotate`, `skew`, `matrix`), linear gradients via `LinearGradientPaint`, fill, stroke, stroke-width, linecap, linejoin, opacity. -A natural question on this PR is "why is all of that in one PR rather than ten". The honest answer is that the porting exercise was the regression fixture for every single one of those additions, and breaking it up would have meant maintaining a long-lived branch where each split-out item had to be re-verified against the port independently. +SMIL animations are supported in the same pipeline: `<animate>`, `<animateTransform>` (`translate`, `scale`, `rotate`), and `<set>`. Time values interpolate against wall-clock time on every paint, with `from` / `to` / `values` / `begin` / `dur` / `repeatCount` / `fill="freeze"` honoured. So an SVG with a rotating sub-element becomes a real animated `Image` you can drop into a `Form` and watch spin without writing any animation code yourself. -## SVG and Lottie at build time +**Important caveat:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick the transcoders up automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build. -The last two PRs in this batch sit on the same "emit Java from declarative input at build time" pattern. [PR #5042](https://github.com/codenameone/CodenameOne/pull/5042) is the SVG transcoder; [PR #5066](https://github.com/codenameone/CodenameOne/pull/5066) is the Lottie / Bodymovin transcoder that reuses the SVG pipeline; [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) is the small set of iOS Metal and Android rendering fixes the SVG screenshot tests exposed. They share one chapter at [SVG-Transcoder.asciidoc](https://github.com/codenameone/CodenameOne/blob/master/docs/developer-guide/SVG-Transcoder.asciidoc). +Coverage and the troubleshooting section are at [SVG Transcoder](https://www.codenameone.com/developer-guide/#_svg_transcoder) in the developer guide. Explicit non-coverage in v1: SVG `text`, masks / clip-paths, filters, radial-gradient paint (falls back to first stop colour), CSS keyframe animations. -**Important caveat before the details:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick up the transcoders automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build. +## Lottie at build time -The shape: +The same pipeline carries Lottie. Drop a Bodymovin export into `src/main/lottie/`: ``` -src/main/svg/ - home.svg - settings.svg - profile.svg src/main/lottie/ - spinner.json pulse.json + spinner.json ``` -After the next build: +After the next build, both are real `Image` instances on every platform that exposes the shape API: ```java -Image home = Resources.getGlobalResources().getImage("home"); +Image pulse = Resources.getGlobalResources().getImage("pulse"); Image spinner = Resources.getGlobalResources().getImage("spinner"); -form.add(home).add(spinner); +form.add(pulse).add(spinner); ``` -The SVG transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX (no Batik, no Flamingo, no external deps) and emits a Codename One `Image` subclass rendering through the `Graphics` shape API. SVG coverage covers what real-world icon SVGs use: rect (rounded corners), circle, ellipse, line, polyline, polygon, the full `path` grammar (M / L / H / V / C / S / Q / T / A / Z plus relative-coordinate and smooth-curve reflection), groups with affine transforms, linear gradients, fill, stroke, opacity. SMIL animations are supported: `<animate>`, `<animateTransform>` (translate / scale / rotate), `<set>`. Time values interpolate against wall-clock time on every paint. +The animation runs against wall-clock time on every paint, with no `Timer` and no allocation in the hot path. A capture of the hellocodenameone Lottie fixture (one pulsing circle and one rotating bar) at six points in the loop: + +![Animated Lottie playback: a red bar that pulses and rotates next to a blue ellipse that scales up and down](/blog/build-time-codegen/lottie-pulse-spinner.gif) -The Lottie pipeline reuses everything: each Bodymovin file is parsed into the same `SVGDocument` model the SVG path uses, the same `JavaCodeGenerator` emits the same `GeneratedSVGImage` subclass, the same `SVGRegistry` registers it. No new `Image` base class, no per-port wiring. v1 covers shape layers (`rc` / `el` / `sh`) with solid fills and strokes, layer transforms (anchor / position / scale / rotation / opacity), animated rotation / position / scale collapsed to a 2-keyframe loop, solid-color layers as filled rects. +The Lottie transcoder lives in `maven/lottie-transcoder/`. It parses Bodymovin JSON with no external dependencies (the framework's built-in JSON parser carries the load) and lowers each file into the same `SVGDocument` model the SVG path uses. The same `JavaCodeGenerator` emits the same `GeneratedSVGImage` subclass, and the same `SVGRegistry` registers it under the source filename. **No new `Image` base class, no new registry, no per-port wiring**, since the SVG path's JavaSE reflective load and iOS / Android Stub weaving already cover the new format. -Why Java at build time rather than parse SVG at runtime: parsing requires `javax.xml`, which is JVM-only by design; the generated code is allocation-light and deterministic (a path's commands become inlined `g.fillShape(new GeneralPath()...)` calls; an animation becomes a `currentTransform.translate(...)` against a wall-clock variable); and R8 / ParparVM rename and dead-code eliminate the generated code as freely as any other class, so SVGs you do not actually `getImage(...)` get dropped from the final binary. +Coverage in v1: shape layers (`rc` / `el` / `sh`) with solid fills and strokes; layer transforms (anchor, position, scale, rotation, opacity); animated rotation, position, and scale collapsed to a two-keyframe loop; solid-color layers as filled rects. Most icon-grade Bodymovin exports lower cleanly. Complex character animations from After Effects with image references, masks, and effects do not, and the transcoder logs which layers it dropped so the source of any blank output is obvious. -### The Metal / Android rendering fixes +## How it works: the build-time codegen pipeline -The SVG screenshot tests exercised the shape API harder than anything we had thrown at it before, and three rendering bugs surfaced; the fixes in [PR #5049](https://github.com/codenameone/CodenameOne/pull/5049) affect any code path that uses `setClip(GeneralPath)`, gradient paint, or text under a transform, not just the SVG pipeline: +Everything above sits on a single Maven-plugin pass. -1. **iOS Metal `setClip(GeneralPath)` triangle.** Metal's stencil clip's triangle fan was treating every Bezier control point as a polygon vertex. Non-rect `ClipShape`s are now midpoint-flattened into a polyline before reaching native. -2. **iOS Metal `drawString` skips the affine scale.** Text under a viewBox scale was rasterised at `font.pointSize` and stretched on the GPU. `CN1MetalDrawString` now reads the effective scale from `currentTransform`, picks an atlas font at `pointSize * scale`, and divides glyph metrics back into caller-side coords. -3. **Android and iOS Metal `gradient_circle.svg` double-circle.** `LinearGradientPaint.paint` was baking `getTranslateX/Y()` into a translate that sat before the SVG scale, sending the cell offset through the scale twice. The "translate dance" is dropped. +The plugin has an `AnnotationProcessor` SPI and two new Mojos: `cn1:generate-annotation-stubs` (in `generate-sources`) and `cn1:process-annotations` (in `process-classes`). The orchestrator ASM-scans `target/classes`, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny `Index` class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into `META-INF/services` with no orchestrator changes. + +The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (`extends Form` is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated `.class` lands on disk. The infrastructure shares the ASM passes that the `BytecodeComplianceMojo`'s existing String rewrites already use. -If your app uses `setClip(GeneralPath)` or paints text under a non-uniform transform anywhere, you pick these fixes up on next rebuild. +A small stub source is emitted under `target/generated-sources/cn1-annotations/` during `generate-sources` so application code that references the generated registry resolves at compile time. The real `.class` overwrites the stub later in `process-classes`. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split. + +cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging. -## What ties this together +Three non-negotiable rules across every processor: -The thread across all six PRs is the same pattern: **emit Java at build time, validate at build time, fail fast with a class name and a reason, R8 / ParparVM rename the generated code together with the rest of the app**. The router uses it to register `@Route` classes. The ORM uses it to generate typed DAOs. The mappers use it to generate typed JSON / XML readers and writers. The binder uses it to wire fields to components. The SVG and Lottie transcoders use it to turn declarative graphics into Java classes that render through the shape API. +- **No `Class.forName`** anywhere in generated code. +- **No service loader.** Everything is wired through the typed registry the codegen emits. +- **No field reflection.** Every read and write in the generated code is a direct symbol reference that ParparVM rename and R8 obfuscation rewrite together with the class they target. -The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year. +The SVG and Lottie transcoders sit on a parallel pipeline (declarative graphics files in place of annotations), but follow the same rules and emit code that obeys the same constraints. The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year. ## Wrapping up -That closes out the post series for this release cycle. The next weekly index lands on Friday in the same short format. +That closes the post series for this release. The next weekly index lands on Friday in the same short format. Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). diff --git a/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif b/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..5f2d85a26e0626f21cd08544fde20b78f25fd448 GIT binary patch literal 15739 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 literal 0 HcmV?d00001 diff --git a/docs/website/static/blog/build-time-codegen/svg-static.png b/docs/website/static/blog/build-time-codegen/svg-static.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f5d8ab77d3c8d60e29af87e214136e5d556fb8 GIT binary patch literal 51992 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 literal 0 HcmV?d00001 From 198b2ce20edae4a5c0a92ca406d506161468e0a9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 22:21:05 +0300 Subject: [PATCH 11/13] Build-time-codegen post: reorder + retitle per review - Title retitled "OpenAPI, ORM, SVG and Lottie" (routing dropped from the title, not the first thing above the fold). - OpenAPI section stays first, then ORM, JSON / XML, binding, SVG, Lottie. Deep links and routing moved to the END of the post, just before the codegen plumbing section. - Routing reframed as "the first place we used the preprocessor" rather than "the piece that motivates everything else". - Deep-link section now spells out the honest history. AppArg worked for small surfaces but got fragile across cold/warm lifecycle paths and especially in the case where a user lands mid-app via a link and then continues to interact (back-stack composition, falling off the edge on back). The new DeepLink + setDeepLinkHandler runs on a consistent path and parses for you. - Removed the "The Initializr, the Playground, the Skin Designer, and the new Build Cloud console are all working examples" sentence; none of those use the routing framework. - Removed the "Three rules the design enforces: no Class.forName, no service loader, no field reflection. The 'this code only breaks in production because R8 renamed a field' shape of JPA-on-Android bug is structurally absent" paragraph from the ORM section. - @Bindable example now shows the Form with the matching components (TextField with setName, ComboBox, Button), so the binder's "match by component name" contract is visible. - New SVG sizing subsection. cn1-svg-width / cn1-svg-height in millimeters is the recommended knob; same constant size at every DPI; sidesteps the design-pixel guesswork. Two-float constructor for projects that don't use CSS. - New paragraph up front in the SVG section spelling out that a transcoded SVG is a vector image but still an Image: works in every Image slot in the framework, just scales cleanly. - iOS Metal caveat fixed. On ios.metal=false the SVG / Lottie shape API renders with visible artifacts in many cases, not the placeholder you might expect. - SVG and Lottie both ask the community to file issues with the failing source file attached when the transcoder mishandles something. The transcoder grows one shape family at a time from those reports. - SVG / Lottie source path corrected from src/main/svg/ and src/main/lottie/ to src/main/css/ (the actual hellocodenameone fixture lays them out alongside theme.css there). - Lottie GIF regenerated. 24 interpolated frames at 60 ms each (was 6 frames at 200 ms); much smoother playback in-browser. - Wrap-up teases Friday's release post: pretty big features landing, the headline pieces are the most substantial things in months and worth checking back for. --- .../content/blog/build-time-codegen.md | 326 ++++++++++-------- .../lottie-pulse-spinner.gif | Bin 15739 -> 81494 bytes 2 files changed, 187 insertions(+), 139 deletions(-) diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index 6d5a6d5db7..415d16747a 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -1,78 +1,83 @@ --- -title: "Routing, ORM, OpenAPI, And Build-Time SVG / Lottie" +title: "OpenAPI, ORM, SVG and Lottie" slug: build-time-codegen url: /blog/build-time-codegen/ date: '2026-06-03' author: Shai Almog -description: A declarative router and a unified deep-link API; a JPA-shaped SQLite ORM; JAXB-shaped JSON / XML mappers; an OpenAPI 3.x client generator that turns a spec into typed Codename One code; SVGs and Lottie animations transcoded to Java Image subclasses at build time. All four pieces sit on the same build-time codegen pipeline; the details of that pipeline are at the end. -feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" alt="Routing, ORM, OpenAPI, And Build-Time SVG / Lottie" /> A declarative router with deep links, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, an OpenAPI client generator, and build-time SVG / Lottie transcoders.' +description: An OpenAPI 3.x client generator that turns a spec into typed Codename One code, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, build-time SVG and Lottie transcoders, plus a declarative router and deep-link API. All ride on the same build-time codegen pipeline. +feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" alt="OpenAPI, ORM, SVG and Lottie" /> An OpenAPI client generator, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, build-time SVG / Lottie transcoders, and a declarative router with deep links. All on the same build-time codegen pipeline.' --- -![Routing, ORM, OpenAPI, And Build-Time SVG / Lottie](/blog/build-time-codegen.jpg) +![OpenAPI, ORM, SVG and Lottie](/blog/build-time-codegen.jpg) -This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; Monday's was about new platform APIs in the core; today's is about four pieces that change how you write the structural parts of an app. +This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; Monday's was about new platform APIs in the core; today's is about a run of pieces that change how you write the structural parts of an app. -The four are routing, persistence, network bindings, and graphics. All four use **build-time codegen** under the hood: a Maven-plugin pass that reads annotations or declarative source files at build time and emits typed Java that compiles into your binary. No reflection, no service loader, no `Class.forName`. The "How it works" section at the end of this post is the place to read about the codegen plumbing once you have seen what it powers. The earlier sections focus on the features themselves. +The pieces are an OpenAPI client generator, a SQLite ORM, JSON and XML mappers, a component binder with validation, build-time SVG and Lottie transcoders, and a declarative router with deep links. All ride on a single **build-time codegen pipeline**: a Maven-plugin pass that reads annotations or declarative source files at build time and emits typed Java that compiles into your binary. No reflection, no service loader, no `Class.forName`. The "How it works" section at the end of this post covers the codegen plumbing once you have seen what it powers. -## Deep links and routing +## OpenAPI client generation -The piece that motivates everything else in this section is deep links. Modern mobile apps need to handle URLs from a wide variety of sources: a notification that wants to land on a specific screen, a marketing email with a link into the app, a "share" sheet that hands the user a URL to a particular item, an associated-domains rule that opens the app when a friend taps `https://yourapp.com/users/42` in Safari. iOS treats these through Universal Links; Android treats them through App Links; the framework collapses both into a single in-app concept. +The headline of this release for any team that talks to a backend. -`Display.setDeepLinkHandler(LinkHandler)` registers a handler that receives a normalised `DeepLink` (scheme, host, path, segments, query map, fragment). The same handler fires for cold launches (the app was not running; the OS started it because of a link) and warm launches (the app was already running and got the URL via app-resume). iOS and Android need no port changes for this to work; the existing platform plumbing already writes URL-shaped values into `Display.setProperty("AppArg", url)` and the new handler intercepts those. +A new `cn1:generate-openapi-client` Mojo reads an OpenAPI 3.x JSON spec (a URL or a local file) and writes typed Codename One client code that compiles into your app: -```java -Display.getInstance().setDeepLinkHandler(link -> { - if ("/users".equals(link.path()) && link.segments().size() == 2) { - showUserDetailForm(link.segments().get(1)); - return true; - } - return false; -}); +- One `@Mapped` POJO per `components.schemas` entry. +- One `<Tag>Api.java` class per OpenAPI tag, with one fluent method per operation. +- Every method routes through `Rest.<verb>` + `Mappers.toJson` + `fetchAsMapped` / `fetchAsMappedList`, so the generated surface integrates with the rest of the framework instead of dragging in a separate HTTP stack. + +Wire it into the project's `pom.xml`: + +```xml +<plugin> + <groupId>com.codenameone</groupId> + <artifactId>codenameone-maven-plugin</artifactId> + <executions> + <execution> + <id>petstore-client</id> + <goals><goal>generate-openapi-client</goal></goals> + <configuration> + <specUrl>https://petstore3.swagger.io/api/v3/openapi.json</specUrl> + <basePackage>com.example.petstore</basePackage> + </configuration> + </execution> + </executions> +</plugin> ``` -That works, but as the surface grows the if/else chain in the handler becomes a real maintenance burden. Five URL patterns, ten patterns, twenty patterns; the handler grows arms and legs, the routing decisions creep into the form constructors that need to read query parameters, and the "what screens does this app have, and at what paths" question is answered by reading through hundreds of lines of switch statements scattered across files. +`mvn generate-sources` picks the spec up, downloads it, and writes one file per schema and one per tag under `target/generated-sources/`. The Petstore reference spec exercised end-to-end produces six model classes (`Pet`, `Order`, `Customer`, `Tag`, `Category`, `User`) and three Api classes (`PetApi`, `StoreApi`, `UserApi`), and the nine generated `.class` files compile cleanly against `codenameone-core`. Documented at [the OpenAPI codegen Maven goal](https://www.codenameone.com/developer-guide/#_appendix_goal_generate_openapi_client). -The declarative router in `com.codename1.router` is what we built to keep that question tractable. Each form declares its own path with a `@Route` annotation: +In application code you call the generated `Api` class the same way you would call any other Java method: ```java -@Route("/") -public class HomeForm extends Form { /* ... */ } +PetApi pets = new PetApi(); -@Route("/users/:id") -public class UserDetailForm extends Form { - public UserDetailForm(RouteMatch match) { - String userId = match.param("id"); - // build UI for user `userId` +// Returns AsyncResource<Pet>; resolves with the deserialised object. +pets.getPetById(42).onResult((pet, err) -> { + if (err == null) Log.p("Got " + pet.getName()); +}); + +// Returns AsyncResource<List<Pet>>. +pets.findPetsByStatus("available").onResult((list, err) -> { + if (err == null) { + for (Pet p : list) Log.p(p.getName()); } -} +}); -@Route("/about") -public class AboutForm extends Form { /* ... */ } +// POST with a request body. addPet takes a Pet, returns a Pet. +Pet candidate = new Pet(); +candidate.setName("Mittens"); +candidate.setStatus("available"); +pets.addPet(candidate).onResult((created, err) -> { /* ... */ }); ``` -`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The deep-link handler then collapses to a single line: `Display.getInstance().setDeepLinkHandler(link -> Router.navigate(link.path()))`. Each form owns its own routing rule; adding or moving a screen is a one-class change. - -**For Spring developers,** the shape is familiar by design. `@Route` plays the same role as Spring MVC's `@RequestMapping`: a class-level declaration that announces "this controller handles URLs of this shape". The `:id` parameter syntax mirrors Spring's `{id}` path-variable syntax; `RouteMatch.param("id")` is the same kind of accessor as Spring's `@PathVariable`. The mental model carries over from server-side Java with almost no friction. The same recognition is available to anyone with React Router, Vue Router, or Angular Router experience; the `:param` convention is the cross-framework default. - -The build-time processor validates that each annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, and that there are no duplicate patterns. Any rule violation fails the build with a class name and a reason, not at runtime with a stack trace. - -The rest of the router surface covers the kind of thing that has become table stakes in modern client routing: - -- **Route guards** run before navigation completes and can cancel or redirect. -- **Per-tab navigation stacks** via `TabsForm`, where each tab keeps its own back stack. -- **Location listeners** so anything in the app can subscribe to "the route changed". -- **`Form.setPopGuard(PopGuard)`** intercepts hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". -- **`Sheet.showForResult()`** returns an `AsyncResource<T>` that auto-cancels with `null` if the user dismisses the sheet. - -The API is opt-in. Apps that prefer the existing `Form.show()` / `Form.showBack()` flow keep using that; nothing changes. +There is no hand-rolled `ConnectionRequest` setup, no manual JSON parsing, no string-typed request bodies. The generated client takes a typed `Pet`, serialises it with `Mappers.toJson(...)`, fires the right HTTP verb, deserialises the response with `Mappers.fromJson(...)`, and surfaces the result through the framework's `AsyncResource` so your callback fires on the EDT. -For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, the Android `intent-filter`, the `.well-known/` upload on your origin server) is at [Routing and Deep Links](https://www.codenameone.com/developer-guide/#_routing_and_deep_links) in the developer guide. +For teams who already publish an OpenAPI spec as part of their backend (most modern backend frameworks do this automatically; FastAPI, Spring's `springdoc-openapi`, NestJS, ASP.NET Core, Go's `gnostic`), the practical effect is that the mobile client's bindings stay in sync with the backend without anyone hand-writing a single network call. Update the spec, re-run `mvn generate-sources`, and the new and changed endpoints land in your app as typed Java the IDE picks up immediately. -The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser's session history. Back and forward in the browser drive the router; reloading the page lands at the deep-link URL; sharing the URL out of the address bar takes a colleague to the same in-app location. The Initializr, the Playground, the Skin Designer, and the new Build Cloud console are all working examples. +It is the kind of change that is most useful when you do not know you have it: pull a fresh spec, rebuild, and your IDE highlights every place in the codebase that called a renamed endpoint or passed the wrong type to a parameter. ## SQLite ORM -The second piece is a SQLite ORM, also driven by annotations. `@Entity` marks the class; `@Id` and `@Column` shape the schema; `@DbTransient` opts a field out: +`@Entity` marks the class; `@Id` and `@Column` shape the schema; `@DbTransient` opts a field out: ```java @Entity @@ -97,8 +102,6 @@ The generated DAO does the typed work underneath. No reflection in `insert`; the **For JPA / Hibernate developers,** the API is intentionally familiar. `@Entity`, `@Id`, `@Column`, and `@Transient` (here renamed `@DbTransient` to avoid colliding with `java.beans.Transient`) carry the same meaning they do under `javax.persistence` / `jakarta.persistence`. The `EntityManager` name is the same. `Dao#findById`, `Dao#findAll`, `Dao#find(where, params)`, `Dao#insert`, `Dao#update`, `Dao#delete` line up with the basic JPA repository contract. The query language is plain SQL (there is no JPQL or Criteria DSL) but the annotation surface, the lifecycle, and the runtime methods will feel like a long-lost friend to anyone with server-side Java persistence experience. -Three rules the design enforces: no `Class.forName`, no service loader, no field reflection. The "this code only breaks in production because R8 renamed a field" shape of JPA-on-Android bug is structurally absent. - ## JSON / XML mapping `@Mapped` marks a class as a transferable POJO. `@JsonProperty` and `@XmlElement` (plus `@XmlRoot`, `@XmlAttribute`, `@JsonIgnore`, `@XmlTransient`) shape the wire format. The runtime entry points are `Mappers.toJson(...)`, `Mappers.fromJson(...)`, `Mappers.toXml(...)`, `Mappers.fromXml(...)`: @@ -133,67 +136,6 @@ Rest.get("https://api.example.com/users") **For JAXB developers,** the XML surface (`@XmlRoot`, `@XmlElement`, `@XmlAttribute`, `@XmlTransient`) is a direct port of the long-established `javax.xml.bind.annotation` surface. The same model class can be both `@XmlRoot`-decorated and `@JsonProperty`-decorated, which gives you a single source of truth for both wire formats. The JSON surface adopts the Jackson convention (`@JsonProperty`, `@JsonIgnore`) that nearly every modern JVM JSON binding (Jackson, Moshi, kotlinx-serialization) inherited. -## OpenAPI client generation - -This is the headline of the codegen post and arguably the most useful single feature in this release for any team that talks to a backend. - -A new `cn1:generate-openapi-client` Mojo reads an OpenAPI 3.x JSON spec (a URL or a local file) and writes typed Codename One client code that compiles into your app: - -- One `@Mapped` POJO per `components.schemas` entry. -- One `<Tag>Api.java` class per OpenAPI tag, with one fluent method per operation. -- Every method routes through `Rest.<verb>` + `Mappers.toJson` + `fetchAsMapped` / `fetchAsMappedList`, so the generated surface integrates with the rest of the framework instead of dragging in a separate HTTP stack. - -Wire it into the project's `pom.xml`: - -```xml -<plugin> - <groupId>com.codenameone</groupId> - <artifactId>codenameone-maven-plugin</artifactId> - <executions> - <execution> - <id>petstore-client</id> - <goals><goal>generate-openapi-client</goal></goals> - <configuration> - <specUrl>https://petstore3.swagger.io/api/v3/openapi.json</specUrl> - <basePackage>com.example.petstore</basePackage> - </configuration> - </execution> - </executions> -</plugin> -``` - -`mvn generate-sources` picks the spec up, downloads it, and writes one file per schema and one per tag under `target/generated-sources/`. The Petstore reference spec exercised end-to-end produces six model classes (`Pet`, `Order`, `Customer`, `Tag`, `Category`, `User`) and three Api classes (`PetApi`, `StoreApi`, `UserApi`), and the nine generated `.class` files compile cleanly against `codenameone-core`. Documented at [the OpenAPI codegen Maven goal](https://www.codenameone.com/developer-guide/#_appendix_goal_generate_openapi_client). - -In application code you call the generated `Api` class the same way you would call any other Java method: - -```java -PetApi pets = new PetApi(); - -// Returns AsyncResource<Pet>; resolves with the deserialised object. -pets.getPetById(42).onResult((pet, err) -> { - if (err == null) Log.p("Got " + pet.getName()); -}); - -// Returns AsyncResource<List<Pet>>. -pets.findPetsByStatus("available").onResult((list, err) -> { - if (err == null) { - for (Pet p : list) Log.p(p.getName()); - } -}); - -// POST with a request body. addPet takes a Pet, returns a Pet. -Pet candidate = new Pet(); -candidate.setName("Mittens"); -candidate.setStatus("available"); -pets.addPet(candidate).onResult((created, err) -> { /* ... */ }); -``` - -There is no hand-rolled `ConnectionRequest` setup, no manual JSON parsing, no string-typed request bodies. The generated client takes a typed `Pet`, serialises it with `Mappers.toJson(...)`, fires the right HTTP verb, deserialises the response with `Mappers.fromJson(...)`, and surfaces the result through the framework's `AsyncResource` so your callback fires on the EDT. - -For teams who already publish an OpenAPI spec as part of their backend (most modern backend frameworks do this automatically; FastAPI, Spring's `springdoc-openapi`, NestJS, ASP.NET Core, Go's `gnostic`), the practical effect is that the mobile client's bindings stay in sync with the backend without anyone hand-writing a single network call. Update the spec, re-run `mvn generate-sources`, and the new and changed endpoints land in your app as typed Java the IDE picks up immediately. - -It is the kind of change that is most useful when you do not know you have it: pull a fresh spec, rebuild, and your IDE highlights every place in the codebase that called a renamed endpoint or passed the wrong type to a parameter. - ## Component binding with validation The fourth annotation processor on the same pipeline is the component binder. `@Bindable` marks a model class; `@Bind(name = "userField")` ties a field to a component on a form by the component's `name`. Field-level validation annotations compose with `@Bind` on the same field: @@ -215,11 +157,24 @@ public class SignupModel { } ``` -The runtime side is two lines: +The matching form sets a `name` on each component so the binder can find them: ```java -Binding b = Binders.bind(model, form); -b.getValidator().addSubmitButtons(submitBtn); +TextField user = new TextField(); user.setName("userField"); +TextField email = new TextField(); email.setName("emailField"); +TextField age = new TextField(); age.setName("ageField"); +ComboBox<String> role = new ComboBox<>("admin", "editor", "viewer"); +role.setName("roleField"); + +Button submit = new Button("Sign up"); + +Form form = new Form("Sign Up", BoxLayout.y()); +form.add(user).add(email).add(age).add(role).add(submit); +form.show(); + +SignupModel model = new SignupModel(); +Binding binding = Binders.bind(model, form); +binding.getValidator().addSubmitButtons(submit); ``` `Binding` is the handle: `refresh()` re-reads the model into the components, `commit()` writes the components back, `disconnect()` tears the listeners down. Multiple validation annotations on a single field compose via `Validator.addConstraint(Component, Constraint...)` and `GroupConstraint` (first failure wins). `@Validate(MyClass.class)` is the escape hatch for hand-written `Constraint` implementations. The validation set: `@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`. @@ -228,12 +183,11 @@ The new `BindAttr` enum lets `@Bind` target a specific attribute of the componen ## SVG at build time -SVG and Lottie use the same codegen pipeline, but emit different output: instead of reading annotations from your Java, they read declarative graphics files from `src/main/svg/` and `src/main/lottie/` and emit Codename One `Image` subclasses that render through the `Graphics` shape API on every platform. - -Drop an SVG into `src/main/svg/`: +Drop an SVG into `src/main/css/`, alongside `theme.css`: ``` -src/main/svg/ +src/main/css/ + theme.css star.svg gradient_circle.svg path_arrow.svg @@ -243,37 +197,61 @@ src/main/svg/ clipped_badge.svg ``` -After the next build: - -```java -Image star = Resources.getGlobalResources().getImage("star.svg"); -Image star2 = Resources.getGlobalResources().getImage("star"); // either form -form.add(star); -``` +After the next build, every SVG is a regular Codename One `Image`. **An SVG handled by the transcoder is a vector image, but it is still an `Image`.** Everywhere a raster `Image` works (`Label.setIcon`, `Button.setIcon`, `BorderLayout.NORTH`, the toolbar, a `MultiButton`'s leading icon, a CSS `background: url(...)` rule), the SVG works too. The difference is that it stays crisp at any size: the same source file is sharp at a 16-point list-row icon, a 64-point hero header, and a 256-point launch screen, on every DPI bucket. A grid of the static SVGs from the hellocodenameone fixture, rendered through the new pipeline: ![Static SVGs rendered by the build-time transcoder on iOS Metal: filled star, gradient-filled circle, path arrow, rounded button, two stroked wave paths, gradient-filled PRO badge, clipped badge](/blog/build-time-codegen/svg-static.png) +### Sizing in millimeters is the important knob + +The SVG transcoder's most useful feature is also the one most easily missed: **size every SVG in millimeters from CSS**. SVGs in the wild routinely declare odd `width` / `height` attributes (a 1024×1024 export of a 24×24 icon, no dimensions at all, design-pixel values from one specific framework). Pinning the rendered size in millimeters sidesteps all of that. + +```css +HomeIcon { + background: url(home.svg); + cn1-svg-width: 6mm; + cn1-svg-height: 6mm; + bg-type: image_scaled_fit; +} + +LogoBanner { + background: url(logo.svg); + cn1-svg-width: 32mm; + cn1-svg-height: 12mm; +} +``` + +A 6 mm icon is 6 mm tall on a 1× desktop, 6 mm on a high-DPI handset, and 6 mm on a 4K tablet. The transcoder routes both values through `Display.convertToPixels()` at install time, the same way `font-size: 3mm` already behaves elsewhere in Codename One CSS. No design-pixel guesswork, no DPI bucket to choose, no scaling surprise when the artist re-exports the source SVG at a different resolution. + +If a project does not use CSS for theming, the two-`float` constructor on the generated class takes millimeters directly: `new com.codename1.generated.svg.Home(6f, 6f)`. + +### Coverage and what we still want feedback on + The transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.xml` StAX. No Batik, no Flamingo, no external dependencies. Coverage targets what real-world icon SVGs use: `rect` (rounded corners included), `circle`, `ellipse`, `line`, `polyline`, `polygon`, the full `path` grammar (`M` / `L` / `H` / `V` / `C` / `S` / `Q` / `T` / `A` / `Z` plus relative-coordinate and smooth-curve reflection), groups with affine transforms (`translate`, `scale`, `rotate`, `skew`, `matrix`), linear gradients via `LinearGradientPaint`, fill, stroke, stroke-width, linecap, linejoin, opacity. -SMIL animations are supported in the same pipeline: `<animate>`, `<animateTransform>` (`translate`, `scale`, `rotate`), and `<set>`. Time values interpolate against wall-clock time on every paint, with `from` / `to` / `values` / `begin` / `dur` / `repeatCount` / `fill="freeze"` honoured. So an SVG with a rotating sub-element becomes a real animated `Image` you can drop into a `Form` and watch spin without writing any animation code yourself. +SMIL animations are supported in the same pipeline: `<animate>`, `<animateTransform>` (`translate`, `scale`, `rotate`), and `<set>`. Time values interpolate against wall-clock time on every paint, with `from` / `to` / `values` / `begin` / `dur` / `repeatCount` / `fill="freeze"` honoured. -**Important caveat:** this is **Metal-only on iOS**. The GL ES 2 path that was the iOS default until [last Friday's flip](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios) does not have the shape API coverage the SVG / Lottie pipeline emits. Apps that opted in to Metal pick the transcoders up automatically; apps still on `ios.metal=false` will see placeholders. Now that Metal is the default this stops being a thing most apps notice on their next build. +Explicit non-coverage in v1: SVG `text`, masks / clip-paths, filters, radial-gradient paint (falls back to first stop colour), CSS keyframe animations. -Coverage and the troubleshooting section are at [SVG Transcoder](https://www.codenameone.com/developer-guide/#_svg_transcoder) in the developer guide. Explicit non-coverage in v1: SVG `text`, masks / clip-paths, filters, radial-gradient paint (falls back to first stop colour), CSS keyframe animations. +**If you hit an SVG that does not transcode the way you expect**, please open an issue at [github.com/codenameone/CodenameOne/issues](https://github.com/codenameone/CodenameOne/issues) and **attach the source file**. The fastest way to extend the coverage is for us to run the failing case through the test fixtures and watch the output. Every SVG we ship test goldens for started as somebody else's "this doesn't render right" report. + +**Caveat on iOS:** the transcoded SVGs use the framework's shape API (`fillShape`, `drawShape`, `LinearGradientPaint`). The full surface is implemented on the Metal renderer. The deprecated GL ES 2 pipeline does not have parity on every operation, so an SVG drawn under `ios.metal=false` will often render with visible artifacts (missing gradients, clipped fills, distorted paths) rather than the placeholder you might expect. Now that Metal is the default for new iOS builds [as of last Friday](/blog/metal-default-new-build-cloud-and-a-new-format/#metal-is-the-default-on-ios), this is a non-issue on most apps; if you have explicitly pinned `ios.metal=false`, expect some visual regressions on SVG content and let us know which. + +The coverage matrix and troubleshooting are at [SVG Transcoder](https://www.codenameone.com/developer-guide/#_svg_transcoder) in the developer guide. ## Lottie at build time -The same pipeline carries Lottie. Drop a Bodymovin export into `src/main/lottie/`: +The same pipeline carries Lottie. Drop a Bodymovin export into the same `src/main/css/`: ``` -src/main/lottie/ +src/main/css/ + theme.css pulse.json spinner.json ``` -After the next build, both are real `Image` instances on every platform that exposes the shape API: +After the next build, both are real `Image` instances on every platform that exposes the shape API. The same vector-everywhere story as SVG: a Lottie animation renders crisply at any size and slots into any `Image` slot in the framework. ```java Image pulse = Resources.getGlobalResources().getImage("pulse"); @@ -281,7 +259,7 @@ Image spinner = Resources.getGlobalResources().getImage("spinner"); form.add(pulse).add(spinner); ``` -The animation runs against wall-clock time on every paint, with no `Timer` and no allocation in the hot path. A capture of the hellocodenameone Lottie fixture (one pulsing circle and one rotating bar) at six points in the loop: +Animation runs against wall-clock time on every paint, with no `Timer` and no allocation in the hot path. A capture of the hellocodenameone Lottie fixture in motion: ![Animated Lottie playback: a red bar that pulses and rotates next to a blue ellipse that scales up and down](/blog/build-time-codegen/lottie-pulse-spinner.gif) @@ -289,6 +267,82 @@ The Lottie transcoder lives in `maven/lottie-transcoder/`. It parses Bodymovin J Coverage in v1: shape layers (`rc` / `el` / `sh`) with solid fills and strokes; layer transforms (anchor, position, scale, rotation, opacity); animated rotation, position, and scale collapsed to a two-keyframe loop; solid-color layers as filled rects. Most icon-grade Bodymovin exports lower cleanly. Complex character animations from After Effects with image references, masks, and effects do not, and the transcoder logs which layers it dropped so the source of any blank output is obvious. +**Same ask as for SVG**: if a Lottie / Bodymovin file does not transcode the way you expect, please open an issue at [github.com/codenameone/CodenameOne/issues](https://github.com/codenameone/CodenameOne/issues) and **attach the source `.json`**. The transcoder grows one shape family at a time from the cases the community reports. + +The same iOS caveat applies: the renderer leans on the shape API, so the deprecated GL ES 2 pipeline shows artifacts on the more elaborate Lottie animations. Use the Metal default (now on by default for new iOS builds). + +## Deep links and routing + +Two pieces of plumbing for apps that handle URLs from outside themselves (notification taps, marketing links, share targets, Universal Links from Safari and the equivalent App Links from Chrome on Android). + +### Deep links + +Codename One has had deep-link support for a long time through `Display.setProperty("AppArg", url)`. The platform plumbing already writes the incoming URL into that property on cold launch, and an app-resume sets it again on warm launch; reading it back from `start()` works fine for a small number of patterns. Where the `AppArg`-only approach gets fragile is consistency. The cold and warm paths execute different lifecycle code, the value is a flat string with no parsing, and the trickiest case is the one where a user lands in the middle of the app via a link and then continues to interact: their next navigation needs to compose with the entry point, the back-stack needs to make sense as if they had arrived through the usual flow, and "fall off the edge of the app" on back is a common bug. With a hand-rolled `AppArg` reader it is easy to miss one of these and ship a half-working flow. + +This release introduces a typed `DeepLink` and a single handler that fires for both cold and warm launches: + +```java +Display.getInstance().setDeepLinkHandler(link -> { + // link is a normalised DeepLink: scheme, host, path, + // segments, query map, fragment. Same shape cold or warm. + if ("/users".equals(link.path()) && link.segments().size() == 2) { + showUserDetailForm(link.segments().get(1)); + return true; + } + return false; +}); +``` + +`AppArg` still works for projects that depend on it, but the new handler is what we recommend going forward. The handler runs on a consistent lifecycle path on both cold and warm starts, and the parsed `DeepLink` value carries the scheme, host, path segments, query map, and fragment so app code does not need to roll its own URL parser. + +### Routing + +For projects that handle more than a handful of URL patterns, the second piece is the declarative router in `com.codename1.router`. We built it on the same build-time codegen pipeline as the ORM and the mappers (the router was actually the first concrete consumer of the new preprocessor) so the two surfaces compose: a deep-link handler that delegates to the router becomes a one-liner. + +Each form declares its own path with a `@Route` annotation: + +```java +@Route("/") +public class HomeForm extends Form { /* ... */ } + +@Route("/users/:id") +public class UserDetailForm extends Form { + public UserDetailForm(RouteMatch match) { + String userId = match.param("id"); + // build UI for user `userId` + } +} + +@Route("/about") +public class AboutForm extends Form { /* ... */ } +``` + +`Router.navigate("/users/42")` resolves the path, instantiates `UserDetailForm`, and shows it. The deep-link handler now collapses to: + +```java +Display.getInstance().setDeepLinkHandler(link -> Router.navigate(link.toString())); +``` + +Each form owns its own routing rule. Adding or moving a screen is a one-class change. The "what screens does this app have, and at what paths?" question is answered by an IDE search for `@Route`, not by reading every form constructor in the project. + +**For Spring developers,** the shape is familiar by design. `@Route` plays the same role as Spring MVC's `@RequestMapping`: a class-level declaration that announces "this controller handles URLs of this shape". The `:id` parameter syntax mirrors Spring's `{id}` path-variable syntax; `RouteMatch.param("id")` is the same kind of accessor as Spring's `@PathVariable`. The mental model carries over from server-side Java with almost no friction. The same recognition is available to anyone with React Router, Vue Router, or Angular Router experience; the `:param` convention is the cross-framework default. + +The build-time processor validates that each annotated class extends `Form`, that the path starts with `/`, that the constructor is accessible, and that there are no duplicate patterns. Any rule violation fails the build with a class name and a reason, not at runtime with a stack trace. + +The rest of the router surface covers the kind of thing that has become table stakes in modern client routing: + +- **Route guards** run before navigation completes and can cancel or redirect. +- **Per-tab navigation stacks** via `TabsForm`, where each tab keeps its own back stack. +- **Location listeners** so anything in the app can subscribe to "the route changed". +- **`Form.setPopGuard(PopGuard)`** intercepts hardware back, toolbar back, or `Router.pop()` with a chance to ask "are you sure?". +- **`Sheet.showForResult()`** returns an `AsyncResource<T>` that auto-cancels with `null` if the user dismisses the sheet. + +The API is opt-in. Apps that prefer the existing `Form.show()` / `Form.showBack()` flow keep using that; nothing changes. + +For the link-publishing side, an `AasaBuilder` emits the iOS `apple-app-site-association` JSON and an `AssetLinksBuilder` emits the Android `assetlinks.json`. The full setup walk-through (entitlements, the Android `intent-filter`, the `.well-known/` upload on your origin server) is at [Routing and Deep Links](https://www.codenameone.com/developer-guide/#_routing_and_deep_links) in the developer guide. + +The JavaScript port bridges the router into `window.history` so navigating the in-app router pushes a real entry into the browser's session history. Back and forward in the browser drive the router; reloading the page lands at the deep-link URL; sharing the URL out of the address bar takes a colleague to the same in-app location. + ## How it works: the build-time codegen pipeline Everything above sits on a single Maven-plugin pass. @@ -301,17 +355,11 @@ A small stub source is emitted under `target/generated-sources/cn1-annotations/` cn1-core ships a no-op stub of each generated index (`RoutesIndex`, `MappersIndex`, `BindersIndex`, `DaosIndex`) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging. -Three non-negotiable rules across every processor: - -- **No `Class.forName`** anywhere in generated code. -- **No service loader.** Everything is wired through the typed registry the codegen emits. -- **No field reflection.** Every read and write in the generated code is a direct symbol reference that ParparVM rename and R8 obfuscation rewrite together with the class they target. - -The SVG and Lottie transcoders sit on a parallel pipeline (declarative graphics files in place of annotations), but follow the same rules and emit code that obeys the same constraints. The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. That is the shape Codename One projects are going to look more and more like over the next year. +The SVG and Lottie transcoders sit on a parallel pipeline (declarative graphics files in place of annotations), but they emit the same shape of code and obey the same constraints. The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references. ## Wrapping up -That closes the post series for this release. The next weekly index lands on Friday in the same short format. +That closes this release's post series. We already have some pretty big features lined up for **this Friday's** release post; the headline pieces are the most substantial things to land in months and worth checking back for. Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/). diff --git a/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif b/docs/website/static/blog/build-time-codegen/lottie-pulse-spinner.gif index 5f2d85a26e0626f21cd08544fde20b78f25fd448..78890247a756d0bafe4303d830e1c9ef8ac6fce0 100644 GIT binary patch literal 81494 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�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 H6@`BR)lq*^ literal 15739 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 From 6afaecd00d861be0bae50dd2845fbc5cf30802d6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 22:44:42 +0300 Subject: [PATCH 12/13] Build-time-codegen post: drop "knob", correct SVG coverage - Section heading "Sizing in millimeters is the important knob" shortened to "Sizing in millimeters". - The "Explicit non-coverage in v1" sentence was wrong: text and clip-path are supported in this release and visible in the static-SVG fixture screenshot we ship in the post. Rewritten to call out that text and clipPath landed via PR #5056, show what is supported (<text> / <tspan> with single-style fills and transforms; <clipPath> via clip-path="url(#id)" against rect / circle / path clip shapes), and accurately list what is still not covered (filter primitives, mask with alpha, radialGradient, CSS-in-SVG with selector style rules). --- docs/website/content/blog/build-time-codegen.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index 415d16747a..db0708eaa9 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -203,7 +203,7 @@ A grid of the static SVGs from the hellocodenameone fixture, rendered through th ![Static SVGs rendered by the build-time transcoder on iOS Metal: filled star, gradient-filled circle, path arrow, rounded button, two stroked wave paths, gradient-filled PRO badge, clipped badge](/blog/build-time-codegen/svg-static.png) -### Sizing in millimeters is the important knob +### Sizing in millimeters The SVG transcoder's most useful feature is also the one most easily missed: **size every SVG in millimeters from CSS**. SVGs in the wild routinely declare odd `width` / `height` attributes (a 1024×1024 export of a 24×24 icon, no dimensions at all, design-pixel values from one specific framework). Pinning the rendered size in millimeters sidesteps all of that. @@ -232,7 +232,9 @@ The transcoder is a `maven/svg-transcoder/` module that parses SVG with `javax.x SMIL animations are supported in the same pipeline: `<animate>`, `<animateTransform>` (`translate`, `scale`, `rotate`), and `<set>`. Time values interpolate against wall-clock time on every paint, with `from` / `to` / `values` / `begin` / `dur` / `repeatCount` / `fill="freeze"` honoured. -Explicit non-coverage in v1: SVG `text`, masks / clip-paths, filters, radial-gradient paint (falls back to first stop colour), CSS keyframe animations. +Text and clip-path landed in the [follow-up PR for the static SVG fixtures](https://github.com/codenameone/CodenameOne/pull/5056), and both are visible in the screenshot above (the "Codename One / build-time SVG" wordmark in the rounded button, the "PRO" badge text, and the clip-path-shaped rounded-corner badge underneath). `<text>` and `<tspan>` work with single-style fills and transforms; `<clipPath>` referenced via `clip-path="url(#id)"` works against `rect`, `circle`, and `path` clip shapes (nested clip refs are ignored). + +What is still not supported: SVG `filter` primitives, `<mask>` (treated as a clip, so alpha masking falls back to opaque), `<radialGradient>` (falls back to the first-stop colour), and CSS-in-SVG (style rules inside the SVG document; the transcoder reads presentation attributes and the inline `style="..."` attribute, but a `<style>` element with selectors is not parsed). **If you hit an SVG that does not transcode the way you expect**, please open an issue at [github.com/codenameone/CodenameOne/issues](https://github.com/codenameone/CodenameOne/issues) and **attach the source file**. The fastest way to extend the coverage is for us to run the failing case through the test fixtures and watch the output. Every SVG we ship test goldens for started as somebody else's "this doesn't render right" report. From 24ecfc4cf19e56a0847845ae38a231089d704af1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 30 May 2026 08:58:42 +0300 Subject: [PATCH 13/13] Build-time-codegen post: shift to 2026-06-01 Tighter daily cadence; the codegen post moves from Wednesday 2026-06-03 to Monday 2026-06-01. - Front-matter date: 2026-06-03 -> 2026-06-01. - Intro recap line "Saturday's was about how you iterate; Monday's was about new platform APIs" -> "Saturday's was about how you iterate; yesterday's was about new platform APIs". Today is now Monday. - "this Friday's release post" wrap-up tease unchanged; that refers to the next weekly release index post and Friday is unchanged. --- docs/website/content/blog/build-time-codegen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/website/content/blog/build-time-codegen.md b/docs/website/content/blog/build-time-codegen.md index db0708eaa9..7ef2a1abbd 100644 --- a/docs/website/content/blog/build-time-codegen.md +++ b/docs/website/content/blog/build-time-codegen.md @@ -2,7 +2,7 @@ title: "OpenAPI, ORM, SVG and Lottie" slug: build-time-codegen url: /blog/build-time-codegen/ -date: '2026-06-03' +date: '2026-06-01' author: Shai Almog description: An OpenAPI 3.x client generator that turns a spec into typed Codename One code, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, build-time SVG and Lottie transcoders, plus a declarative router and deep-link API. All ride on the same build-time codegen pipeline. feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" alt="OpenAPI, ORM, SVG and Lottie" /> An OpenAPI client generator, a JPA-shaped SQLite ORM, JAXB-shaped JSON / XML mappers, build-time SVG / Lottie transcoders, and a declarative router with deep links. All on the same build-time codegen pipeline.' @@ -10,7 +10,7 @@ feed_html: '<img src="https://www.codenameone.com/blog/build-time-codegen.jpg" a ![OpenAPI, ORM, SVG and Lottie](/blog/build-time-codegen.jpg) -This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; Monday's was about new platform APIs in the core; today's is about a run of pieces that change how you write the structural parts of an app. +This is the third follow-up to [Friday's release post](/blog/metal-default-new-build-cloud-and-a-new-format/). Saturday's was about how you iterate; yesterday's was about new platform APIs in the core; today's is about a run of pieces that change how you write the structural parts of an app. The pieces are an OpenAPI client generator, a SQLite ORM, JSON and XML mappers, a component binder with validation, build-time SVG and Lottie transcoders, and a declarative router with deep links. All ride on a single **build-time codegen pipeline**: a Maven-plugin pass that reads annotations or declarative source files at build time and emits typed Java that compiles into your binary. No reflection, no service loader, no `Class.forName`. The "How it works" section at the end of this post covers the codegen plumbing once you have seen what it powers.