<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shai Almog</title>
    <description>The latest articles on DEV Community by Shai Almog (@codenameone).</description>
    <link>https://dev.to/codenameone</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F417973%2Fa7b22dd9-5565-48f5-bfab-7e5035b3888f.png</url>
      <title>DEV Community: Shai Almog</title>
      <link>https://dev.to/codenameone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codenameone"/>
    <language>en</language>
    <item>
      <title>Lies, Damn Lies and Benchmarks</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 18 Sep 2026 17:04:18 +0000</pubDate>
      <link>https://dev.to/codenameone/lies-damn-lies-and-benchmarks-1f4p</link>
      <guid>https://dev.to/codenameone/lies-damn-lies-and-benchmarks-1f4p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwh1k5widy8gegu9yspf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwh1k5widy8gegu9yspf.jpg" alt="Lies, Damn Lies And Benchmarks" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's an old joke about three kinds of lies: &lt;a href="https://www.spec.org/osg/news/articles/news9412/lies.html" rel="noopener noreferrer"&gt;lies, damn lies, and benchmarks&lt;/a&gt;. Codename One did very well in our benchmarks. But were they accurate? Were they representative?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Performance is the fitted sheet of programming. Just when you get the damn corner into place, the rest of the bed is a complete mess.&lt;/p&gt;

&lt;p&gt;This week we made a concentrated effort to get a few more corners into place, and got a lot done. There's still work ahead that we'll discuss next week, but it's getting to a point where I feel confident saying Codename One is pretty fast. Not the best (yet), but fast.&lt;/p&gt;

&lt;p&gt;The trouble with a good benchmark score is how little it can tell you about the next operation. Our map was fast at finding keys that existed. Asking for missing keys exposed a lookup that could walk hundreds of thousands of slots. A collector setting that looked reasonable for a larger process let a small process retain garbage without buying measurable throughput. A rendering benchmark had no reason to notice that a switch generated blurred artwork before anyone asked to paint it.&lt;/p&gt;

&lt;p&gt;Those costs meet in an application. Opening a screen constructs styles and lays out components. Loading its data fills maps and boxes numbers. Displaying images creates results worth caching, until memory gets tight. We worked through those layers together because making one loop faster does not tell us whether the screen starts sooner or leaves less memory behind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;Last week&lt;/a&gt;, builders let us take responsibility for native integration that used to belong in each application's platform project. This week we are using the same reach inside the runtime: changing collection, object representation, UI internals, and generated JavaScript while keeping the application code in Java.&lt;/p&gt;

&lt;p&gt;Two major additions let users take their work further. &lt;strong&gt;Native drag and drop&lt;/strong&gt; lets them move files and content between your application and other apps. &lt;strong&gt;Cross-device continuity&lt;/strong&gt; lets them continue a task on another device, as well as recover it after the OS closes the process. Both now have Java APIs in Codename One.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming up: less waiting, more room, and work that travels
&lt;/h2&gt;

&lt;p&gt;Tomorrow's &lt;a href="https://www.codenameone.com/blog/parparvm-gc-small-heaps/" rel="noopener noreferrer"&gt;What Go Taught Us About Java Garbage Collection&lt;/a&gt; asks why a collector should keep memory that buys no speed, and how to stop a cache flush from making you decode every image again. On Sunday, &lt;a href="https://www.codenameone.com/blog/hashmap-misses-probe-sequence/" rel="noopener noreferrer"&gt;Faster Maps: Chasing Swiss Speed&lt;/a&gt; follows our comparison with Go's map design into faster lookups and numbers that no longer need their own heap objects.&lt;/p&gt;

&lt;p&gt;Monday brings &lt;a href="https://www.codenameone.com/blog/startup-cost-before-first-paint/" rel="noopener noreferrer"&gt;Faster Starts, Less JavaScript Overhead&lt;/a&gt;. A margin calculation was waiting on AppKit, and a blocking method was making unrelated JavaScript calls suspend. Tuesday's &lt;a href="https://www.codenameone.com/blog/continuity-restoring-work/" rel="noopener noreferrer"&gt;Native Drag and Drop Meets Cross-Device Continuity&lt;/a&gt; puts the new integration APIs to work: offer a document to another app, resume a draft on another device, and keep account changes from restoring the wrong user's screen.&lt;/p&gt;

&lt;p&gt;On Wednesday, &lt;a href="https://www.codenameone.com/blog/javadoc-hugo-markdown-doclet/" rel="noopener noreferrer"&gt;Javadoc That Feels Like Your Website&lt;/a&gt; shows how to give your own Java API reference the site's layout and search. We finish on September 17 with &lt;a href="https://www.codenameone.com/blog/android-37-readiness-location-button/" rel="noopener noreferrer"&gt;Android 17 Without the Last-Minute Scramble&lt;/a&gt;: catching SDK changes early, letting the OS handle location consent, and removing more security-sensitive glue from application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Less garbage, more room for your app
&lt;/h2&gt;

&lt;p&gt;As Java developers, HotSpot is the standard we tend to reach for. Comparing ParparVM with Go made me look beyond that familiar reference. My first suspicion was stack allocation: perhaps Go was simply creating fewer heap objects, the same sort of overhead that makes &lt;a href="https://github.com/openjdk/valhalla-docs/blob/main/site/design-notes/state-of-valhalla/02-object-model.md" rel="noopener noreferrer"&gt;Valhalla's value objects&lt;/a&gt; interesting.&lt;/p&gt;

&lt;p&gt;But Go's collection policy mattered too. ParparVM is an AOT runtime with a closed-world build, so we can experiment with allocation and collection together. We translate the application to C, own its object representation, and know what has to cooperate with the collector.&lt;/p&gt;

&lt;p&gt;We started with the allowance for new garbage. Letting more accumulate can save collection work, but that extra memory should buy something.&lt;/p&gt;

&lt;p&gt;In our trigger experiment, it bought very little. The stock 24 MB allocation floor was associated with 98 MB of loaded resident memory. Sweeping through lower trigger settings left throughput and p99 within run-to-run noise. A subsequent lower-floor configuration brought loaded RSS down to &lt;strong&gt;38 MB&lt;/strong&gt;. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5717" rel="noopener noreferrer"&gt;PR #5717&lt;/a&gt; records the experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fgc-trigger-rss.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Fgc-trigger-rss.svg" alt="Reported resident memory at five GC trigger settings" width="737" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Resident memory at each trigger setting. The final tuned configuration reached 38 MB; RSS includes memory outside the Java heap.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The live-set estimate was missing live objects
&lt;/h3&gt;

&lt;p&gt;The tempting next step was to derive the floor from the amount of live data. That would let a small process collect sooner and a larger one keep more allocation headroom.&lt;/p&gt;

&lt;p&gt;We tried it and backed it out. The sweep counters described retired pages, not the whole live population. Partial pages and the pages a mutator was still allocating into were outside that count; large objects had another path. Feeding that number into a proportional policy gave the policy an incomplete picture. One attempt retained an old live-set estimate long enough to suppress the sweeps needed to return pages to the OS.&lt;/p&gt;

&lt;p&gt;The merged code lets a deployment choose its minimum instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifndef CN1_BIBOP_GC_MIN_TRIGGER_BYTES
#define CN1_BIBOP_GC_MIN_TRIGGER_BYTES CN1_BIBOP_GC_TRIGGER_BYTES
#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A runtime build can select the smaller floor. We kept the stock default while working through more workloads and the incomplete live-set counters.&lt;/p&gt;

&lt;p&gt;Parallel marking is the other part of the investigation. In the recorded allocation loop, ParparVM's median and p99 matched Go. The worst pause still favored Go: about 20 ms, against 0.3 to 0.9 seconds with four ParparVM markers and 2.2 to 3.3 seconds with one. Parallel marking remains experimental. Allocation threads can help drain marking work instead of merely waiting at the run-ahead cap, but that assistance belongs to the parallel path, not the unchanged serial default.&lt;/p&gt;

&lt;h3&gt;
  
  
  A useful image should survive longer than a cold one
&lt;/h3&gt;

&lt;p&gt;Collecting sooner cannot help if the collector treats disposable cached data as permanently reachable. ParparVM's old &lt;code&gt;WeakReference&lt;/code&gt; held its referent in a field that the translator marked like any strong field. As long as the wrapper survived, so did the supposedly weak object.&lt;/p&gt;

&lt;p&gt;The iOS port approximated soft references with a strong-reference table and a low-memory warning. &lt;code&gt;flushSoftRefMap()&lt;/code&gt; replaced the table when pressure arrived. That could free a batch of cached artwork, but it also discarded the image the user was about to scroll back to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5732" rel="noopener noreferrer"&gt;PR #5732&lt;/a&gt; gives the collector real weak and soft references. It discovers the referent as a special edge, decides whether a soft referent should survive, then clears references eligible for reclamation. Soft retention uses the age since the last successful &lt;code&gt;get()&lt;/code&gt;. Repeatedly used images tend to stay recent; cold ones lose priority.&lt;/p&gt;

&lt;p&gt;The application-facing contract looks like this on the updated ParparVM path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PreviewCache&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;SoftReference&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/preview.png"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SoftReference&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This excerpt uses &lt;code&gt;java.lang.ref.SoftReference&lt;/code&gt;, &lt;code&gt;com.codename1.ui.Image&lt;/code&gt;, and &lt;code&gt;java.io.IOException&lt;/code&gt;, and assumes one calling thread. The local &lt;code&gt;result&lt;/code&gt; keeps a successful read strongly reachable while it is used. A miss reconstructs the image. A soft cache is appropriate only when that reconstruction is possible.&lt;/p&gt;

&lt;p&gt;The difficult case is a read during collection. A thread can call &lt;code&gt;get()&lt;/code&gt; after the collector has scanned its roots. Freeing the referent without noticing that read would hand Java code a dangling native pointer. Reference reads now participate in the marking barrier, and the collector drains new work before deciding it can sweep.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQXBwbGljYXRpb24gdGhyZWFkCiAgICBwYXJ0aWNpcGFudCBSZWYgYXMgU29mdCByZWZlcmVuY2UKICAgIHBhcnRpY2lwYW50IEdDIGFzIENvbGxlY3RvcgogICAgR0MtPj5BcHA6IFNjYW4gcm9vdHMKICAgIEFwcC0-PlJlZjogZ2V0KCkKICAgIFJlZi0-PkdDOiBMb2cgcmVmZXJlbnQgd2hlbiBtYXJraW5nIHJlcXVpcmVzIGl0CiAgICBSZWYtPj5BcHA6IFJldHVybiBpbWFnZQogICAgR0MtPj5HQzogRHJhaW4gbmV3IHdvcmsgYmVmb3JlIGVuZGluZyBtYXJraW5nCiAgICBHQy0-PlJlZjogQ2xlYXIgZWxpZ2libGUgY29sZCByZWZlcmVudHMKICAgIEdDLT4-R0M6IFN3ZWVw%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQXBwbGljYXRpb24gdGhyZWFkCiAgICBwYXJ0aWNpcGFudCBSZWYgYXMgU29mdCByZWZlcmVuY2UKICAgIHBhcnRpY2lwYW50IEdDIGFzIENvbGxlY3RvcgogICAgR0MtPj5BcHA6IFNjYW4gcm9vdHMKICAgIEFwcC0-PlJlZjogZ2V0KCkKICAgIFJlZi0-PkdDOiBMb2cgcmVmZXJlbnQgd2hlbiBtYXJraW5nIHJlcXVpcmVzIGl0CiAgICBSZWYtPj5BcHA6IFJldHVybiBpbWFnZQogICAgR0MtPj5HQzogRHJhaW4gbmV3IHdvcmsgYmVmb3JlIGVuZGluZyBtYXJraW5nCiAgICBHQy0-PlJlZjogQ2xlYXIgZWxpZ2libGUgY29sZCByZWZlcmVudHMKICAgIEdDLT4-R0M6IFN3ZWVw%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="817" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the simulated 160 MB budget test, ranked retention produced a &lt;strong&gt;97.44% hit rate at 82.1 MB&lt;/strong&gt;, compared with &lt;strong&gt;87.99% at 91.0 MB&lt;/strong&gt; for clearing on pressure. The next step is moving the iOS cache table and framework call sites onto the new reference machinery, then measuring the effect on real screens.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/parparvm-gc-small-heaps/" rel="noopener noreferrer"&gt;collector and cache article&lt;/a&gt; follows both experiments in detail. Lowering garbage headroom and retaining useful cached results address different sides of the same memory budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing Swiss speed without changing your Java maps
&lt;/h2&gt;

&lt;p&gt;Our open-addressed HashMap stores entries in arrays. A key's hash selects its first slot. If that slot is occupied by another key, the map probes elsewhere until it finds the key or an empty slot.&lt;/p&gt;

&lt;p&gt;Dense integer keys happened to make the first probe look excellent. Integer hashes preserve the value, and the usual &lt;code&gt;h ^= h &amp;gt;&amp;gt;&amp;gt; 16&lt;/code&gt; spread leaves small integers near their original positions. Insert consecutive integers starting at zero and occupied slots form a long run. A successful lookup often lands directly on its key. A missing key entering that run must walk to its end under linear probing. Tombstones left by deletion cannot terminate the search either.&lt;/p&gt;

&lt;p&gt;At 100,000 entries, the measured miss averaged &lt;strong&gt;16,742 probes&lt;/strong&gt;. At a million entries, it averaged &lt;strong&gt;222,721&lt;/strong&gt;. Hits stayed at one probe. A benchmark dominated by hits had almost nothing to say about this failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preserve the first probe and change what follows
&lt;/h3&gt;

&lt;p&gt;Scrambling every hash fixed the misses, but made dense construction and scans 1.8 to 2.2 times slower in the measured cases. We wanted to keep the locality of that first slot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5722" rel="noopener noreferrer"&gt;PR #5722&lt;/a&gt; keeps the first probe and changes the collision sequence using the recurrence associated with CPython's dictionary probing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;cn1NextSlot&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;perturb&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;perturb&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller shifts the unsigned perturbation right by five bits between probes. As those hash bits feed the recurrence, a collision can escape the occupied run. Once the perturbation reaches zero, the recurrence still visits the power-of-two table. Lookup, insertion, growth, and deletion have to agree on that sequence.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mean probes per miss, 100,000 entries&lt;/td&gt;
&lt;td&gt;16,742&lt;/td&gt;
&lt;td&gt;1.53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three million calls in the miss-heavy workload&lt;/td&gt;
&lt;td&gt;32,698 ms&lt;/td&gt;
&lt;td&gt;44.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String-key workload&lt;/td&gt;
&lt;td&gt;33.6 ms&lt;/td&gt;
&lt;td&gt;25.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large table with random hits&lt;/td&gt;
&lt;td&gt;26.6 ms&lt;/td&gt;
&lt;td&gt;33.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row got worse. That tradeoff belongs beside the spectacular miss result, because the change did not make every map operation hundreds of times faster.&lt;/p&gt;

&lt;p&gt;Go's &lt;a href="https://go.dev/blog/swisstable" rel="noopener noreferrer"&gt;Swiss maps&lt;/a&gt; were another useful comparison. ParparVM already keeps compact metadata separate from its key and value arrays, so we were closer in structure than a map built around an allocation per entry. The merged lookup remains scalar perturbed probing. The SIMD-related improvement is on the string-key path: unequal cached hashes reject a match early, and compatible UTF-16 storage goes through native &lt;code&gt;memcmp&lt;/code&gt;, where the platform can use optimized vector comparisons. Our probe sequence and string comparisons address different stages of the lookup. The timings here compare the old and new ParparVM implementations.&lt;/p&gt;

&lt;p&gt;Hashtable also moves away from an &lt;code&gt;Entry&lt;/code&gt; allocation per mapping. IdentityHashMap needed a different fix: ParparVM's identity hash comes from an aligned address, so copying an indexing expression suited to HotSpot's already-scrambled identity hashes left too many zero low bits. Folding high bits down improved its measured distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  A compact table can still be full of little allocations
&lt;/h3&gt;

&lt;p&gt;A JSON parser filling a map may allocate a wrapper for every number. Our &lt;a href="https://www.codenameone.com/blog/beating-hotspot-performance/" rel="noopener noreferrer"&gt;earlier performance work&lt;/a&gt; removed much of that cost for Integer. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5735" rel="noopener noreferrer"&gt;PR #5735&lt;/a&gt; extends the representation to Short, Character, Float, Long, and Double.&lt;/p&gt;

&lt;p&gt;Aligned object addresses leave three low bits free in this 64-bit runtime. The collector's root scan already distinguishes words with those bits set from ordinary object pointers. We use them as a type tag and put the value in the remaining 61 bits. The map slot contains the number's representation directly, instead of a pointer to a separate wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42L&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// Tagged on ParparVM&lt;/span&gt;
&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Heap fallback&lt;/span&gt;
&lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="n"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;12.5&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// Tagged&lt;/span&gt;
&lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="n"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// Heap fallback&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short, Character, and Float fit across their full ranges. Long fits from &lt;code&gt;-2^60&lt;/code&gt; through &lt;code&gt;2^60 - 1&lt;/code&gt;. Double fits when its low three mantissa bits are clear. Byte and Boolean already have bounded caches. Use &lt;code&gt;equals()&lt;/code&gt; for value equality; application code should not infer identity from the runtime's allocation choices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBWW0JveCBhIHByaW1pdGl2ZSB2YWx1ZV0gLS0-IEZ7Rml0cyB0aGUgZW5jb2Rpbmc_fQogICAgRiAtLT58WWVzfCBUW1R5cGUgdGFnIGFuZCBwYXlsb2FkIGluIHRoZSByZWZlcmVuY2Ugd29yZF0KICAgIEYgLS0-fE5vfCBIW0FsbG9jYXRlIGEgaGVhcCB3cmFwcGVyXQogICAgVCAtLT4gRFtEaXNwYXRjaCB1c2luZyB0aGUgdGFnZ2VkIHR5cGVdCiAgICBIIC0tPiBECiAgICBEIC0tPiBKW0phdmEgd3JhcHBlciBiZWhhdmlvcl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBWW0JveCBhIHByaW1pdGl2ZSB2YWx1ZV0gLS0-IEZ7Rml0cyB0aGUgZW5jb2Rpbmc_fQogICAgRiAtLT58WWVzfCBUW1R5cGUgdGFnIGFuZCBwYXlsb2FkIGluIHRoZSByZWZlcmVuY2Ugd29yZF0KICAgIEYgLS0-fE5vfCBIW0FsbG9jYXRlIGEgaGVhcCB3cmFwcGVyXQogICAgVCAtLT4gRFtEaXNwYXRjaCB1c2luZyB0aGUgdGFnZ2VkIHR5cGVdCiAgICBIIC0tPiBECiAgICBEIC0tPiBKW0phdmEgd3JhcHBlciBiZWhhdmlvcl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1376" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JSON-like allocation census fell from &lt;strong&gt;24.02 boxed allocations per map to 5.24&lt;/strong&gt;. That is work the collector never has to do. This extends our “poor man's Valhalla” work: put a known wrapper value directly into its reference word. The tag travels with it, including when that word lives in a heap collection. Long and Double keep a heap fallback for values that do not fit.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/hashmap-misses-probe-sequence/" rel="noopener noreferrer"&gt;maps and boxing article&lt;/a&gt; covers the coverage measurements and dispatch hazards. A type tag must select the right &lt;code&gt;hashCode&lt;/code&gt; and &lt;code&gt;equals&lt;/code&gt; implementation. Reusing the Integer fast path for every tag would produce a fast, incorrect map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster starts begin with work your app never needed
&lt;/h2&gt;

&lt;p&gt;Before the first frame, a screen asks a lot of components how big they are. Padding and margin conversion needs the screen scale. Our native Mac path synchronously asked AppKit which screen contained the window, even though the answer usually had not changed since the previous component asked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5686" rel="noopener noreferrer"&gt;PR #5686&lt;/a&gt; publishes screen identity and scale together when the window is created or moves. Layout reads that state atomically. Publishing the pair together also prevents a reader from combining one screen with another screen's scale.&lt;/p&gt;

&lt;p&gt;The old query accounted for 35 ms of blocked event-dispatch-thread time in the recorded startup profile. Installing a window observer added another synchronous wait even though the caller needed no return value. That path accounted for 37 ms. The Mac profile gave us two specific waits to remove from startup.&lt;/p&gt;

&lt;p&gt;The same profile found an uncontended lock announcing that it was about to park before trying to acquire its mutex. That announcement could wait on a GC handshake. The runtime now tries the lock first and enters the park protocol when it actually has to wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout was doing work that belonged to painting
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;UIManager&lt;/code&gt; scanned the whole theme table to find dark variants for each distinct UIID. We now build an index once per theme generation. First-use dark-variant lookup in the benchmark fell from &lt;strong&gt;111,955 ns to 17,378 ns&lt;/strong&gt;. The index is invalidated when a new theme changes the answer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Switch.getPreferredSize()&lt;/code&gt; had a more surprising cost: generating switch artwork, including a Gaussian blur. A layout query could pay that cost for a component that was never painted. Preferred size now comes from the same dimensions used by the drawing code; generating the pixels waits until they are needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBXW1dpbmRvdyBjcmVhdGVkIG9yIG1vdmVkXSAtLT4gU1tQdWJsaXNoIHNjcmVlbiBzY2FsZV0KICAgIFMgLS0-IExbTGF5b3V0IHJlYWRzIGN1cnJlbnQgc2NhbGVdCiAgICBUW1RoZW1lIGxvYWRlZF0gLS0-IElbSW5kZXggZGFyayBVSUlEcyBvbmNlXQogICAgSSAtLT4gQ1tDb25zdHJ1Y3QgY29tcG9uZW50IHN0eWxlXQogICAgRFtTd2l0Y2ggZHJhd2luZyBkaW1lbnNpb25zXSAtLT4gUFtDYWxjdWxhdGUgcHJlZmVycmVkIHNpemVdCiAgICBQIC0tPiBSW0dlbmVyYXRlIGFydHdvcmsgd2hlbiBwYWludGluZyBuZWVkcyBpdF0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBXW1dpbmRvdyBjcmVhdGVkIG9yIG1vdmVkXSAtLT4gU1tQdWJsaXNoIHNjcmVlbiBzY2FsZV0KICAgIFMgLS0-IExbTGF5b3V0IHJlYWRzIGN1cnJlbnQgc2NhbGVdCiAgICBUW1RoZW1lIGxvYWRlZF0gLS0-IElbSW5kZXggZGFyayBVSUlEcyBvbmNlXQogICAgSSAtLT4gQ1tDb25zdHJ1Y3QgY29tcG9uZW50IHN0eWxlXQogICAgRFtTd2l0Y2ggZHJhd2luZyBkaW1lbnNpb25zXSAtLT4gUFtDYWxjdWxhdGUgcHJlZmVycmVkIHNpemVdCiAgICBQIC0tPiBSW0dlbmVyYXRlIGFydHdvcmsgd2hlbiBwYWludGluZyBuZWVkcyBpdF0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="858" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Metal path also avoids taking each picture back through the CPU and retaining a decoded &lt;code&gt;EncodedImage&lt;/code&gt; copy beside the texture. Rounded corners can be drawn in the shader. Removing an image representation saves both its construction cost and the memory it would occupy after startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript was preparing to suspend methods that could not block
&lt;/h3&gt;

&lt;p&gt;The web compiler had a similar problem with an overly broad assumption. A Java method that might block becomes a JavaScript generator so execution can yield and later resume. Our analysis used the method name and descriptor without distinguishing its receiver class.&lt;/p&gt;

&lt;p&gt;Consider these two unrelated classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WaitingTask&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterTask&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A call on &lt;code&gt;CounterTask&lt;/code&gt; cannot reach &lt;code&gt;WaitingTask.run()&lt;/code&gt;. But treating &lt;code&gt;run()&lt;/code&gt; as one blocking signature made unrelated calls suspension points, then spread that property into their callers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5755" rel="noopener noreferrer"&gt;PR #5755&lt;/a&gt; reuses the receiver types already computed by reachability analysis. It considers implementations that can actually receive the call. Unresolved dispatch stays conservative, and analysis and code generation share the same model so they agree about where &lt;code&gt;yield*&lt;/code&gt; is legal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generated sample output&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;yield*&lt;/code&gt; sites&lt;/td&gt;
&lt;td&gt;54,549&lt;/td&gt;
&lt;td&gt;40,741&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generator methods&lt;/td&gt;
&lt;td&gt;13,068&lt;/td&gt;
&lt;td&gt;11,044&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;8,089,807 bytes&lt;/td&gt;
&lt;td&gt;7,993,916 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Yield sites fell &lt;strong&gt;25.3%&lt;/strong&gt; while bundle size fell only &lt;strong&gt;1.2%&lt;/strong&gt;. Counting bytes would barely notice the change in dispatch. Node throughput measurements found gains in several workloads, but &lt;code&gt;iteratorWalk&lt;/code&gt; took &lt;strong&gt;13.7% longer&lt;/strong&gt;, with no established cause yet. That is on the list for further investigation.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/startup-cost-before-first-paint/" rel="noopener noreferrer"&gt;native and JavaScript performance article&lt;/a&gt; follows these paths and their tests. The common question is whether an operation needs to wait at all, before trying to make its waiting machinery faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-device continuity: pick up where you left off
&lt;/h2&gt;

&lt;p&gt;Start writing on your phone and continue on your tablet. Reopen an app after the OS reclaimed its process and return to the draft you were editing. Cross-device continuity lets an application preserve the task the user cares about, instead of making the current process its only home.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Form&lt;/code&gt; saved in a field cannot do that. The field disappears with the process, and another device cannot reconstruct a screen from an object in the first device's memory. It needs a description of the work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5663" rel="noopener noreferrer"&gt;PR #5663&lt;/a&gt; adds &lt;code&gt;com.codename1.continuity&lt;/code&gt;. It saves application state and the router stack as a checkpoint that a new process can reconstruct. A route identifies the screen; a &lt;code&gt;StateProvider&lt;/code&gt; supplies the data needed to resume the work. For a draft editor, that can be as small as the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Continuity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStateProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateProvider&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;saveState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"draft"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draftField&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;restoreState&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"draft"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;draftField&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;draftField&lt;/code&gt; is the application's TextArea. Registering the provider enables continuity. Navigation schedules checkpoints; edits can call &lt;code&gt;Continuity.checkpoint()&lt;/code&gt;. Keep the payload small, exclude credentials, and decide whether sensitive drafts belong in it.&lt;/p&gt;

&lt;p&gt;Restoration happens after the application knows which account is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run after authentication and route registration.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="nc"&gt;Continuity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;restore&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Navigation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apple Handoff can offer the activity to another signed-in device. An application-owned &lt;code&gt;StateRelay&lt;/code&gt; can carry state through your existing accounts and server, including across Android, Apple devices, and a browser. Codename One does not operate that relay or choose its account-isolation policy. iCloud key-value sync is a separate package with its own entitlement requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native drag and drop: take it outside your app
&lt;/h2&gt;

&lt;p&gt;Drag a report into another application. Drop a file into your own import screen. Offer a selection as formatted HTML to an editor or as plain text to an application that only wants the words. These are operating-system drag sessions, with a destination outside your form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5662" rel="noopener noreferrer"&gt;PR #5662&lt;/a&gt; adds that native drag and drop beside our existing lightweight API. In-form dragging still works as before. The new API connects components to the OS drag session using &lt;code&gt;ClipboardContent&lt;/code&gt;, so the same representations that work for copy and paste can be offered to a drag receiver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNativeDragOperation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NativeDragOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createFileDrag&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNativeDropTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAcceptedDropMimeTypes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClipboardContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MIME_FILE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addNativeDropListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;NativeDropEvent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getFiles&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;queueImport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This excerpt assumes existing export &lt;code&gt;paths&lt;/code&gt;, a receiving component named &lt;code&gt;inbox&lt;/code&gt;, and an application &lt;code&gt;queueImport&lt;/code&gt; that validates files and moves expensive parsing off the event dispatch thread. MIME acceptance is not a trust decision.&lt;/p&gt;

&lt;p&gt;Provider timing depends on the port. JavaSE can request the representation lazily. Android assembles complete &lt;code&gt;ClipData&lt;/code&gt; before starting the drag, so every provider runs then. iOS resolves file lists at drag start to count the items. Keep providers cheap enough for that moment, or prepare and cache an expensive export beforehand. Canceling a drag does not guarantee that its data was never generated.&lt;/p&gt;

&lt;p&gt;The initial implementation covers JavaSE, Android, and UIKit ports, with cross-application behavior on Android, iPadOS, and Mac Catalyst. iPhone has a narrower interaction model. Native AppKit, Windows, Linux, and JavaScript do not implement this native-drag path yet. Check &lt;code&gt;NativeDragAndDrop.isSupported()&lt;/code&gt; before relying on it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/continuity-restoring-work/" rel="noopener noreferrer"&gt;native drag and drop and continuity article&lt;/a&gt; includes the platform matrix, callback threading, and logout sequence. A received checkpoint must still pass the current account's authorization checks; a completed move must be confirmed before deleting the source. Moving work should not mean losing ownership of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give your Javadoc the website it deserves
&lt;/h2&gt;

&lt;p&gt;We kept improving the developer guide, but the API reference still carried standard Javadoc's layout and stylesheet into a wrapper inside our site. Search and dark mode made that separation particularly obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5743" rel="noopener noreferrer"&gt;PR #5743&lt;/a&gt; changes what the doclet emits. Javadoc supplies the Java API model and documentation trees. The new &lt;code&gt;maven/javadoc-hugo-doclet&lt;/code&gt; module writes Hugo content with structured front matter for signatures and members. Hugo renders it using the site's own templates and Markdown renderer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBKW0phdmEgc291cmNlcyBhbmQgTWFya2Rvd24gY29tbWVudHNdIC0tPiBEW0phdmFkb2MgQVBJIG1vZGVsXQogICAgRCAtLT4gQ1tIdWdvIGNvbnRlbnQgYW5kIG1lbWJlciBtZXRhZGF0YV0KICAgIEMgLS0-IEhbV2Vic2l0ZSB0ZW1wbGF0ZXNdCiAgICBEIC0tPiBJW0FQSSBzZWFyY2ggaW5kZXhdCiAgICBJIC0tPiBTW1dlYnNpdGUgc2VhcmNoXQogICAgRCAtLT4gWltTdGFuZGFyZCBkb2NsZXQgZm9yIG9mZmxpbmUgYXJjaGl2ZV0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBKW0phdmEgc291cmNlcyBhbmQgTWFya2Rvd24gY29tbWVudHNdIC0tPiBEW0phdmFkb2MgQVBJIG1vZGVsXQogICAgRCAtLT4gQ1tIdWdvIGNvbnRlbnQgYW5kIG1lbWJlciBtZXRhZGF0YV0KICAgIEMgLS0-IEhbV2Vic2l0ZSB0ZW1wbGF0ZXNdCiAgICBEIC0tPiBJW0FQSSBzZWFyY2ggaW5kZXhdCiAgICBJIC0tPiBTW1dlYnNpdGUgc2VhcmNoXQogICAgRCAtLT4gWltTdGFuZGFyZCBkb2NsZXQgZm9yIG9mZmxpbmUgYXJjaGl2ZV0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1074" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tooling uses Java 25 in a separate module from the Java 8 build. Markdown documentation comments themselves arrived in JDK 23. The doclet preserves their prose for Hugo and extracts recognized sections such as parameters and returns into member data. We still generate the standard &lt;code&gt;javadocs.zip&lt;/code&gt; archive from the same sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdg7ykswxkeo09wcyg25h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdg7ykswxkeo09wcyg25h.png" alt="Website search finds PublicKey and PrivateKey fromPem members" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A live site capture from September 10. API methods now appear in website search, grouped by type. The full developer guide retains its own navigation and browser search rather than joining this index.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is reusable source for Java developers who want their own site generator to own the API pages. The &lt;a href="https://github.com/codenameone/CodenameOne/tree/561dab8e05/maven/javadoc-hugo-doclet" rel="noopener noreferrer"&gt;doclet module&lt;/a&gt;, &lt;a href="https://github.com/codenameone/CodenameOne/tree/561dab8e05/docs/website/layouts/javadoc" rel="noopener noreferrer"&gt;Hugo layouts&lt;/a&gt;, and build integration are available in the repository. You still need to adapt the templates and URL conventions to your site.&lt;/p&gt;

&lt;p&gt;The migration also checked &lt;strong&gt;2,272 pages and 29,583 fragments&lt;/strong&gt; against the old output. A prettier member page is not much use if years of links to that member stop working. The &lt;a href="https://www.codenameone.com/blog/javadoc-hugo-markdown-doclet/" rel="noopener noreferrer"&gt;doclet article&lt;/a&gt; includes a complete sample, build commands, and the anchor defects that parity testing caught.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 17 without the last-minute scramble
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5731" rel="noopener noreferrer"&gt;PR #5731&lt;/a&gt; adds API 37 compilation checks and generated-application assembly. This matters before changing the default target SDK: compiling against the old platform cannot tell us which referenced classes the new one has removed.&lt;/p&gt;

&lt;p&gt;The check compiles the same port sources against both platform jars and compares the errors. It removes duplicate &lt;code&gt;android.jar&lt;/code&gt; entries so an old jar cannot silently supply a symbol missing from the new platform. A fault-injection test using removed fingerprint APIs proved that the check catches the failure it was designed to find.&lt;/p&gt;

&lt;p&gt;Builder assumptions needed attention too. Platform names such as &lt;code&gt;android-37.2&lt;/code&gt; are no longer integer suffixes. Stripping punctuation would turn &lt;code&gt;37.2&lt;/code&gt; into &lt;code&gt;372&lt;/code&gt;, which passes minimum-version checks for completely the wrong reason. The builder now extracts the major level correctly and uses the SDK manager belonging to the selected SDK root. The normal SDK floor remains 36 while this preparation continues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask for location at the moment the user needs it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5738" rel="noopener noreferrer"&gt;PR #5738&lt;/a&gt; adds the Android 17 system location button, with an ordinary Codename One button and existing permission flow on older platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;LocationButton&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LocationButton&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;LocationButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TEXT_USE_PRECISE_LOCATION&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addLocationSharedListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;searchNearby&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLatitude&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLongitude&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searchNearby&lt;/code&gt; belongs to the application. Declined permission or an unavailable result can produce &lt;code&gt;null&lt;/code&gt;. The component exposes &lt;code&gt;isSystemRendered()&lt;/code&gt; so an application can verify which path is active.&lt;/p&gt;

&lt;p&gt;The OS draws the supported control into a hosted surface and ties the request to a visible user action and a session-scoped location grant. The implementation uses the platform session API through reflection, avoiding an AndroidX dependency that would force a newer Android Gradle Plugin on every application using the button. The builder adds &lt;code&gt;USE_LOCATION_BUTTON&lt;/code&gt; when it finds the component; the separate cloud-builder mirror still needs to be present in the builder serving the app.&lt;/p&gt;

&lt;p&gt;The PR exercised the system button, a human tap, consent, and a returned location on an Android 17 emulator, with fallback on API 36. The regular screenshot suite still runs on API 36; broader API 37 coverage is part of the remaining migration work. The &lt;a href="https://www.codenameone.com/blog/android-37-readiness-location-button/" rel="noopener noreferrer"&gt;Android and security article&lt;/a&gt; separates those checks and links the current platform guidance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop making each application parse its own key armor
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5707" rel="noopener noreferrer"&gt;PR #5707&lt;/a&gt; adds &lt;code&gt;PublicKey.fromPem&lt;/code&gt; and &lt;code&gt;PrivateKey.fromPem&lt;/code&gt; for text or file bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PublicKey&lt;/span&gt; &lt;span class="n"&gt;publicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromPem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publicStream&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;PrivateKey&lt;/span&gt; &lt;span class="n"&gt;privateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrivateKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromPem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privateStream&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These use the Codename One security classes and the application's existing input streams. The parser strips the armor, decodes Base64, validates a complete DER element, and recognizes the key container. Supported PKCS#1 RSA and SEC1 EC inputs are rewrapped into the container expected downstream. Encrypted keys, certificates, wrong public/private direction, and truncated or trailing DER are rejected with an explanation.&lt;/p&gt;

&lt;p&gt;That removes format handling from application glue code. It does not decide whether a public key is trusted or where a private key should be stored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing a task is part of ending a session
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5746" rel="noopener noreferrer"&gt;PR #5746&lt;/a&gt; adds &lt;code&gt;CN.exitAndClearTask()&lt;/code&gt;. On Android it removes the task from recents before following the process-exit path. Killing the process alone could leave the task in the switcher.&lt;/p&gt;

&lt;p&gt;For an application using continuity, the framework calls at logout are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Continuity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Continuity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disable&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Finish account cleanup and any asynchronous sign-out work before this call.&lt;/span&gt;
&lt;span class="no"&gt;CN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exitAndClearTask&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clearing saved state without disabling continuity leaves a route for an arriving activity to restore it again. Removing the task without revoking credentials leaves a session alive. These calls handle restoration and task lifecycle; your application still owns credential revocation and account data. Other ports retain their existing exit behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Less runtime work, fewer places to get security wrong
&lt;/h2&gt;

&lt;p&gt;The collector and reference changes let us distinguish garbage from useful cached data. The map changes fix a pathological search and remove wrapper allocations from its contents. Style construction, layout, and JavaScript generation now do less work before the application can get on with its own job. There are still regressions to investigate and cache integrations to finish, which is why we are continuing the performance work next week.&lt;/p&gt;

&lt;p&gt;Continuity and native drag and drop expand what the user can do with that application. The doclet puts the API contracts where site search can find them. Android preparation moves platform breakage into our checks before a mandatory migration moves it into a customer's release schedule.&lt;/p&gt;

&lt;p&gt;An application can use ordinary Java references while Codename One's runtime handles the race between a cache read and collection. It can read a PEM key without maintaining its own DER conversion. It can ask for a location through the OS consent surface and explicitly shut down restoration when an account signs out.&lt;/p&gt;

&lt;p&gt;Secure-by-default programming depends on those details. We maintain the API, translator, runtime, and builders together so app teams do not each have to rediscover them in native code. The application's authorization and data rules still belong to its developers. The shared implementation should leave them more time to get those rules right.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Pick One Contact Without Asking for the Address Book</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 16 Sep 2026 17:39:18 +0000</pubDate>
      <link>https://dev.to/codenameone/pick-one-contact-without-asking-for-the-address-book-578c</link>
      <guid>https://dev.to/codenameone/pick-one-contact-without-asking-for-the-address-book-578c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvsp6dt1ik2vvm7t4hl3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvsp6dt1ik2vvm7t4hl3.jpg" alt="One selected contact leaving a private address book" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A share screen needs one phone number. A delivery form needs one address. Neither feature needs a database of everybody the user knows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5680" rel="noopener noreferrer"&gt;PR #5680&lt;/a&gt; adds &lt;code&gt;ContactPicker&lt;/code&gt;, a permission-free path that returns only the contacts and fields the user selects. It uses the Android 17 system Contact Picker, a narrow fallback on older Android versions, and Apple's &lt;code&gt;CNContactPickerViewController&lt;/code&gt; on iOS.&lt;/p&gt;

&lt;p&gt;For VoIP, VPN, native Mac and Windows work, OTP, billing, and the platform-watch process that found this gap, read the &lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;weekly overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for the fields the screen needs
&lt;/h2&gt;

&lt;p&gt;The basic flow declares a field mask and opens the system picker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ContactPicker&lt;/span&gt; &lt;span class="n"&gt;picker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContactPicker&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;picker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestedFields&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ContactPicker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nc"&gt;ContactPicker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PHONE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;picker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pick&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContactPicker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPickedContacts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Contact&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;recipientName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;recipientPhone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrimaryPhoneNumber&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available fields include name, phone, email, address, photo, birthday, and website. Requesting a field describes the desired result. A platform may return less because the contact lacks it or because its picker does not support every combination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgUGlja2VyIGFzIFN5c3RlbSBDb250YWN0IFBpY2tlcgogICAgcGFydGljaXBhbnQgVXNlcgogICAgcGFydGljaXBhbnQgQm9vayBhcyBQcml2YXRlIGFkZHJlc3MgYm9vawogICAgQXBwLT4-UGlja2VyOiBSZXF1ZXN0IG5hbWUgYW5kIHBob25lCiAgICBQaWNrZXItPj5Vc2VyOiBTaG93IHN5c3RlbS1vd25lZCBjaG9pY2VzCiAgICBVc2VyLT4-UGlja2VyOiBTZWxlY3Qgb25lIGNvbnRhY3QKICAgIFBpY2tlci0-PkJvb2s6IFJlYWQgc2VsZWN0ZWQgZmllbGRzCiAgICBQaWNrZXItLT4-QXBwOiBSZXR1cm4gYSBzbmFwc2hvdAogICAgTm90ZSBvdmVyIEFwcCxCb29rOiBObyBzdGFuZGluZyBhZGRyZXNzLWJvb2sgcGVybWlzc2lvbg%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgUGlja2VyIGFzIFN5c3RlbSBDb250YWN0IFBpY2tlcgogICAgcGFydGljaXBhbnQgVXNlcgogICAgcGFydGljaXBhbnQgQm9vayBhcyBQcml2YXRlIGFkZHJlc3MgYm9vawogICAgQXBwLT4-UGlja2VyOiBSZXF1ZXN0IG5hbWUgYW5kIHBob25lCiAgICBQaWNrZXItPj5Vc2VyOiBTaG93IHN5c3RlbS1vd25lZCBjaG9pY2VzCiAgICBVc2VyLT4-UGlja2VyOiBTZWxlY3Qgb25lIGNvbnRhY3QKICAgIFBpY2tlci0-PkJvb2s6IFJlYWQgc2VsZWN0ZWQgZmllbGRzCiAgICBQaWNrZXItLT4-QXBwOiBSZXR1cm4gYSBzbmFwc2hvdAogICAgTm90ZSBvdmVyIEFwcCxCb29rOiBObyBzdGFuZGluZyBhZGRyZXNzLWJvb2sgcGVybWlzc2lvbg%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="938" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The returned &lt;code&gt;Contact&lt;/code&gt; is a snapshot. Copy the values the feature needs while handling the result. Its picker identifier is not a token for later unrestricted access through &lt;code&gt;ContactsManager&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform differences remain visible
&lt;/h2&gt;

&lt;p&gt;On Android 17, &lt;a href="https://developer.android.com/about/versions/17/features/contact-picker" rel="noopener noreferrer"&gt;&lt;code&gt;ACTION_PICK_CONTACTS&lt;/code&gt;&lt;/a&gt; supports the new privacy-focused system surface. Older Android releases use &lt;code&gt;ACTION_PICK&lt;/code&gt; for one contact without requesting broad contacts permission. The fallback cannot promise every requested field or multiple selection.&lt;/p&gt;

&lt;p&gt;iOS presents &lt;code&gt;CNContactPickerViewController&lt;/code&gt;. This path does not need &lt;code&gt;NSContactsUsageDescription&lt;/code&gt; because the user chooses the data in a system-owned interface. The simulator supplies a deterministic picker for UI and cancellation tests.&lt;/p&gt;

&lt;p&gt;Other ports report the capability as unsupported. Application code should keep its manual-entry path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContactPicker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chooseContactButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEnabled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chooseContactButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setVisible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;manualEntry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setVisible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-selection and “require all fields” are requests, not portable guarantees. A form should treat a missing phone number as a normal result and let the user enter it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getDisplayName()&lt;/code&gt; may synthesize a useful label when the native contact has no display string. Use the given and family name fields when the distinction matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permission detection had to become more precise
&lt;/h2&gt;

&lt;p&gt;The builder previously treated references near the contacts API as a reason to add broad address-book access. That would defeat the point of the new picker.&lt;/p&gt;

&lt;p&gt;Feature scanning now distinguishes &lt;code&gt;ContactPicker&lt;/code&gt;, contact value objects, and direct broad-reader calls. Using &lt;code&gt;Contact&lt;/code&gt; as the picker result does not add permission by association. Calling a &lt;code&gt;Display&lt;/code&gt; method that reads the address book still does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBSW1JlZmVyZW5jZWQgY29udGFjdCBjb2RlXSAtLT4gUXtCcm9hZCByZWFkZXIgdXNlZD99CiAgICBRIC0tPnxObywgcGlja2VyIGFuZCB2YWx1ZXMgb25seXwgUFtTeXN0ZW0gcGlja2VyPGJyLz5ubyBicm9hZCBwZXJtaXNzaW9uXQogICAgUSAtLT58WWVzfCBCW0NvbnRhY3RzIEFQSTxici8-cGxhdGZvcm0gcGVybWlzc2lvbiBmbG93XQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBSW1JlZmVyZW5jZWQgY29udGFjdCBjb2RlXSAtLT4gUXtCcm9hZCByZWFkZXIgdXNlZD99CiAgICBRIC0tPnxObywgcGlja2VyIGFuZCB2YWx1ZXMgb25seXwgUFtTeXN0ZW0gcGlja2VyPGJyLz5ubyBicm9hZCBwZXJtaXNzaW9uXQogICAgUSAtLT58WWVzfCBCW0NvbnRhY3RzIEFQSTxici8-cGxhdGZvcm0gcGVybWlzc2lvbiBmbG93XQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="503" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The builder is part of the API design. A permission-safe Java surface is incomplete if packaging adds a permission the application never asked for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy is also product design
&lt;/h2&gt;

&lt;p&gt;Broad contact access asks users to trust the application with relationships unrelated to the task in front of them. A system picker changes that conversation. The user sees the operating system's interface, chooses a specific person, and returns only the relevant snapshot.&lt;/p&gt;

&lt;p&gt;The security gain is concrete, but the product flow improves too. There is no pre-permission explanation, denial recovery, settings detour, or empty contact screen after a refusal. Manual entry remains available when selection is unsupported or the desired field is missing.&lt;/p&gt;

&lt;p&gt;This week's &lt;a href="https://www.codenameone.com/blog/sms-otp-autofill/" rel="noopener noreferrer"&gt;OTP support&lt;/a&gt; follows the same rule by accepting one code without reading the inbox. Call and VPN packages activate only their own native services. Builders inspect what the application uses and keep everything else out.&lt;/p&gt;

&lt;p&gt;Secure defaults grow through choices this ordinary. The easiest API asks for the smallest useful slice of data. A developer must make a deliberate choice to widen access.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>We Stopped Waiting for Platform Changes to Find Us</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 15 Sep 2026 17:40:13 +0000</pubDate>
      <link>https://dev.to/codenameone/we-stopped-waiting-for-platform-changes-to-find-us-4f15</link>
      <guid>https://dev.to/codenameone/we-stopped-waiting-for-platform-changes-to-find-us-4f15</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7chweiz7prh0m57f9jn6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7chweiz7prh0m57f9jn6.jpg" alt="A daily platform notice moving through source checks into a fix" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For years, our warning system for Apple and Google changes was a developer whose build failed first. The community gave us excellent reports, but the sequence was backwards. A platform owner announced a deadline, time passed, an application hit the change, and only then did we trace it into the builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This week we started a daily scheduled Codex task that reads official notices and checks them against Codename One. Its first useful results include Android 16 back handling in &lt;a href="https://github.com/codenameone/CodenameOne/pull/5673" rel="noopener noreferrer"&gt;PR #5673&lt;/a&gt; and the permission-safe Contact Picker in &lt;a href="https://github.com/codenameone/CodenameOne/pull/5680" rel="noopener noreferrer"&gt;PR #5680&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the rest of the release, read the &lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;weekly overview&lt;/a&gt;. The Contact Picker has a separate &lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;code-focused article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A notice is the start of an investigation
&lt;/h2&gt;

&lt;p&gt;Scheduled browsing alone would create noise. Platform pages change wording, policies overlap, and a requirement may already be handled in a builder branch that application code never sees. The task must connect the notice to an artifact we produce.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBOW09mZmljaWFsIEFwcGxlIG9yIEdvb2dsZSBub3RpY2VdIC0tPiBSW0V4dHJhY3QgcmVxdWlyZW1lbnQ8YnIvPnNjb3BlIGFuZCBkYXRlXQogICAgUiAtLT4gUFtUcmFjZSBDTjEgQVBJPGJyLz5idWlsZGVyIG9yIGdlbmVyYXRlZCB0YXJnZXRdCiAgICBQIC0tPiBEe0FscmVhZHkgaGFuZGxlZD99CiAgICBEIC0tPnxZZXN8IEVbUmVjb3JkIHNvdXJjZSBhbmQgYnVpbGQgZXZpZGVuY2VdCiAgICBEIC0tPnxOb3wgVFtSZXByb2R1Y2Ugb3IgaW5zcGVjdCBhcnRpZmFjdF0KICAgIFQgLS0-IEZbUGF0Y2ggb3IgZm9jdXNlZCBpc3N1ZV0KICAgIEYgLS0-IFZbUnVuIGFmZmVjdGVkIHRlc3RzPGJyLz5hbmQgaW5zcGVjdCBvdXRwdXRd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBOW09mZmljaWFsIEFwcGxlIG9yIEdvb2dsZSBub3RpY2VdIC0tPiBSW0V4dHJhY3QgcmVxdWlyZW1lbnQ8YnIvPnNjb3BlIGFuZCBkYXRlXQogICAgUiAtLT4gUFtUcmFjZSBDTjEgQVBJPGJyLz5idWlsZGVyIG9yIGdlbmVyYXRlZCB0YXJnZXRdCiAgICBQIC0tPiBEe0FscmVhZHkgaGFuZGxlZD99CiAgICBEIC0tPnxZZXN8IEVbUmVjb3JkIHNvdXJjZSBhbmQgYnVpbGQgZXZpZGVuY2VdCiAgICBEIC0tPnxOb3wgVFtSZXByb2R1Y2Ugb3IgaW5zcGVjdCBhcnRpZmFjdF0KICAgIFQgLS0-IEZbUGF0Y2ggb3IgZm9jdXNlZCBpc3N1ZV0KICAgIEYgLS0-IFZbUnVuIGFmZmVjdGVkIHRlc3RzPGJyLz5hbmQgaW5zcGVjdCBvdXRwdXRd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="586" height="963"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We require a primary source, an applicability test, and a concrete Codename One producer before filing work. A release-note headline is not enough. For native libraries, a Gradle setting is not enough either; the packaged binary may need inspection.&lt;/p&gt;

&lt;p&gt;The scheduled task does not merge code or declare support from a search result. It narrows the distance between a platform announcement and an evidence-backed change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 16 changed the Back contract
&lt;/h2&gt;

&lt;p&gt;Applications targeting Android 16 no longer receive the old &lt;code&gt;onBackPressed()&lt;/code&gt; path in the situations Codename One depended on. Without a registered predictive-back callback, Back could leave the application instead of navigating its form stack.&lt;/p&gt;

&lt;p&gt;Google's &lt;a href="https://developer.android.com/google/play/requirements/target-sdk" rel="noopener noreferrer"&gt;target API requirements&lt;/a&gt; moved application updates to Android 16. The fix registers an &lt;code&gt;OnBackInvokedCallback&lt;/code&gt; bridge and routes it into the existing Codename One back command. The Android builder still compiles against an older SDK surface in parts of the pipeline, so the bridge uses reflection and a proxy rather than linking the newer class directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System Back gesture
        |
OnBackInvokedCallback
        |
PredictiveBackBridge
        |
Codename One back command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input methods can emit both a legacy key event and the new callback. The bridge suppresses the duplicate so one gesture does not navigate twice. Predictive progress animation is not part of this change; correctness came first.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of regression that can hide until a target-SDK deadline changes which platform path is active. The daily scan connected the policy date to the generated Android activity and its runtime behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contact policy became an API design task
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/googleplay/android-developer/answer/16909972" rel="noopener noreferrer"&gt;contacts permission policy&lt;/a&gt; is not solved by changing one manifest line. Many applications ask for an address-book permission only to let the user choose one person. The appropriate response is a narrower interaction, not a more elaborate permission dialog.&lt;/p&gt;

&lt;p&gt;That led to &lt;code&gt;ContactPicker&lt;/code&gt;, which uses the Android system picker on Android 17, a permission-free single-contact fallback on older Android versions, and &lt;code&gt;CNContactPickerViewController&lt;/code&gt; on iOS. The picker returns the selected snapshot. It does not grant the application a standing right to query the address book.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;next article&lt;/a&gt; covers requested fields, multi-selection limits, snapshots, fallbacks, and builder permission detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for release work
&lt;/h2&gt;

&lt;p&gt;The immediate changes matter. The larger gain is a repeatable process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read Apple and Google primary sources every day.&lt;/li&gt;
&lt;li&gt;Extract a date, affected target, and technical condition.&lt;/li&gt;
&lt;li&gt;Search issues and merged code before creating work.&lt;/li&gt;
&lt;li&gt;Trace the actual producer, including build-server generators and packaged artifacts.&lt;/li&gt;
&lt;li&gt;Reproduce the gap or prove the current output already satisfies it.&lt;/li&gt;
&lt;li&gt;Patch and test the narrow affected path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The developer guide overhaul in this release follows the same idea. Structure, cross-references, missing code listings, links, images, and prose now have CI gates. A problem found by automation becomes a specific repair instead of a vague promise to keep the docs current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ahead of the deadline, with evidence
&lt;/h2&gt;

&lt;p&gt;Automation will miss notices, misread scope, and find changes that do not apply. That is why the result is a source trail rather than an autonomous verdict. We can inspect the notice, the relevant builder, the generated product, the tests, and the merged fix.&lt;/p&gt;

&lt;p&gt;Calls, VPN, OTP, contacts, billing, and platform navigation all sit near permissions or operating-system policy. Finding changes earlier gives us time to choose the narrower API instead of making a deadline patch that widens access. It also moves security work into the normal release cycle, where it can be reviewed beside the code it protects.&lt;/p&gt;

&lt;p&gt;The next article shows how to &lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;pick one contact without asking for the address book&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Fill an SMS Verification Code Without Reading the Inbox</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:47:29 +0000</pubDate>
      <link>https://dev.to/codenameone/fill-an-sms-verification-code-without-reading-the-inbox-926</link>
      <guid>https://dev.to/codenameone/fill-an-sms-verification-code-without-reading-the-inbox-926</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbru3r0pvp80r16ry590c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbru3r0pvp80r16ry590c.jpg" alt="A phone verification code flowing from an SMS into an OTP field" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A six-digit text field should not need access to every message on a phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5642" rel="noopener noreferrer"&gt;PR #5642&lt;/a&gt; adds one-time-code autofill, a country-aware phone-number field, and a complete verification component. iOS, Android, and the browser can offer the code through their protected autofill path. The application never receives the rest of the inbox.&lt;/p&gt;

&lt;p&gt;For the rest of this release, including native call management, VPN, AppKit, windows, and contacts, read the &lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;weekly overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete verification flow
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PhoneVerification&lt;/code&gt; combines phone entry, code entry, resend timing, and the two server calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PhoneVerification&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PhoneVerification&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCodeSender&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCodeVerifier&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVerifiedListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;showAccountHome&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callbacks are asynchronous. The component owns the screen state while the application owns the network and account policy. That split is important: Codename One cannot decide how long a code lives or whether a verified phone number should create a session.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBVc2VyCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgU2VydmVyCiAgICBwYXJ0aWNpcGFudCBPUyBhcyBPUyBhdXRvZmlsbAogICAgVXNlci0-PkFwcDogRW50ZXIgcGhvbmUgbnVtYmVyCiAgICBBcHAtPj5TZXJ2ZXI6IFJlcXVlc3QgY29kZQogICAgU2VydmVyLS0-PlVzZXI6IFNlbmQgU01TCiAgICBPUy0tPj5BcHA6IE9mZmVyIG9ubHkgdGhlIGRldGVjdGVkIGNvZGUKICAgIFVzZXItPj5BcHA6IEFjY2VwdCBzdWdnZXN0aW9uCiAgICBBcHAtPj5TZXJ2ZXI6IFZlcmlmeSBwaG9uZSBhbmQgY29kZQogICAgU2VydmVyLS0-PkFwcDogVmVyaWZpY2F0aW9uIHJlc3VsdCBvciBzZXNzaW9u%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBVc2VyCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgU2VydmVyCiAgICBwYXJ0aWNpcGFudCBPUyBhcyBPUyBhdXRvZmlsbAogICAgVXNlci0-PkFwcDogRW50ZXIgcGhvbmUgbnVtYmVyCiAgICBBcHAtPj5TZXJ2ZXI6IFJlcXVlc3QgY29kZQogICAgU2VydmVyLS0-PlVzZXI6IFNlbmQgU01TCiAgICBPUy0tPj5BcHA6IE9mZmVyIG9ubHkgdGhlIGRldGVjdGVkIGNvZGUKICAgIFVzZXItPj5BcHA6IEFjY2VwdCBzdWdnZXN0aW9uCiAgICBBcHAtPj5TZXJ2ZXI6IFZlcmlmeSBwaG9uZSBhbmQgY29kZQogICAgU2VydmVyLS0-PkFwcDogVmVyaWZpY2F0aW9uIHJlc3VsdCBvciBzZXNzaW9u%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="904" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server should rate-limit requests by account, number, device, and network; expire codes; cap attempts; and issue a session only after successful verification. The endpoint must never return the code in its response. Autofill improves entry. It does not provide the security policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add autofill to an existing form
&lt;/h2&gt;

&lt;p&gt;Applications with their own flow can use the new input constraint directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Verification code"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;TextArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NUMERIC&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TextArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ONE_TIME_CODE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On iOS this maps to the one-time-code content type. Android exposes the appropriate autofill hint. The JavaScript port emits the browser's &lt;code&gt;one-time-code&lt;/code&gt; autocomplete value. Desktop ignores the constraint.&lt;/p&gt;

&lt;p&gt;There is no SMS permission, entitlement, native library, or build hint. An application that uses the field gets metadata on the editor it already has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six boxes, one editor
&lt;/h2&gt;

&lt;p&gt;The visual OTP component still presents separate boxes, but they no longer contain six independent native editors. One editor owns the entire value while the boxes draw its characters.&lt;/p&gt;

&lt;p&gt;The old design set a maximum size of one on each box. When the operating system supplied all six digits to the focused editor, the native peer truncated the value after the first digit. Paste had the same basic problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBTW1NNUyBzdWdnZXN0aW9uPGJyLz5zaXggZGlnaXRzXSAtLT4gRVtPbmUgaGlkZGVuIGVkaXRvcjxici8-b3ducyBjb21wbGV0ZSB2YWx1ZV0KICAgIEUgLS0-IEIxWzFdCiAgICBFIC0tPiBCMlsyXQogICAgRSAtLT4gQjNbM10KICAgIEUgLS0-IEI0WzRdCiAgICBFIC0tPiBCNVs1XQogICAgRSAtLT4gQjZbNl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBTW1NNUyBzdWdnZXN0aW9uPGJyLz5zaXggZGlnaXRzXSAtLT4gRVtPbmUgaGlkZGVuIGVkaXRvcjxici8-b3ducyBjb21wbGV0ZSB2YWx1ZV0KICAgIEUgLS0-IEIxWzFdCiAgICBFIC0tPiBCMlsyXQogICAgRSAtLT4gQjNbM10KICAgIEUgLS0-IEI0WzRdCiAgICBFIC0tPiBCNVs1XQogICAgRSAtLT4gQjZbNl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="571" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This changes one obscure customization path. Code should read and write the &lt;code&gt;OtpField&lt;/code&gt; value through the field API. Calling &lt;code&gt;getBox(i).setText()&lt;/code&gt; no longer changes the code because the boxes are presentation, not storage.&lt;/p&gt;

&lt;p&gt;That trade makes typing, deletion, selection, paste, accessibility, and autofill behave as one input instead of a relay race between six editors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Less access is the better integration
&lt;/h2&gt;

&lt;p&gt;Some older Android OTP examples ask for SMS access and scan the inbox. That makes a small convenience feature responsible for one of the most sensitive data sets on the device. Protected autofill gives the application the value the user intends to enter and nothing else.&lt;/p&gt;

&lt;p&gt;The same rule appears throughout this release. The &lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;Contact Picker&lt;/a&gt; returns chosen fields without exposing the address book. Call and VPN packages trigger only the native services they use. The builders turn an API reference into exact platform metadata rather than accumulating permissions in a generic application template.&lt;/p&gt;

&lt;p&gt;Secure-by-default development often looks like subtraction: no inbox reader, no copied SMS parser, no permission explanation screen, and no background component holding more data than the feature needs.&lt;/p&gt;

&lt;p&gt;Next, read how the &lt;a href="https://www.codenameone.com/blog/platform-deprecation-watch/" rel="noopener noreferrer"&gt;daily platform watch&lt;/a&gt; found framework gaps before users reported them.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>A Mac App, Not an iOS App on a Mac</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sun, 13 Sep 2026 16:57:43 +0000</pubDate>
      <link>https://dev.to/codenameone/a-mac-app-not-an-ios-app-on-a-mac-2jce</link>
      <guid>https://dev.to/codenameone/a-mac-app-not-an-ios-app-on-a-mac-2jce</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv2cx3sic1sx8za3vn0mi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv2cx3sic1sx8za3vn0mi.jpg" alt="A Codename One application running as a native AppKit Mac app" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mac Catalyst runs an iPad application on macOS. That was a useful bridge for Codename One, but it remained a bridge. Once we added multiple desktop windows, the missing desktop operations and rendering compromises stopped being edge cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5601" rel="noopener noreferrer"&gt;PR #5601&lt;/a&gt; changes the default native Mac build to AppKit. The result is an &lt;code&gt;NSApplication&lt;/code&gt; with &lt;code&gt;NSWindow&lt;/code&gt;, &lt;code&gt;NSMenu&lt;/code&gt;, &lt;code&gt;NSTextInputClient&lt;/code&gt;, &lt;code&gt;NSScreen&lt;/code&gt;, and a &lt;code&gt;CAMetalLayer&lt;/code&gt; for each rendered window. It is a Mac application rather than an iOS application asking macOS to translate the experience.&lt;/p&gt;

&lt;p&gt;For the complete release, including calls, VPN, OTP, contacts, and Android billing, read the &lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;weekly overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catalyst was doing expensive work
&lt;/h2&gt;

&lt;p&gt;The Catalyst port rendered a secondary Codename One window into a mutable image and then copied that image into the scene. A 4K BGRA surface is roughly 33 MB before another buffer or copy enters the picture. Moving or repainting several windows multiplies that cost.&lt;/p&gt;

&lt;p&gt;AppKit gives each &lt;code&gt;Window&lt;/code&gt; its own Metal-backed drawable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBDYXRhbHlzdAogICAgICAgIEMxW0NvZGVuYW1lIE9uZSBwYWludF0gLS0-IEJbTXV0YWJsZSBpbWFnZSBidWZmZXJdCiAgICAgICAgQiAtLT4gVVtVSUtpdCBzY2VuZSBjb3B5XQogICAgZW5kCiAgICBzdWJncmFwaCBBcHBLaXQKICAgICAgICBBMVtDb2RlbmFtZSBPbmUgcGFpbnRdIC0tPiBNW1Blci13aW5kb3cgQ0FNZXRhbExheWVyXQogICAgICAgIE0gLS0-IE5bTlNXaW5kb3cgcHJlc2VudHNdCiAgICBlbmQ%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBDYXRhbHlzdAogICAgICAgIEMxW0NvZGVuYW1lIE9uZSBwYWludF0gLS0-IEJbTXV0YWJsZSBpbWFnZSBidWZmZXJdCiAgICAgICAgQiAtLT4gVVtVSUtpdCBzY2VuZSBjb3B5XQogICAgZW5kCiAgICBzdWJncmFwaCBBcHBLaXQKICAgICAgICBBMVtDb2RlbmFtZSBPbmUgcGFpbnRdIC0tPiBNW1Blci13aW5kb3cgQ0FNZXRhbExheWVyXQogICAgICAgIE0gLS0-IE5bTlNXaW5kb3cgcHJlc2VudHNdCiAgICBlbmQ%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="335" height="840"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dirty regions stay attached to one native window. A repaint in an inspector does not need an intermediate image the size of the editor window. Monitor scale comes from the &lt;code&gt;NSScreen&lt;/code&gt; that owns the window, not from an iOS scene approximation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw4lfans65a5ljxad2tlz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw4lfans65a5ljxad2tlz.png" alt="Codename One components inside a native AppKit secondary window" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A Codename One component tree rendered into an AppKit window at 900 by 700.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop operations are desktop operations again
&lt;/h2&gt;

&lt;p&gt;The AppKit port implements always-on-top, utility windows, minimize, restore, maximize, modality, undecorated windows, and native dirty clipping. Menus use &lt;code&gt;NSMenu&lt;/code&gt;. Text editing implements &lt;code&gt;NSTextInputClient&lt;/code&gt;, including composition and input-method interaction. Multiple monitors come through &lt;code&gt;NSScreen&lt;/code&gt; with their independent work areas and scale factors.&lt;/p&gt;

&lt;p&gt;Those details matter to applications such as editors, trading terminals, control rooms, and internal tools. A utility palette that cannot remain above its owner is not a utility palette. A text editor that bypasses the input method works only for the developer who tested it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/dialogs-in-native-windows/" rel="noopener noreferrer"&gt;dialog work&lt;/a&gt; builds on this port. A dialog may stay in its owner's layered pane or become a native modal window. Both paths now have a real Mac window model underneath them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing build targets, different native foundation
&lt;/h2&gt;

&lt;p&gt;The normal targets keep their names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.platform&lt;span class="o"&gt;=&lt;/span&gt;ios &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.buildTarget&lt;span class="o"&gt;=&lt;/span&gt;mac-source &lt;span class="se"&gt;\&lt;/span&gt;
  package

mvn &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.platform&lt;span class="o"&gt;=&lt;/span&gt;ios &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.buildTarget&lt;span class="o"&gt;=&lt;/span&gt;mac-os-x-native &lt;span class="se"&gt;\&lt;/span&gt;
  package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mac-source&lt;/code&gt; produces an Xcode project under the build target directory. &lt;code&gt;mac-os-x-native&lt;/code&gt; produces the signed native result through the build service. Existing projects do not need a new application API.&lt;/p&gt;

&lt;p&gt;Catalyst has no separate build target. An application that depends on it can preserve the previous behavior by setting the existing hint and building an ordinary iOS target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.arg.macNative.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;ios-source&lt;/code&gt; for a local Xcode project or &lt;code&gt;ios-device&lt;/code&gt; for a build-server build. Catalyst is no longer the default definition of “native Mac.”&lt;/p&gt;

&lt;h2&gt;
  
  
  A port is a contract, not a screenshot
&lt;/h2&gt;

&lt;p&gt;The pull request ran the full native screenshot suite plus platform and input tests. The same window layouts are captured at several aspect ratios. Text input, mouse routing, focus, resizing, modality, and screen scale have dedicated coverage.&lt;/p&gt;

&lt;p&gt;Accessibility follows the window model too. &lt;code&gt;IOSNative.updateAccessibilityTree()&lt;/code&gt; sends the framework semantics tree to &lt;code&gt;CN1MacAccessibilityUpdateTree()&lt;/code&gt;, which installs a hierarchy of &lt;code&gt;NSAccessibilityElement&lt;/code&gt; children on the relevant window. Roles, labels, values, states, standard and custom actions, and live-region announcements reach VoiceOver through AppKit instead of stopping at the portable tree.&lt;/p&gt;

&lt;p&gt;Platform services with a UIKit-only implementation still need individual AppKit work. Switching the default port does not make those integrations native to macOS automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Tuesday
&lt;/h2&gt;

&lt;p&gt;A new Codename One platform port once deserved an entire release cycle and its own launch. This week the AppKit port arrived beside VoIP, VPN, phone verification, private contact selection, native dialogs, billing, browser density, and a documentation overhaul. A new native platform has become another Tuesday.&lt;/p&gt;

&lt;p&gt;The common component model and builder contract carry most of the application unchanged. The Mac port owns native windows, input, menus, text, and rendering. It does not require a second application UI or a Mac-specific navigation architecture.&lt;/p&gt;

&lt;p&gt;The same division strengthens secure defaults. Native platform integration belongs in a reviewed port and build pipeline, not in copied project fragments that drift across application repositories. Applications keep their own interface and data model. The builder supplies the narrow native product needed to run them.&lt;/p&gt;

&lt;p&gt;Next, the &lt;a href="https://www.codenameone.com/blog/sms-otp-autofill/" rel="noopener noreferrer"&gt;OTP article&lt;/a&gt; removes an inbox permission from a common verification flow.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>A Dialog Can Now Be a Native Desktop Window</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 12 Sep 2026 16:17:44 +0000</pubDate>
      <link>https://dev.to/codenameone/a-dialog-can-now-be-a-native-desktop-window-1jgk</link>
      <guid>https://dev.to/codenameone/a-dialog-can-now-be-a-native-desktop-window-1jgk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyrjfafspm26et7jryon.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyrjfafspm26et7jryon.jpg" alt="A modal dialog above a native desktop window" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week's &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;native-window release&lt;/a&gt; could open an editor, inspector, and tool palette as separate operating-system windows. Then an ordinary &lt;code&gt;Dialog.show()&lt;/code&gt; inside the inspector looked for the current &lt;code&gt;Form&lt;/code&gt; and appeared on the wrong surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That bug exposed every component that treated “top level” and “form” as synonyms. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5624" rel="noopener noreferrer"&gt;PR #5624&lt;/a&gt; fixes those assumptions and adds something more visible: a &lt;code&gt;Dialog&lt;/code&gt; can now become a real modal desktop window.&lt;/p&gt;

&lt;p&gt;For VoIP, VPN, the AppKit port, OTP, contacts, and the rest of this release, read the &lt;a href="https://www.codenameone.com/blog/voip-vpn-builders/" rel="noopener noreferrer"&gt;weekly overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two useful kinds of dialog
&lt;/h2&gt;

&lt;p&gt;The default remains a lightweight dialog painted inside its owner's layered pane. It matches Codename One styling and works on every port. A desktop application can make ordinary confirmation dialogs native windows while keeping the familiar static API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultNativeWindowMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;show&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Confirm deletion"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Delete Quarterly report.pdf?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Delete"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Cancel"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;deleteDocument&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdmyae6fh7azkfpmp6qd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdmyae6fh7azkfpmp6qd.png" alt="A themed Codename One confirmation dialog in its own macOS window" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An actual JavaSE desktop capture on macOS. The title bar belongs to a separate operating-system window; the body and actions use the application's Codename One theme.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;defaultNativeWindowModeBool&lt;/code&gt; theme constant provides the same application-wide setting without a startup call. For a single custom dialog, call &lt;code&gt;setNativeWindowMode(true)&lt;/code&gt; on that instance. The instance setting wins over the static default, which wins over the theme. On a port without native windows, the dialog stays lightweight. There is no second code path to maintain.&lt;/p&gt;

&lt;p&gt;Native-window mode is useful when the dialog must participate in desktop window ordering, focus, or task switching. Lightweight mode is usually better for a small prompt that should inherit the exact visual treatment of its owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership was the real bug
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Dialog&lt;/code&gt;, &lt;code&gt;Sheet&lt;/code&gt;, &lt;code&gt;ToastBar&lt;/code&gt;, &lt;code&gt;ComboBox&lt;/code&gt;, &lt;code&gt;FloatingActionButton&lt;/code&gt;, &lt;code&gt;InfiniteProgress&lt;/code&gt;, tooltips, and &lt;code&gt;HTMLComponent&lt;/code&gt; all had paths that asked &lt;code&gt;Display&lt;/code&gt; for the current form. That question has one answer, even when the event came from another window.&lt;/p&gt;

&lt;p&gt;The corrected path starts with the component that caused the action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBFW0V2ZW50IHNvdXJjZSBjb21wb25lbnRdIC0tPiBUW2dldFRvcExldmVsQ29udGFpbmVyXQogICAgVCAtLT4gRltGb3JtIGxheWVyZWQgcGFuZV0KICAgIFQgLS0-IFdbV2luZG93IGxheWVyZWQgcGFuZV0KICAgIEYgLS0-IE9bT3ZlcmxheSBvciBwb3B1cF0KICAgIFcgLS0-IE8KICAgIFcgLS0-IE5bT3B0aW9uYWwgbmF0aXZlIGRpYWxvZyB3aW5kb3dd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBFW0V2ZW50IHNvdXJjZSBjb21wb25lbnRdIC0tPiBUW2dldFRvcExldmVsQ29udGFpbmVyXQogICAgVCAtLT4gRltGb3JtIGxheWVyZWQgcGFuZV0KICAgIFQgLS0-IFdbV2luZG93IGxheWVyZWQgcGFuZV0KICAgIEYgLS0-IE9bT3ZlcmxheSBvciBwb3B1cF0KICAgIFcgLS0-IE8KICAgIFcgLS0-IE5bT3B0aW9uYWwgbmF0aXZlIGRpYWxvZyB3aW5kb3dd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="526" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code that creates a dialog without a source component can bind it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Details"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTopLevelHost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inspectorWindow&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Selection metadata"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;show&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dimming layer, popup position, focus restoration, repaint region, and input routing now belong to that window. Accessibility state is also maintained per top level, so opening an inspector does not replace the main form's accessible root.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popups should not become surprise windows
&lt;/h2&gt;

&lt;p&gt;An anchored combo-box popup stays attached to its field. A floating-action submenu stays near its button. Turning either into an independent desktop window would break positioning and keyboard behavior, so the new native mode is limited to dialogs and interaction dialogs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Picker&lt;/code&gt; inside a window still uses its lightweight popup. &lt;code&gt;Toolbar&lt;/code&gt; remains a &lt;code&gt;Form&lt;/code&gt; concept. A transition cannot animate one operating-system window into another because the two surfaces do not share a graphics context. Change content inside one window when an animated transition matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;inspectorWindow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextPanel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CommonTransitions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createFade&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those limits are explicit because a fake cross-window animation would be less predictable than no animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same contract on Windows and Mac
&lt;/h2&gt;

&lt;p&gt;The test suite opens the same controls in 400 by 300, 900 by 700, and 1000 by 400 windows. The wide case catches code that still reads the main display width. Modal cases verify that the owner is blocked while other event-dispatch work can continue.&lt;/p&gt;

&lt;p&gt;This also validates the new &lt;a href="https://www.codenameone.com/blog/native-appkit-mac-port/" rel="noopener noreferrer"&gt;AppKit port&lt;/a&gt; against the existing Windows implementation. A platform port is much easier to trust when it must pass behavior captured by another native port instead of defining success for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop work without a desktop fork
&lt;/h2&gt;

&lt;p&gt;The Window API is settling into the same pattern as this week's call, VPN, and contact work. The common API owns the behavior application code can rely on. Each port handles the native surface it actually has. Unsupported behavior stays visible instead of becoming a silent approximation.&lt;/p&gt;

&lt;p&gt;That consistency matters for security as well as polish. A confirmation must block the window that contains the sensitive operation. A popup must not leak input to another surface. Accessibility focus must remain attached to the content the user is operating. These details are small until the wrong window accepts a command.&lt;/p&gt;

&lt;p&gt;Next, read how &lt;a href="https://www.codenameone.com/blog/native-appkit-mac-port/" rel="noopener noreferrer"&gt;Catalyst gave way to a real AppKit application&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>VoIP, VPN, and the Build System Behind Them</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 11 Sep 2026 17:01:39 +0000</pubDate>
      <link>https://dev.to/codenameone/voip-vpn-and-the-build-system-behind-them-58j3</link>
      <guid>https://dev.to/codenameone/voip-vpn-and-the-build-system-behind-them-58j3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filgcydozj71n8eoje8e9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filgcydozj71n8eoje8e9.jpg" alt="A phone, secure tunnel, and native build pipeline" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2019, a reader asked whether our WhatsApp clone included VoIP. &lt;a href="https://www.codenameone.com/blog/whatsapp-clone-ga/#comment-24081" rel="noopener noreferrer"&gt;I said no&lt;/a&gt;. It would require native integration, and the setup was too much for that project. For years, that was the line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;VPN sat behind the same line. These features do not live neatly inside the application process. On iOS, a VoIP push can arrive before the application installs a Java listener, yet CallKit expects the incoming call to reach the lock screen on its deadline. A Call Directory integration is a separate extension executable with its own lifecycle and signing identity. Managed VPN adds Network Extension entitlements and profile rules. Android solves the same jobs through &lt;code&gt;ConnectionService&lt;/code&gt;, &lt;code&gt;VpnManager&lt;/code&gt;, &lt;code&gt;VpnService&lt;/code&gt;, manifest declarations, and foreground-service rules.&lt;/p&gt;

&lt;p&gt;Wrapping those APIs in Java is the small part. A cross-platform runtime cannot add a service before installation, answer a native callback before the application is ready, or create and sign another executable inside the application bundle. Once every team must maintain those pieces in Xcode and Android Studio, the shared abstraction has stopped at the hardest part.&lt;/p&gt;

&lt;p&gt;Codename One has a secret weapon for that problem: builders.&lt;/p&gt;

&lt;p&gt;Builders already assemble the native product, so they can change its shape based on the Java packages the application actually uses. They link frameworks, inject permissions and background modes, generate native delegates and services, and create extension targets when needed. Leave a package out and its native machinery stays out too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5604" rel="noopener noreferrer"&gt;PR #5604&lt;/a&gt; is where we crossed that old line. It introduced first-class call management, VoIP push handling, managed VPN, and the packet-tunnel API and Android host. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5693" rel="noopener noreferrer"&gt;PR #5693&lt;/a&gt; completes the iOS path by generating a separately translated and signed Network Extension. The Java API is the visible result. The bigger change is that builders assemble the system integration around it without turning every application team into the maintainer of two native build projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Calls&lt;/code&gt; connects application call state to CallKit on iOS and self-managed &lt;code&gt;ConnectionService&lt;/code&gt; on Android. Your media and signaling stack remain yours.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vpn&lt;/code&gt; installs and controls managed IKEv2 profiles on iOS and Android. iOS also supports managed IPsec profiles.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VpnTunnel&lt;/code&gt; exposes raw packets for an application-implemented tunnel on Android and iOS. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5693" rel="noopener noreferrer"&gt;The iOS builder work&lt;/a&gt; generates and signs a separate Network Extension containing the translated Java tunnel.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codenameone.com/blog/dialogs-in-native-windows/" rel="noopener noreferrer"&gt;Dialogs and secondary windows&lt;/a&gt; now work together, including an opt-in native modal window.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codenameone.com/blog/native-appkit-mac-port/" rel="noopener noreferrer"&gt;The new AppKit port&lt;/a&gt; builds a real Mac application instead of presenting an iOS application through Catalyst.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codenameone.com/blog/sms-otp-autofill/" rel="noopener noreferrer"&gt;SMS one-time-code autofill&lt;/a&gt; fills a verification code without asking to read the inbox.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codenameone.com/blog/platform-deprecation-watch/" rel="noopener noreferrer"&gt;A daily platform watch&lt;/a&gt; now turns Apple and Google notices into source-level checks before deadlines become fires.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;The new Contact Picker&lt;/a&gt; returns selected contact data without broad address-book access.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A call is a system session before it is a media session
&lt;/h2&gt;

&lt;p&gt;A VoIP application has two timelines. Its signaling server and media engine have one. The operating system has another. On iOS, an incoming VoIP push must be reported to CallKit immediately. The Java application may not be running yet. On Android, a self-managed connection participates in system call routing and audio focus even when the application's own screen is elsewhere.&lt;/p&gt;

&lt;p&gt;The new API keeps those responsibilities separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="nc"&gt;Calls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;Calls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CallConfiguration&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Acme Talk"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;Calls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addActionListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CallActionAdapter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;answerRequested&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;callId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CallAction&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;signalling&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;audioSessionActivated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CallAudioSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCallId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;providerReset&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stopEverything&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Calls&lt;/code&gt; does not choose a codec, move audio packets, or invent a signaling protocol. It owns the bridge to the system call UI and lifecycle. The application starts media only after the native audio session activates, not merely because the user tapped Answer.&lt;/p&gt;

&lt;p&gt;The package boundary also carries meaning. &lt;code&gt;com.codename1.call.session&lt;/code&gt; adds the core system call integration. &lt;code&gt;com.codename1.call.voip&lt;/code&gt; adds PushKit and the iOS VoIP background mode. &lt;code&gt;com.codename1.call.directory&lt;/code&gt; adds directory integration without silently pulling in the self-managed calling permission. Apple can reject an application that declares VoIP background execution without using it, so importing less code must build less native machinery.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBQdXNoIGFzIFB1c2ggc2VydmljZQogICAgcGFydGljaXBhbnQgTmF0aXZlIGFzIE5hdGl2ZSBjYWxsIGJyaWRnZQogICAgcGFydGljaXBhbnQgT1MgYXMgU3lzdGVtIGNhbGwgVUkKICAgIHBhcnRpY2lwYW50IEphdmEgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgTWVkaWEgYXMgQXBwIG1lZGlhIGVuZ2luZQogICAgUHVzaC0-Pk5hdGl2ZTogSW5jb21pbmcgY2FsbCBwYXlsb2FkCiAgICBOYXRpdmUtPj5PUzogUmVwb3J0IGNhbGwgYmVmb3JlIGRlYWRsaW5lCiAgICBPUy0-PkphdmE6IGFuc3dlclJlcXVlc3RlZChjYWxsSWQpCiAgICBKYXZhLT4-SmF2YTogQWNjZXB0IHRocm91Z2ggc2lnbmFsaW5nIHNlcnZlcgogICAgT1MtPj5KYXZhOiBhdWRpb1Nlc3Npb25BY3RpdmF0ZWQoY2FsbElkKQogICAgSmF2YS0-Pk1lZGlhOiBTdGFydCBtZWRpYQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBQdXNoIGFzIFB1c2ggc2VydmljZQogICAgcGFydGljaXBhbnQgTmF0aXZlIGFzIE5hdGl2ZSBjYWxsIGJyaWRnZQogICAgcGFydGljaXBhbnQgT1MgYXMgU3lzdGVtIGNhbGwgVUkKICAgIHBhcnRpY2lwYW50IEphdmEgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgTWVkaWEgYXMgQXBwIG1lZGlhIGVuZ2luZQogICAgUHVzaC0-Pk5hdGl2ZTogSW5jb21pbmcgY2FsbCBwYXlsb2FkCiAgICBOYXRpdmUtPj5PUzogUmVwb3J0IGNhbGwgYmVmb3JlIGRlYWRsaW5lCiAgICBPUy0-PkphdmE6IGFuc3dlclJlcXVlc3RlZChjYWxsSWQpCiAgICBKYXZhLT4-SmF2YTogQWNjZXB0IHRocm91Z2ggc2lnbmFsaW5nIHNlcnZlcgogICAgT1MtPj5KYXZhOiBhdWRpb1Nlc3Npb25BY3RpdmF0ZWQoY2FsbElkKQogICAgSmF2YS0-Pk1lZGlhOiBTdGFydCBtZWRpYQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1171" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simulator supplies deterministic call and audio-session events. That makes the awkward orderings testable before a build reaches a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed VPN with an important platform boundary
&lt;/h2&gt;

&lt;p&gt;Many enterprise applications do not implement a VPN protocol. They install an operating-system profile and ask the platform to connect it. That path now has one Java API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="nc"&gt;Vpn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;VpnProfile&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VpnProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vpn.example.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;VpnProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IKEV2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remoteIdentifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vpn.example.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;localIdentifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernamePassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Acme Corporate"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Vpn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Vpn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iOS and Android 11 or newer support managed IKEv2. iOS also supports managed IPsec with a pre-shared key. The operating system owns user consent, credential storage, and the active configuration. The API exposes those boundaries rather than storing a VPN password in application preferences.&lt;/p&gt;

&lt;h3&gt;
  
  
  The packet tunnel boundary
&lt;/h3&gt;

&lt;p&gt;For work that does not fit a managed VPN profile, &lt;code&gt;VpnTunnel&lt;/code&gt; lets application code inspect raw IP packets and decide whether to forward or drop them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalTunnel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;VpnTunnel&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LocalTunnel&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onStart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TunnelConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onPacket&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PacketBuffer&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFamily&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;PacketBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FAMILY_IPV4&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;forward&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onStop&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TunnelStopReason&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same Java packet loop runs on Android and iOS, but the process boundary is different. Android hosts it in a &lt;code&gt;VpnService&lt;/code&gt; inside the application process. iOS requires a &lt;code&gt;NEPacketTunnelProvider&lt;/code&gt; Network Extension: a separate executable with its own bundle identifier, provisioning profile, memory limit, and translated VM. The builder work in &lt;a href="https://github.com/codenameone/CodenameOne/pull/5693" rel="noopener noreferrer"&gt;PR #5693&lt;/a&gt; creates that target, translates from the selected &lt;code&gt;VpnTunnel&lt;/code&gt; class instead of the UIKit application shell, adds the framework and entitlements, and embeds the signed extension in the application.&lt;/p&gt;

&lt;p&gt;The iOS extension contains the translated tunnel and VM, but not the Codename One networking stack. It can inspect, rewrite, drop, and &lt;code&gt;forward&lt;/code&gt; packets on the device. It cannot open a remote connection through &lt;code&gt;com.codename1.io.Socket&lt;/code&gt;, &lt;code&gt;ConnectionRequest&lt;/code&gt;, or ParparVM's &lt;code&gt;java.net&lt;/code&gt;. Android can relay packets because its tunnel runs in the application process. A protocol that needs a remote relay should put that transport behind a platform-specific interface instead of assuming the two hosts are identical.&lt;/p&gt;

&lt;p&gt;Apple grants the Network Extension entitlement case by case, so the target is deliberately opt-in. Once the App ID has that entitlement, name the tunnel class in &lt;code&gt;codenameone_settings.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.arg.ios.vpn.tunnel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;codename1.arg.ios.vpn.tunnel.class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;com.acme.LocalTunnel&lt;/span&gt;
&lt;span class="py"&gt;codename1.arg.ios.appext.CN1VpnTunnel.provisioningURL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://example.com/CN1VpnTunnel.mobileprovision&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the host application and &lt;code&gt;&amp;lt;packageName&amp;gt;.vpntunnel&lt;/code&gt; App IDs need Apple's Network Extension entitlement. The extension also needs its own provisioning profile. It starts fresh, requires an accessible no-argument constructor, and cannot see application statics or &lt;code&gt;Display&lt;/code&gt;. Pass local configuration through &lt;code&gt;TunnelSetup.data&lt;/code&gt; and read it in &lt;code&gt;VpnTunnel.onStart()&lt;/code&gt;. This is a real cross-platform API without pretending the two operating systems have the same lifecycle or networking facilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The builder is part of the runtime contract
&lt;/h2&gt;

&lt;p&gt;A browser application cannot register CallKit, a self-managed Android connection service, or an Apple Network Extension. Flutter, React Native, and .NET MAUI can reach those APIs through native plugins and platform projects. The difficult work then lives in target membership, manifests, entitlements, background modes, native delegates, and signing.&lt;/p&gt;

&lt;p&gt;Builders change where that difficult work lives. The build server already owns the native product graph. It can see which Java packages survive into the application and generate the platform pieces that match them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBKW1JlZmVyZW5jZWQgSmF2YSBwYWNrYWdlc10gLS0-IENbUGxhdGZvcm0gZmVhdHVyZSBjYXRhbG9nXQogICAgQyAtLT4gSVtpT1MgZnJhbWV3b3Jrczxici8-ZGVsZWdhdGVzIGFuZCBtb2Rlc10KICAgIEMgLS0-IEFbQW5kcm9pZCBzZXJ2aWNlczxici8-cGVybWlzc2lvbnMgYW5kIG1hbmlmZXN0XQogICAgQyAtLT4gRVtFeHRlbnNpb24gdGFyZ2V0czxici8-YW5kIHNpZ25pbmcgcHJlZmxpZ2h0XQogICAgQyAtLT4gU1tTaW11bGF0b3IgaW1wbGVtZW50YXRpb25dCiAgICBVW1VudXNlZCBwYWNrYWdlc10gLS0-IE5bTm8gbmF0aXZlIGJhZ2dhZ2Vd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBKW1JlZmVyZW5jZWQgSmF2YSBwYWNrYWdlc10gLS0-IENbUGxhdGZvcm0gZmVhdHVyZSBjYXRhbG9nXQogICAgQyAtLT4gSVtpT1MgZnJhbWV3b3Jrczxici8-ZGVsZWdhdGVzIGFuZCBtb2Rlc10KICAgIEMgLS0-IEFbQW5kcm9pZCBzZXJ2aWNlczxici8-cGVybWlzc2lvbnMgYW5kIG1hbmlmZXN0XQogICAgQyAtLT4gRVtFeHRlbnNpb24gdGFyZ2V0czxici8-YW5kIHNpZ25pbmcgcHJlZmxpZ2h0XQogICAgQyAtLT4gU1tTaW11bGF0b3IgaW1wbGVtZW50YXRpb25dCiAgICBVW1VudXNlZCBwYWNrYWdlc10gLS0-IE5bTm8gbmF0aXZlIGJhZ2dhZ2Vd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="841" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dependency injection cannot do this work. The builder can add a service that must exist before Java starts, compile a native callback that meets an operating-system deadline, and sign an extension as part of the same product. Application teams keep control of their UI, protocol, and business logic without maintaining a parallel native build system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dialog can become a native modal window
&lt;/h2&gt;

&lt;p&gt;Last week's &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;native-window release&lt;/a&gt; named several components that still assumed every top level was a &lt;code&gt;Form&lt;/code&gt;. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5624" rel="noopener noreferrer"&gt;PR #5624&lt;/a&gt; closes most of that list. &lt;code&gt;Dialog&lt;/code&gt;, &lt;code&gt;Sheet&lt;/code&gt;, &lt;code&gt;ToastBar&lt;/code&gt;, combo-box popups, floating-action submenus, progress overlays, tooltips, and &lt;code&gt;HTMLComponent&lt;/code&gt; now resolve the window that contains them. Accessibility state is tracked per window as well.&lt;/p&gt;

&lt;p&gt;A dialog can remain a lightweight overlay inside its owner, or opt into a real operating-system window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultNativeWindowMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dialog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;show&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Confirm deletion"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Delete Quarterly report.pdf?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Delete"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Cancel"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/dialogs-in-native-windows/" rel="noopener noreferrer"&gt;window follow-up&lt;/a&gt; covers precedence, fallback behavior, anchored popups, and the remaining limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Mac application built on AppKit
&lt;/h2&gt;

&lt;p&gt;Mac Catalyst helped us get an iOS application onto macOS, but its desktop behavior remained bounded by UIKit. It could not supply several ordinary window operations, and a secondary 4K surface could require roughly 33 MB for each intermediate frame before the image was copied into place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5601" rel="noopener noreferrer"&gt;PR #5601&lt;/a&gt; replaces the default Mac native path with AppKit. The generated application now owns &lt;code&gt;NSApplication&lt;/code&gt;, &lt;code&gt;NSWindow&lt;/code&gt;, &lt;code&gt;NSMenu&lt;/code&gt;, &lt;code&gt;NSScreen&lt;/code&gt;, and a &lt;code&gt;CAMetalLayer&lt;/code&gt; per window. Always-on-top, utility windows, minimize, restore, maximize, native modality, and independent dirty-region painting are real desktop operations.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/native-appkit-mac-port/" rel="noopener noreferrer"&gt;AppKit article&lt;/a&gt; explains the rendering change, build targets, test evidence, and the native accessibility bridge to VoiceOver.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTP without inbox permission
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5642" rel="noopener noreferrer"&gt;PR #5642&lt;/a&gt; adds a one-time-code constraint plus reusable phone-number and verification components. On iOS, Android, and the web, the operating system can offer the code from an SMS without giving the application permission to read messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PhoneVerification&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PhoneVerification&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCodeSender&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCodeVerifier&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVerifiedListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;showAccount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codename One does not send or verify the SMS. Your server still owns expiry, attempt limits, rate limits, and session issuance. The &lt;a href="https://www.codenameone.com/blog/sms-otp-autofill/" rel="noopener noreferrer"&gt;OTP article&lt;/a&gt; includes both the complete component and the lower-level text-field option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop learning about platform changes from failed builds
&lt;/h2&gt;

&lt;p&gt;Until this week, an Apple or Google change usually reached us through a community report. That is useful, but it starts the clock after somebody is already exposed.&lt;/p&gt;

&lt;p&gt;We now run a daily scheduled Codex task that reads official platform notices, extracts the requirement and date, traces the affected Codename One producer, and checks for an existing issue or fix. It found the Android 16 back-navigation gap repaired in &lt;a href="https://github.com/codenameone/CodenameOne/pull/5673" rel="noopener noreferrer"&gt;PR #5673&lt;/a&gt;, and the privacy direction that led to the Contact Picker in &lt;a href="https://github.com/codenameone/CodenameOne/pull/5680" rel="noopener noreferrer"&gt;PR #5680&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The task does not make a notice true, and it does not replace platform tests. Its job is to connect a primary-source deadline to the exact builder, generated project, or API that must change. The &lt;a href="https://www.codenameone.com/blog/platform-deprecation-watch/" rel="noopener noreferrer"&gt;platform-watch article&lt;/a&gt; shows that chain. Contact selection gets its own &lt;a href="https://www.codenameone.com/blog/private-contact-picker/" rel="noopener noreferrer"&gt;code-focused article&lt;/a&gt; because the permission model deserves more than a paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four smaller changes with a large blast radius
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google Play Billing 8
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5651" rel="noopener noreferrer"&gt;PR #5651&lt;/a&gt; moves the default Android billing dependency to 8.0.0 and completes the move from the retired SKU API to &lt;code&gt;ProductDetails&lt;/code&gt;. Google's &lt;a href="https://developer.android.com/google/play/billing/deprecation-faq" rel="noopener noreferrer"&gt;billing deprecation schedule&lt;/a&gt; now makes version 8 the minimum for updates. The builder absorbs a generated-project migration that most application teams should never have to chase themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript density now means device pixels
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5634" rel="noopener noreferrer"&gt;PR #5634&lt;/a&gt; fixes display width, height, and millimeter conversion when the browser device-pixel ratio is neither one nor two. A viewport that is 390 CSS pixels wide at a ratio of three now reports 1,170 device pixels.&lt;/p&gt;

&lt;p&gt;This can change layout and screenshot results in existing JavaScript applications. Read &lt;code&gt;CN.getProperty("browser.window.devicePixelRatio", "1")&lt;/code&gt; when a breakpoint genuinely needs CSS pixels.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mouse wheel is no longer a fake drag
&lt;/h3&gt;

&lt;p&gt;Desktop ports used to translate one wheel turn into a press, three drag events, and a release. That could trigger a click when nothing scrolled. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5660" rel="noopener noreferrer"&gt;PR #5660&lt;/a&gt; routes native wheel events directly.&lt;/p&gt;

&lt;p&gt;Built-in scrolling finds the nearest scrollable ancestor. A custom component that consumes wheel input should now override &lt;code&gt;mouseWheel(WheelEvent)&lt;/code&gt; or register a listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addMouseWheelListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;WheelEvent&lt;/span&gt; &lt;span class="n"&gt;wheel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WheelEvent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wheel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isControlDown&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;zoomBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wheel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeltaY&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The developer guide became a maintained product
&lt;/h3&gt;

&lt;p&gt;We removed obsolete IDE and Ant-era guidance, folded the Maven workflow into the main book, restored missing samples, and reworked the order of several chapters. More important, CI now checks the guide's structure, cross-references, promised code blocks, served links, images, prose, and generated output.&lt;/p&gt;

&lt;p&gt;Documentation drift is now a failing build instead of a cleanup project for some future week. The restored call, VPN, desktop, security, identity, event-thread, graphics, and performance material is part of the release contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native reach with narrower defaults
&lt;/h2&gt;

&lt;p&gt;This week stretches Codename One in both directions. Calls, VPN profiles, AppKit windows, contact selection, and OTP reach deeper into each operating system. Package-triggered builders keep that reach narrow. An application does not receive VoIP background execution because another product needed it. It does not receive broad contacts access to pick one person. It does not gain a message-reading permission to fill six digits.&lt;/p&gt;

&lt;p&gt;Secure-by-default programming depends on small public APIs, generated native integration, explicit platform limits, and fewer opportunities to turn a convenience feature into permanent access. You still own the application UI and the protocol choices. The builders absorb the native product plumbing needed to ship them.&lt;/p&gt;

&lt;p&gt;Start with the &lt;a href="https://www.codenameone.com/developer-guide/#_call_management" rel="noopener noreferrer"&gt;Call Management&lt;/a&gt; or &lt;a href="https://www.codenameone.com/developer-guide/#_vpn" rel="noopener noreferrer"&gt;VPN&lt;/a&gt; chapter, then test the failure path in the simulator before sending a native build.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Build Hints That Fail Before the Build Server</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:03:01 +0000</pubDate>
      <link>https://dev.to/codenameone/build-hints-that-fail-before-the-build-server-3c6o</link>
      <guid>https://dev.to/codenameone/build-hints-that-fail-before-the-build-server-3c6o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo9kvdfe2dmgvcykm5g4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo9kvdfe2dmgvcykm5g4.jpg" alt="Build hint annotations passing through the Java compiler before reaching the Codename One build server" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A bad configuration line can be worse than a failed build. If the build stays green while ignoring the setting, you usually discover the mistake in a generated native project or on a device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5586" rel="noopener noreferrer"&gt;PR #5586&lt;/a&gt; moves 87 common Codename One build hints into Java's type system. A misspelled hint is now an unknown annotation attribute. A wrong value type or unsupported option stops at compilation instead of traveling to the build server as an inert string.&lt;/p&gt;

&lt;p&gt;This change is part of the &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;native windows weekly release&lt;/a&gt;. It is available through the Codename One Maven repository, so existing projects need the repository and plugin repository entries shown at the top of that post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build hints existed in the first place
&lt;/h2&gt;

&lt;p&gt;The original Codename One build server changed frequently. We needed to expose a new Android, iOS, or packaging switch without waiting for every IDE plugin to ship an updated settings screen.&lt;/p&gt;

&lt;p&gt;Build hints separated those two release clocks. The plugin could send an open-ended map of strings. A builder could start reading a new key immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;codename1.arg.&amp;lt;name&amp;gt;=&amp;lt;value&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That escape hatch still does useful work more than a decade later. It lets a CN1Lib contribute native dependencies and lets a project tune low-level output without a new plugin protocol for every option.&lt;/p&gt;

&lt;p&gt;The cost is that neither Java nor the plugin knew what most of those strings meant. The prefix confused people. Names and capitalization had to be exact. Values with commas, semicolons, or XML had their own merge rules. A name that no builder read was accepted, uploaded, and discarded.&lt;/p&gt;

&lt;p&gt;This line looks plausible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Accepted by the old path, but ignored by the Android builder
&lt;/span&gt;&lt;span class="py"&gt;codename1.arg.android.minSdkVersion&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;24&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The builder reads &lt;code&gt;android.min_sdk_version&lt;/code&gt;, not &lt;code&gt;android.minSdkVersion&lt;/code&gt;. The project asked for API 24, the build succeeded, and the request used the builder's default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compiler now checks the common path
&lt;/h2&gt;

&lt;p&gt;The same setting can live on the application's main class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.Android&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.AndroidMinSdk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.Build&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.DesktopBuild&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.DesktopTitleBar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.Ios&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.ThemeMode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.annotations.buildhints.Toggle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.codename1.system.Lifecycle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Android&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;themeMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThemeMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MODERN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;minSdkVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AndroidMinSdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;API_24&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nativeTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThemeMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MODERN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@DesktopBuild&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;titleBar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DesktopTitleBar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NATIVE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Ios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;themeMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThemeMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MODERN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;newStorageLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Toggle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ON&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Intercom"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"AFNetworking"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApplication&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Lifecycle&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;minSdkVersion&lt;/code&gt; takes &lt;code&gt;AndroidMinSdk&lt;/code&gt;, so an integer does not compile. &lt;code&gt;titleBar&lt;/code&gt; takes &lt;code&gt;DesktopTitleBar&lt;/code&gt;, so autocomplete shows the accepted values and a made-up constant fails at the same line.&lt;/p&gt;

&lt;p&gt;Seven annotations cover the common groups: &lt;code&gt;@Ios&lt;/code&gt;, &lt;code&gt;@Android&lt;/code&gt;, &lt;code&gt;@DesktopBuild&lt;/code&gt;, &lt;code&gt;@Build&lt;/code&gt;, &lt;code&gt;@Hardening&lt;/code&gt;, &lt;code&gt;@IosPrivacy&lt;/code&gt;, and &lt;code&gt;@OnDeviceDebug&lt;/code&gt;. Ten enums describe closed value sets. Open-ended values remain strings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBPbGRbUHJvcGVydGllcy1vbmx5IHBhdGhdCiAgICAgICAgUFtjb2RlbmFtZTEuYXJnIG5hbWUgYW5kIHN0cmluZyB2YWx1ZV0gLS0-IFJbQnVpbGQgcmVxdWVzdF0KICAgICAgICBSIC0tPiBLe0RvZXMgYSBidWlsZGVyIHJlYWQgdGhpcyBleGFjdCBrZXk_fQogICAgICAgIEsgLS0-fFllc3wgVVtBcHBseSB2YWx1ZV0KICAgICAgICBLIC0tPnxOb3wgU1tHcmVlbiBidWlsZCwgc2V0dGluZyBpZ25vcmVkXQogICAgZW5kCgogICAgc3ViZ3JhcGggTmV3W0NvbXBpbGVyLWNoZWNrZWQgcGF0aF0KICAgICAgICBBW0Fubm90YXRpb24gb24gbWFpbiBjbGFzc10gLS0-IEp7amF2YWMgY2hlY2tzIGF0dHJpYnV0ZSwgdHlwZSwgYW5kIGVudW19CiAgICAgICAgSiAtLT58SW52YWxpZHwgRltDb21waWxlIGVycm9yIGF0IHRoZSBkZWNsYXJhdGlvbl0KICAgICAgICBKIC0tPnxWYWxpZHwgTVtwcm9jZXNzLWFubm90YXRpb25zXQogICAgICAgIE0gLS0-IFdbVGhlIHNhbWUgbmFtZSBhbmQgc3RyaW5nIHdpcmUgZm9ybWF0XQogICAgICAgIFcgLS0-IEJbRXhpc3RpbmcgYnVpbGRlcl0KICAgIGVuZA%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBzdWJncmFwaCBPbGRbUHJvcGVydGllcy1vbmx5IHBhdGhdCiAgICAgICAgUFtjb2RlbmFtZTEuYXJnIG5hbWUgYW5kIHN0cmluZyB2YWx1ZV0gLS0-IFJbQnVpbGQgcmVxdWVzdF0KICAgICAgICBSIC0tPiBLe0RvZXMgYSBidWlsZGVyIHJlYWQgdGhpcyBleGFjdCBrZXk_fQogICAgICAgIEsgLS0-fFllc3wgVVtBcHBseSB2YWx1ZV0KICAgICAgICBLIC0tPnxOb3wgU1tHcmVlbiBidWlsZCwgc2V0dGluZyBpZ25vcmVkXQogICAgZW5kCgogICAgc3ViZ3JhcGggTmV3W0NvbXBpbGVyLWNoZWNrZWQgcGF0aF0KICAgICAgICBBW0Fubm90YXRpb24gb24gbWFpbiBjbGFzc10gLS0-IEp7amF2YWMgY2hlY2tzIGF0dHJpYnV0ZSwgdHlwZSwgYW5kIGVudW19CiAgICAgICAgSiAtLT58SW52YWxpZHwgRltDb21waWxlIGVycm9yIGF0IHRoZSBkZWNsYXJhdGlvbl0KICAgICAgICBKIC0tPnxWYWxpZHwgTVtwcm9jZXNzLWFubm90YXRpb25zXQogICAgICAgIE0gLS0-IFdbVGhlIHNhbWUgbmFtZSBhbmQgc3RyaW5nIHdpcmUgZm9ybWF0XQogICAgICAgIFcgLS0-IEJbRXhpc3RpbmcgYnVpbGRlcl0KICAgIGVuZA%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1904" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The builders did not need a replacement protocol. &lt;code&gt;BuildHintAnnotationProcessor&lt;/code&gt; converts the checked declaration into the same key and string value they already consume. Old projects, older framework versions, and the build service keep speaking the existing wire format.&lt;/p&gt;

&lt;h2&gt;
  
  
  An unset annotation does not freeze a server default
&lt;/h2&gt;

&lt;p&gt;The build service still needs room to change. An Android packaging default that made sense ten years ago might be wrong for the current Play Store.&lt;/p&gt;

&lt;p&gt;That is why no annotation attribute has a meaningful default. An empty string, an empty array, zero, &lt;code&gt;Toggle.DEFAULT&lt;/code&gt;, or an enum's unset marker all mean that the application said nothing. The processor emits no hint, leaving the current builder default in control.&lt;/p&gt;

&lt;p&gt;There are no &lt;code&gt;boolean&lt;/code&gt; attributes. Consider an &lt;code&gt;appBundle&lt;/code&gt; attribute with a Java default of &lt;code&gt;false&lt;/code&gt;. The Android builder currently defaults that hint to &lt;code&gt;true&lt;/code&gt;. Compiling &lt;code&gt;false&lt;/code&gt; into every application would silently override the server even when the developer never selected it. &lt;code&gt;Toggle.ON&lt;/code&gt;, &lt;code&gt;Toggle.OFF&lt;/code&gt;, and &lt;code&gt;Toggle.DEFAULT&lt;/code&gt; preserve all three states.&lt;/p&gt;

&lt;p&gt;This keeps the reason build hints existed. The server can evolve without requiring a client release for every default change. The difference is that an explicit value can now be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arrays carry the merge rule
&lt;/h2&gt;

&lt;p&gt;Complex values exposed another weak point in the string form. Some lists use commas, some use semicolons, and a CN1Lib may append its own entry to the application's value.&lt;/p&gt;

&lt;p&gt;An annotation expresses a hint that accepts library additions as an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Ios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Intercom"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"AFNetworking"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hint metadata records the wire key, separator, and whether a CN1Lib may append. The processor and library merger use that metadata instead of maintaining separate hand-written separator lists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW01haW4tY2xhc3MgYW5ub3RhdGlvbnNdIC0tPiBDW0NvbXBpbGVyIGNoZWNrc10KICAgIEMgLS0-IE1bR2VuZXJhdGVkIGhpbnQgbWFuaWZlc3RdCiAgICBQW1Byb3BlcnRpZXMtb25seSBsb25nIHRhaWxdIC0tPiBHW0NhdGFsb2ctYXdhcmUgbWVyZ2VdCiAgICBNIC0tPiBHCiAgICBMW0NOMUxpYiBjb250cmlidXRpb25zXSAtLT4gRwogICAgRyAtLT4gT1tDb21tYW5kLWxpbmUgb3ZlcmxheV0KICAgIE8gLS0-IFJbRXhpc3RpbmcgYnVpbGQgcmVxdWVzdF0KICAgIFIgLS0-IEJbVW5jaGFuZ2VkIGJ1aWxkZXJzXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW01haW4tY2xhc3MgYW5ub3RhdGlvbnNdIC0tPiBDW0NvbXBpbGVyIGNoZWNrc10KICAgIEMgLS0-IE1bR2VuZXJhdGVkIGhpbnQgbWFuaWZlc3RdCiAgICBQW1Byb3BlcnRpZXMtb25seSBsb25nIHRhaWxdIC0tPiBHW0NhdGFsb2ctYXdhcmUgbWVyZ2VdCiAgICBNIC0tPiBHCiAgICBMW0NOMUxpYiBjb250cmlidXRpb25zXSAtLT4gRwogICAgRyAtLT4gT1tDb21tYW5kLWxpbmUgb3ZlcmxheV0KICAgIE8gLS0-IFJbRXhpc3RpbmcgYnVpbGQgcmVxdWVzdF0KICAgIFIgLS0-IEJbVW5jaGFuZ2VkIGJ1aWxkZXJzXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1797" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A command-line &lt;code&gt;-Dcodename1.arg.&amp;lt;name&amp;gt;=...&lt;/code&gt; override still wins. A CN1Lib can still append to a hint whose metadata permits library additions. Declaring the same hint in an annotation and &lt;code&gt;codenameone_settings.properties&lt;/code&gt; is different: the build stops and names the duplicate rather than guessing which source you intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  One catalog replaces five drifting descriptions
&lt;/h2&gt;

&lt;p&gt;The old hint set was partially restated in the developer guide, the Settings tool, the simulator, the Maven plugin's separator map, and an agent reference. Only 147 of roughly 520 names appeared in more than one of those places.&lt;/p&gt;

&lt;p&gt;The annotations are now the source of truth for the 87 hints they expose. The remaining hints live in &lt;code&gt;maven/build-hint-catalog&lt;/code&gt;, including dynamic families and build-service-only settings. Together they describe all 497 hints read by the builders. The generated developer guide table now contains 577 rows with type, default, and annotation columns.&lt;/p&gt;

&lt;p&gt;The Settings tool reads the catalog instead of scraping an AsciiDoc table and guessing types from prose. It validates closed value sets and identifies hints owned by an annotation, so it cannot add a duplicate property behind your back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdqdt1kpqi2g82n7x7ye1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdqdt1kpqi2g82n7x7ye1.png" alt="The Codename One Settings build-hint editor" width="799" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The properties editor remains available for the long tail. Its schema now comes from the same catalog as the guide and compiler tooling.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two CI gates protect the catalog. One fails when framework code reads a hint the catalog does not describe, or when our documentation names a hint no builder reads. The second renders the annotation-backed editor and guide data to prove generation still produces the complete set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating an existing project
&lt;/h2&gt;

&lt;p&gt;After updating the project POM and Codename One version, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:migrate-build-hints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal moves every property backed by an annotation when it can reproduce the value faithfully. That includes open-ended strings such as &lt;code&gt;facebook.appId&lt;/code&gt;, &lt;code&gt;gcm.sender_id&lt;/code&gt;, and &lt;code&gt;ios.teamId&lt;/code&gt;. Hints without annotations, including dynamic families, stay in &lt;code&gt;codenameone_settings.properties&lt;/code&gt;; values that cannot be translated without changing their meaning stay there too. The goal then runs the annotation processing path and verifies that every moved setting reached the generated manifest. If verification fails, it restores both files.&lt;/p&gt;

&lt;p&gt;Older projects might not bind &lt;code&gt;process-annotations&lt;/code&gt; to the module that compiles the main class. The build refuses to ignore the annotations and reports the missing execution. Add this to the Codename One Maven plugin configuration when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;cn1-process-classes&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;process-classes&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;process-annotations&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some settings must remain properties. &lt;code&gt;codename1.arg.java.version&lt;/code&gt; chooses the compiler that must compile the annotated class, so reading it from that class would be too late. Dynamic families such as &lt;code&gt;android.permission.&amp;lt;NAME&amp;gt;&lt;/code&gt; also stay in the file because an annotation cannot expose an arbitrary map key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compile failures are the compatibility policy
&lt;/h2&gt;

&lt;p&gt;The annotation form intentionally does not promise source compatibility for build hints. If a weekly release renames an attribute, changes its type, or removes an accepted value, the project stops compiling at the declaration. That is the desired failure mode.&lt;/p&gt;

&lt;p&gt;The build service still accepts the old string protocol. An already-built project keeps building. If an annotation disappears and you need the previous spelling, the properties form remains available.&lt;/p&gt;

&lt;p&gt;This matters most for settings that affect more than appearance. &lt;code&gt;@Hardening&lt;/code&gt; and &lt;code&gt;@IosPrivacy&lt;/code&gt; now make common security and privacy configuration visible to the compiler. A security setting that is misspelled is no security setting at all.&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;mvn cn1:migrate-build-hints&lt;/code&gt;, review the generated annotations, and keep the properties that the goal leaves behind. The split is deliberate: checked declarations for the stable, common path and strings where the build still needs an open-ended escape hatch.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Rootless Jailbreak Detection: Updating the Signals, Not the Claim</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 09 Sep 2026 17:09:25 +0000</pubDate>
      <link>https://dev.to/codenameone/rootless-jailbreak-detection-updating-the-signals-not-the-claim-3b1e</link>
      <guid>https://dev.to/codenameone/rootless-jailbreak-detection-updating-the-signals-not-the-claim-3b1e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzuj2epwdsdiwy6g474tr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzuj2epwdsdiwy6g474tr.jpg" alt="An iPhone integrity scan finding a rootless jailbreak path and hooked system call" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ios.detectJailbreak=true&lt;/code&gt; did not fire on a device jailbroken with palera1n 2.1.1 in rootless mode. The build hint was wired correctly. The detector was asking questions that described the previous generation of jailbreaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5584" rel="noopener noreferrer"&gt;PR #5584&lt;/a&gt; replaces that signal set. It recognizes rootless bootstraps, checks whether detection APIs disagree, inspects mounts and loaded images, and reruns the gate when the application returns from the background.&lt;/p&gt;

&lt;p&gt;For native desktop windows, nearby devices, wearable follow-through, and the rest of this release, see the &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;weekly release overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rootless means the old signals can be correctly absent
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;rootful&lt;/code&gt; jailbreak modifies or replaces files on the system volume. The traditional detector looked for &lt;code&gt;Cydia&lt;/code&gt; and &lt;code&gt;apt&lt;/code&gt; paths, Substrate-era injected libraries, a writable root filesystem, and a successful write outside the application sandbox.&lt;/p&gt;

&lt;p&gt;Current jailbreaks such as &lt;code&gt;palera1n&lt;/code&gt;, &lt;code&gt;Dopamine&lt;/code&gt;, and &lt;code&gt;XinaA15&lt;/code&gt; keep the signed system volume sealed. They place the &lt;code&gt;Procursus&lt;/code&gt; bootstrap under &lt;code&gt;/var/jb&lt;/code&gt;, inject through frameworks such as &lt;code&gt;ElleKit&lt;/code&gt;, and commonly use &lt;code&gt;Sileo&lt;/code&gt;. Writing to the root filesystem still fails because that is the design.&lt;/p&gt;

&lt;p&gt;The old detector could therefore receive the correct answer to every old probe and make the wrong integrity decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBBW2lPUyBpbnRlZ3JpdHkgc2Nhbl0gLS0-IFBbRmlsZXN5c3RlbSBwYXRoc10KICAgIEEgLS0-IE1bTW91bnQgdGFibGVdCiAgICBBIC0tPiBMW0xvYWRlZCBpbWFnZXNdCiAgICBBIC0tPiBVW1JlZ2lzdGVyZWQgVVJMIHNjaGVtZXNdCiAgICBQIC0tPiBSMVtSb290ZnVsIGFydGlmYWN0c10KICAgIFAgLS0-IFIyW1Jvb3RsZXNzIC92YXIvamIgYW5kIFByb2N1cnN1cyBhcnRpZmFjdHNdCiAgICBNIC0tPiBSM1tXcml0YWJsZSByb290IG9yIGJvb3RzdHJhcCBtb3VudF0KICAgIEwgLS0-IFI0W0VsbGVLaXQsIEZyaWRhLCBTdWJzdHJhdGUgYW5kIHJlbGF0ZWQgaW1hZ2VzXQogICAgVSAtLT4gUjVbU2lsZW8sIEZpbHphLCBaZWJyYSwgQ3lkaWFdCiAgICBQIC0tPiBDW0luZGVwZW5kZW50IEFQSSBjcm9zcy1jaGVja10KICAgIEwgLS0-IEMKICAgIEMgLS0-IEhbSG9va2VkIGRldGVjdGlvbiBBUEkgc2lnbmFsXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBBW2lPUyBpbnRlZ3JpdHkgc2Nhbl0gLS0-IFBbRmlsZXN5c3RlbSBwYXRoc10KICAgIEEgLS0-IE1bTW91bnQgdGFibGVdCiAgICBBIC0tPiBMW0xvYWRlZCBpbWFnZXNdCiAgICBBIC0tPiBVW1JlZ2lzdGVyZWQgVVJMIHNjaGVtZXNdCiAgICBQIC0tPiBSMVtSb290ZnVsIGFydGlmYWN0c10KICAgIFAgLS0-IFIyW1Jvb3RsZXNzIC92YXIvamIgYW5kIFByb2N1cnN1cyBhcnRpZmFjdHNdCiAgICBNIC0tPiBSM1tXcml0YWJsZSByb290IG9yIGJvb3RzdHJhcCBtb3VudF0KICAgIEwgLS0-IFI0W0VsbGVLaXQsIEZyaWRhLCBTdWJzdHJhdGUgYW5kIHJlbGF0ZWQgaW1hZ2VzXQogICAgVSAtLT4gUjVbU2lsZW8sIEZpbHphLCBaZWJyYSwgQ3lkaWFdCiAgICBQIC0tPiBDW0luZGVwZW5kZW50IEFQSSBjcm9zcy1jaGVja10KICAgIEwgLS0-IEMKICAgIEMgLS0-IEhbSG9va2VkIGRldGVjdGlvbiBBUEkgc2lnbmFsXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1717" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;lstat&lt;/code&gt; sees the symlink after its target is gone
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/var/jb&lt;/code&gt; is commonly a symlink. On a rebooted semi-tethered device, the target can be absent while the link remains. &lt;code&gt;stat&lt;/code&gt;, &lt;code&gt;access&lt;/code&gt;, and &lt;code&gt;NSFileManager.fileExistsAtPath&lt;/code&gt; follow the link and report that the target is missing.&lt;/p&gt;

&lt;p&gt;The new check uses &lt;code&gt;lstat&lt;/code&gt;, which reports the link itself. It checks rootless bootstrap paths separately from the &lt;code&gt;rootful&lt;/code&gt; file list and emits a &lt;code&gt;rootlessPath&lt;/code&gt; signal.&lt;/p&gt;

&lt;p&gt;It also distinguishes three results: present, absent, and denied. Collapsing &lt;code&gt;EPERM&lt;/code&gt; into &lt;code&gt;ENOENT&lt;/code&gt; loses information and can manufacture a false disagreement between two probes that were both refused.&lt;/p&gt;

&lt;p&gt;The mount scan does not depend on guessing one path. It detects a writable root file system and additional bootstrap file systems associated with a rootless jailbreak.&lt;/p&gt;

&lt;h2&gt;
  
  
  A detector should notice when its own answers are filtered
&lt;/h2&gt;

&lt;p&gt;Jailbreak-bypass tweaks hook common filesystem and image APIs so they return clean answers. Checking more paths through the same hooked method adds length without adding an independent signal.&lt;/p&gt;

&lt;p&gt;The detector now asks equivalent questions through different entry points. It compares libc &lt;code&gt;lstat&lt;/code&gt; with a raw system trap where the platform permits it. It compares the image name returned by &lt;code&gt;_dyld_get_image_name&lt;/code&gt; with the path resolved by &lt;code&gt;dladdr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The two answers should agree on an untouched process. When one path sees an artifact and the other denies it, &lt;code&gt;DeviceIntegrity&lt;/code&gt; reports the instrumentation as &lt;code&gt;frida&lt;/code&gt;, which is the public reason category for hooking and dynamic instrumentation.&lt;/p&gt;

&lt;p&gt;The raw trap is not compiled on watchOS or tvOS because those SDKs mark &lt;code&gt;syscall&lt;/code&gt; unavailable. Rootless filesystem and image checks still compile there; only the second opinion is omitted on platforms where a jailbreak is not the target threat.&lt;/p&gt;

&lt;p&gt;Loaded-image matching now includes current rootless injectors such as &lt;code&gt;ElleKit&lt;/code&gt; alongside &lt;code&gt;Frida&lt;/code&gt; and older Substrate names. The implementation avoids walking &lt;code&gt;dyld&lt;/code&gt;'s live internal table, which can move under another thread. &lt;code&gt;dladdr&lt;/code&gt; gives the independent answer without dereferencing a structure intended to be read while a process is suspended.&lt;/p&gt;

&lt;h2&gt;
  
  
  The package-manager check now starts with Sileo
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;canOpenURL&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; for a scheme the application did not declare, even if the app handling it is installed. The build now adds jailbreak-related schemes to &lt;code&gt;LSApplicationQueriesSchemes&lt;/code&gt; when detection is enabled.&lt;/p&gt;

&lt;p&gt;The order is deliberate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sileo
filza
zbra
cydia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iOS limits how many query schemes an application can declare. The jailbreak probe is lower priority than the schemes requested by application features. The builder appends as many detector schemes as fit after every other feature has claimed its slots. &lt;code&gt;Sileo&lt;/code&gt; comes first so a one-slot remainder checks the package manager used by current rootless devices rather than &lt;code&gt;Cydia&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;canOpenURL&lt;/code&gt; is only one signal and is deprecated as of iOS 27. The filesystem, mount, and image checks carry the main weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard gate or runtime policy
&lt;/h2&gt;

&lt;p&gt;The zero-code launch gate remains one build hint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.arg.ios.detectJailbreak&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It now runs at launch and whenever the application returns from the background. That closes a simple attach-after-launch gap. A detected gate exits with a failure status instead of telling the operating system that the process ended normally.&lt;/p&gt;

&lt;p&gt;An application that needs a narrower response can inspect the same signal family at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DeviceIntegrity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDeviceCompromised&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;reasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DeviceIntegrity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCompromiseReasons&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;requireStepUpAuthentication&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reasons&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime API does not cache its result, so a check before a sensitive operation can see instrumentation attached after startup. It does not terminate the process. The application can warn, degrade one feature, or request additional authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  This raises bypass cost; it does not establish trust
&lt;/h2&gt;

&lt;p&gt;The PR compiled the detector across device, simulator, Mac Catalyst, watchOS, and tvOS configurations. The symlink behavior and independent-probe logic ran in a native harness. Builder tests pin scheme merging and ordering.&lt;/p&gt;

&lt;p&gt;The change was not verified on a jailbroken device during the PR. The original report names real hardware, but the revised detector still needs an on-device confirmation before we call that case closed.&lt;/p&gt;

&lt;p&gt;More importantly, every probe runs inside a process controlled by the attacker. A sufficiently capable bypass can patch the final boolean, skip the exit, or modify the application around the check.&lt;/p&gt;

&lt;p&gt;High-value authorization belongs on the backend. Bind an Apple App Attest or Google Play Integrity token to a fresh server nonce, verify the token with the platform provider, and let the backend decide whether to permit the action. App Shield packages that server-side verification and policy when an application team does not want to build it directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW09uLWRldmljZSBqYWlsYnJlYWsgc2lnbmFsc10gLS0-IEJbV2FybiwgZGVncmFkZSwgb3IgcmVxdWVzdCBzdGVwLXVwXQogICAgQ1tIYXJkd2FyZS1iYWNrZWQgYXR0ZXN0YXRpb25dIC0tPiBEW0JhY2tlbmQgdmVyaWZpY2F0aW9uXQogICAgRCAtLT4gRVtQZXJtaXQgb3IgcmVqZWN0IHNlbnNpdGl2ZSBhY3Rpb25dCiAgICBBIC0uIHJhaXNlcyBieXBhc3MgY29zdCAuLT4gRQogICAgQSAtLiBkb2VzIG5vdCBhdXRob3JpemUgLi0-IEQ%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW09uLWRldmljZSBqYWlsYnJlYWsgc2lnbmFsc10gLS0-IEJbV2FybiwgZGVncmFkZSwgb3IgcmVxdWVzdCBzdGVwLXVwXQogICAgQ1tIYXJkd2FyZS1iYWNrZWQgYXR0ZXN0YXRpb25dIC0tPiBEW0JhY2tlbmQgdmVyaWZpY2F0aW9uXQogICAgRCAtLT4gRVtQZXJtaXQgb3IgcmVqZWN0IHNlbnNpdGl2ZSBhY3Rpb25dCiAgICBBIC0uIHJhaXNlcyBieXBhc3MgY29zdCAuLT4gRQogICAgQSAtLiBkb2VzIG5vdCBhdXRob3JpemUgLi0-IEQ%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1028" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security work that states its boundary
&lt;/h2&gt;

&lt;p&gt;This week adds multiple native desktop windows, selected document publication, nearby-device APIs, complete watch complications, and current rootless jailbreak signals. The features are broad. Their access paths are narrow.&lt;/p&gt;

&lt;p&gt;A window owns one paint and input surface. A document provider publishes one read-only tree. Nearby features bring only the frameworks their package uses. A watch face receives a bounded timeline, not a live application process. Jailbreak detection supplies a local risk signal, while backend attestation remains the trust decision.&lt;/p&gt;

&lt;p&gt;Codename One keeps strengthening its security lead by putting these boundaries in the ordinary API. Secure-by-default programming is not a claim that every device is safe. Restricted access, capability checks, explicit failure, and server verification should be the shortest supported path.&lt;/p&gt;

&lt;p&gt;If you enable &lt;code&gt;ios.detectJailbreak&lt;/code&gt;, test the generated build on the device classes your policy covers. Keep the runtime response proportionate, and gate money movement, credential changes, or sensitive exports on a verdict your backend verified.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Watch Follow-Through: Complications, Tiles, and a Companion Wear APK</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 08 Sep 2026 17:15:35 +0000</pubDate>
      <link>https://dev.to/codenameone/watch-follow-through-complications-tiles-and-a-companion-wear-apk-1dk2</link>
      <guid>https://dev.to/codenameone/watch-follow-through-complications-tiles-and-a-companion-wear-apk-1dk2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pc07n3io0s5snziv0w8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pc07n3io0s5snziv0w8.jpg" alt="Watch complications and a companion Wear application generated from a Codename One project" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week's wearable post ended with two conspicuous limits. Declaring a &lt;code&gt;WATCH_*&lt;/code&gt; surface did not generate the native extension or service that puts it on a watch face. An Android companion build did not return a Wear APK beside the phone APK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5583" rel="noopener noreferrer"&gt;PR #5583&lt;/a&gt; closes both gaps. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5594" rel="noopener noreferrer"&gt;PR #5594&lt;/a&gt; fixes two watchOS failures that appeared once the complete watch path ran. This is follow-through on the model we shipped last week, not a second wearable announcement.&lt;/p&gt;

&lt;p&gt;For native desktop windows and the rest of this release, see the &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;weekly release overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One surface declaration now reaches the watch face
&lt;/h2&gt;

&lt;p&gt;A complication remains a watch family in the existing surfaces API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WidgetKind&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WidgetKind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"steps"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Steps"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WidgetSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WATCH_CIRCULAR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WidgetSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WATCH_RECTANGULAR&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Surfaces&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerWidgetKind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Surfaces&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"steps"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four portable families lower to the platform surfaces below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codename One family&lt;/th&gt;
&lt;th&gt;Apple Watch&lt;/th&gt;
&lt;th&gt;Wear OS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WATCH_CIRCULAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accessoryCircular&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ranged value or monochrome image complication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WATCH_RECTANGULAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accessoryRectangular&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Long-text complication and a Tile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WATCH_INLINE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accessoryInline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short-text complication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WATCH_CORNER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accessoryCorner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Circular complication because Wear has no corner slot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The build now produces the missing platform pieces. Apple Watch receives a &lt;code&gt;CN1WatchWidgets&lt;/code&gt; WidgetKit extension embedded inside the watch application. Wear OS receives one complication data source per kind and a ProtoLayout Tile for the rectangular family.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBBW1dpZGdldEtpbmQgd2l0aCBXQVRDSCBmYW1pbGllc10gLS0-IEJbU2hhcmVkIHN1cmZhY2UgZGVzY3JpcHRvciBhbmQgdGltZWxpbmVdCiAgICBCIC0tPiBJW3dhdGNoT1MgYnVpbGRdCiAgICBCIC0tPiBXW1dlYXIgT1MgYnVpbGRdCiAgICBJIC0tPiBJRVtDTjFXYXRjaFdpZGdldHM8YnIvPldpZGdldEtpdCBleHRlbnNpb25dCiAgICBXIC0tPiBXQ1tDb21wbGljYXRpb24gZGF0YSBzb3VyY2U8YnIvPnBlciBraW5kXQogICAgVyAtLT4gV1RbVGlsZSBzZXJ2aWNlPGJyLz5yZWN0YW5ndWxhciBmYW1pbHldCiAgICBJRSAtLT4gRjFbQXBwbGUgV2F0Y2ggZmFjZV0KICAgIFdDIC0tPiBGMltXZWFyIE9TIHdhdGNoIGZhY2VdCiAgICBXVCAtLT4gVFtXZWFyIE9TIFRpbGUgY2Fyb3VzZWxd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBBW1dpZGdldEtpbmQgd2l0aCBXQVRDSCBmYW1pbGllc10gLS0-IEJbU2hhcmVkIHN1cmZhY2UgZGVzY3JpcHRvciBhbmQgdGltZWxpbmVdCiAgICBCIC0tPiBJW3dhdGNoT1MgYnVpbGRdCiAgICBCIC0tPiBXW1dlYXIgT1MgYnVpbGRdCiAgICBJIC0tPiBJRVtDTjFXYXRjaFdpZGdldHM8YnIvPldpZGdldEtpdCBleHRlbnNpb25dCiAgICBXIC0tPiBXQ1tDb21wbGljYXRpb24gZGF0YSBzb3VyY2U8YnIvPnBlciBraW5kXQogICAgVyAtLT4gV1RbVGlsZSBzZXJ2aWNlPGJyLz5yZWN0YW5ndWxhciBmYW1pbHldCiAgICBJRSAtLT4gRjFbQXBwbGUgV2F0Y2ggZmFjZV0KICAgIFdDIC0tPiBGMltXZWFyIE9TIHdhdGNoIGZhY2VdCiAgICBXVCAtLT4gVFtXZWFyIE9TIFRpbGUgY2Fyb3VzZWxd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="761" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No watch family means no watch extension, complication service, Tile service, or related dependency. A kind that declares only watch families also stops producing an accidental Android home-screen widget. Add a phone family when the product needs both.&lt;/p&gt;

&lt;h2&gt;
  
  
  A watch face does not render a small widget
&lt;/h2&gt;

&lt;p&gt;Wear OS asks a complication data source for a typed value. It does not accept a component hierarchy. The generated adapter mines the surface tree for at most two text nodes and one image, then maps them to short text, long text, ranged value, or monochrome image data.&lt;/p&gt;

&lt;p&gt;Containers, padding, backgrounds, corner radii, alignment, weight, per-node color, and child actions do not survive that lowering. The renderer logs dropped details once through &lt;code&gt;CN1Surfaces&lt;/code&gt;, so &lt;code&gt;adb logcat -s CN1Surfaces&lt;/code&gt; shows what the watch face received.&lt;/p&gt;

&lt;p&gt;Apple's WidgetKit accessory families render more of the SwiftUI tree, but the slots remain small and often monochrome. A single number or gauge is safer than a layout that depends on several labels.&lt;/p&gt;

&lt;p&gt;A Wear Tile is different. It renders the node tree and supports per-node actions. Circular progress can remain circular there. A &lt;code&gt;SurfaceDynamicText&lt;/code&gt; countdown does not tick continuously on the Tile; it advances when the published timeline refreshes.&lt;/p&gt;

&lt;p&gt;The simulator previews all four watch families at their real point sizes and clips circular shapes. It cannot show the lossy Wear OS conversion. Check a final complication on the device and watch face you plan to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phone-published data has to cross to the watch
&lt;/h2&gt;

&lt;p&gt;The phone and watch use separate storage. Even Apple's App Group identifier resolves to a watch-local container on the watch. Publishing a timeline on the phone cannot change a file in that container.&lt;/p&gt;

&lt;p&gt;When a kind contains a watch family, a successful phone-side &lt;code&gt;Surfaces.publish()&lt;/code&gt; now mirrors the descriptor after the local publish completes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgUGhvbmUgYXBwCiAgICBwYXJ0aWNpcGFudCBMb2NhbCBhcyBQaG9uZSBzdXJmYWNlIHN0b3JlCiAgICBwYXJ0aWNpcGFudCBCcmlkZ2UgYXMgV2F0Y2hDb25uZWN0aXZpdHkgb3IgRGF0YSBMYXllcgogICAgcGFydGljaXBhbnQgV2F0Y2ggYXMgV2F0Y2ggc3VyZmFjZSBzdG9yZQogICAgcGFydGljaXBhbnQgRmFjZSBhcyBXYXRjaCBmYWNlCiAgICBBcHAtPj5Mb2NhbDogUHVibGlzaCBkZXNjcmlwdG9yIGFuZCB0aW1lbGluZQogICAgTG9jYWwtLT4-QXBwOiBMb2NhbCBwdWJsaXNoIHN1Y2NlZWRzCiAgICBBcHAtPj5CcmlkZ2U6IE1pcnJvciB3YXRjaC1iZWFyaW5nIGtpbmQKICAgIEJyaWRnZS0-PldhdGNoOiBTdG9yZSBkZXNjcmlwdG9yIGFuZCBpbWFnZXJ5CiAgICBXYXRjaC0-PkZhY2U6IFJlcXVlc3QgdGltZWxpbmUgcmVsb2Fk%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgUGhvbmUgYXBwCiAgICBwYXJ0aWNpcGFudCBMb2NhbCBhcyBQaG9uZSBzdXJmYWNlIHN0b3JlCiAgICBwYXJ0aWNpcGFudCBCcmlkZ2UgYXMgV2F0Y2hDb25uZWN0aXZpdHkgb3IgRGF0YSBMYXllcgogICAgcGFydGljaXBhbnQgV2F0Y2ggYXMgV2F0Y2ggc3VyZmFjZSBzdG9yZQogICAgcGFydGljaXBhbnQgRmFjZSBhcyBXYXRjaCBmYWNlCiAgICBBcHAtPj5Mb2NhbDogUHVibGlzaCBkZXNjcmlwdG9yIGFuZCB0aW1lbGluZQogICAgTG9jYWwtLT4-QXBwOiBMb2NhbCBwdWJsaXNoIHN1Y2NlZWRzCiAgICBBcHAtPj5CcmlkZ2U6IE1pcnJvciB3YXRjaC1iZWFyaW5nIGtpbmQKICAgIEJyaWRnZS0-PldhdGNoOiBTdG9yZSBkZXNjcmlwdG9yIGFuZCBpbWFnZXJ5CiAgICBXYXRjaC0-PkZhY2U6IFJlcXVlc3QgdGltZWxpbmUgcmVsb2Fk%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1241" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apple uses &lt;code&gt;transferCurrentComplicationUserInfo&lt;/code&gt; when the watch reports that a complication is active and the daily transfer budget has room. Otherwise it queues ordinary user info for the next watch wake. The payload is capped at 48 KB. Imagery is dropped before the whole update so the number can still refresh.&lt;/p&gt;

&lt;p&gt;Wear OS sends the descriptor through the Data Layer and moves imagery as a file. The watch process can apply the framework-reserved update without launching the Codename One UI.&lt;/p&gt;

&lt;p&gt;This mirror is best effort. A product that requires a guaranteed current value should let the watch fetch from the network or publish from its own application lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android companion builds now return two artifacts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;codename1.watchMain&lt;/code&gt; still names the watch application entry point on both platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.watchMain&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;com.example.MyWatchApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;codename1.watchStandalone&lt;/code&gt;, Android now returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myapp.apk
myapp-wear.apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Wear artifact declares the watch hardware feature and receives a higher version code. Google Play uses supported-device filters and version codes to select the phone or watch product from one listing. &lt;code&gt;android.watchVersionCodeOffset&lt;/code&gt; changes the default gap, and &lt;code&gt;android.watchVersionCode&lt;/code&gt; sets the watch code directly.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;codename1.watchStandalone=true&lt;/code&gt; still makes the single Android artifact the standalone watch application. Set &lt;code&gt;android.watchModule=false&lt;/code&gt; when the application wants the phone-to-watch Data Layer but does not ship its own watch UI.&lt;/p&gt;

&lt;p&gt;On Apple, a companion remains embedded inside the phone application. A standalone watch target is generated, but the returned archive still targets the phone scheme. App Store submission of that standalone product needs a manual archive of the watch scheme in Xcode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the complete path found two watchOS failures
&lt;/h2&gt;

&lt;p&gt;The new complication work exercised a watch application from build through launch. That exposed two issues fixed by &lt;a href="https://github.com/codenameone/CodenameOne/pull/5594" rel="noopener noreferrer"&gt;PR #5594&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Six native functions in &lt;code&gt;IOSNative.m&lt;/code&gt; call translated &lt;code&gt;IOSWearableCallbacks&lt;/code&gt; entry points. Their generated header was never included. The watch compiler rejected the undeclared calls. A phone compiler that accepted the implicit declarations could pass the Java objects through the wrong native calling convention.&lt;/p&gt;

&lt;p&gt;After that compile failure was fixed, the watch app crashed at launch. Objective-C called &lt;code&gt;[session activate]&lt;/code&gt;, which is the Swift spelling of the API. The Objective-C selector is &lt;code&gt;activateSession&lt;/code&gt;. iOS happened to answer the wrong selector in the existing path; watchOS did not.&lt;/p&gt;

&lt;p&gt;The generated callback header is now included only when WatchConnectivity is translated, and both activation call sites use &lt;code&gt;activateSession&lt;/code&gt;. The watch screenshot run reaches the full suite instead of stopping before the first capture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the gap we wrote down
&lt;/h2&gt;

&lt;p&gt;The useful part of publishing explicit limits is that they become work items instead of folklore. Last week's post said complications were a portable model without generated platform adapters and that Android companion packaging was missing. This release replaces both sentences with working build outputs, tests, and device-facing documentation.&lt;/p&gt;

&lt;p&gt;The rest of this week's work applies the same discipline. Desktop windows isolate paint and input state per native surface. Document Provider publishes a narrow file tree to another process. Nearby Devices separates ranging, association, and transport so an application takes only the platform access it uses. Rootless jailbreak detection updates the signal set without promoting a local heuristic into a server trust decision.&lt;/p&gt;

&lt;p&gt;Codename One's security lead depends on these boundaries remaining visible. A watch update can be delayed. A face may discard most of a layout. A companion APK has its own lifecycle and storage. Code that handles those states explicitly is safer than code built on a cross-platform promise that the operating systems do not keep.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/rootless-jailbreak-detection/" rel="noopener noreferrer"&gt;next post upgrades iOS jailbreak detection for current rootless layouts&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>UWB and Nearby Devices: Distance, Direction, Association, and Transport</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sun, 06 Sep 2026 16:14:40 +0000</pubDate>
      <link>https://dev.to/codenameone/uwb-and-nearby-devices-distance-direction-association-and-transport-196i</link>
      <guid>https://dev.to/codenameone/uwb-and-nearby-devices-distance-direction-association-and-transport-196i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0wb3v5gwb0514jrzuy1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0wb3v5gwb0514jrzuy1.jpg" alt="A phone measuring the distance and direction to nearby devices" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bluetooth can tell you that a device is probably nearby. Ultra-wideband can tell you that it is 42 centimeters away and 18 degrees to the right, until the person holding it turns and the direction disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That last clause is part of the API contract. Nearby hardware gives partial answers, permissions change by operating-system version, and Apple's local transport cannot discover Google's. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5589" rel="noopener noreferrer"&gt;PR #5589&lt;/a&gt; exposes those boundaries through three packages under &lt;code&gt;com.codename1.nearby&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For multiple native windows, Document Provider, and the rest of this release, see the &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;weekly release overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three packages answer three different questions
&lt;/h2&gt;

&lt;p&gt;The API is split by capability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.nearby.ranging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How far away is the peer, and in which direction?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.nearby.companion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which accessory did the user associate with this app?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.nearby.transport&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How do two nearby instances exchange a payload?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW05lYXJieSBkZXZpY2UgZmVhdHVyZV0gLS0-IFJbUmFuZ2luZzxici8-VVdCIGRpc3RhbmNlIGFuZCBkaXJlY3Rpb25dCiAgICBBIC0tPiBDW0NvbXBhbmlvbjxici8-dXNlci1hcHByb3ZlZCBhc3NvY2lhdGlvbl0KICAgIEEgLS0-IFRbVHJhbnNwb3J0PGJyLz5kaXNjb3ZlcnkgYW5kIHBheWxvYWRzXQogICAgUiAtLT4gWFtFeGlzdGluZyB0b2tlbiBjaGFubmVsPGJyLz51c3VhbGx5IEJsdWV0b290aCBMRV0KICAgIEMgLS0-IEJbQmx1ZXRvb3RoIGRldmljZSBoYW5kbGVdCiAgICBUIC0tPiBQW1NhbWUtcGxhdGZvcm0gcGVlcl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBBW05lYXJieSBkZXZpY2UgZmVhdHVyZV0gLS0-IFJbUmFuZ2luZzxici8-VVdCIGRpc3RhbmNlIGFuZCBkaXJlY3Rpb25dCiAgICBBIC0tPiBDW0NvbXBhbmlvbjxici8-dXNlci1hcHByb3ZlZCBhc3NvY2lhdGlvbl0KICAgIEEgLS0-IFRbVHJhbnNwb3J0PGJyLz5kaXNjb3ZlcnkgYW5kIHBheWxvYWRzXQogICAgUiAtLT4gWFtFeGlzdGluZyB0b2tlbiBjaGFubmVsPGJyLz51c3VhbGx5IEJsdWV0b290aCBMRV0KICAgIEMgLS0-IEJbQmx1ZXRvb3RoIGRldmljZSBoYW5kbGVdCiAgICBUIC0tPiBQW1NhbWUtcGxhdGZvcm0gcGVlcl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="821" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Referencing a package is the opt-in. An application that imports only ranging does not receive Nearby Connections, MultipeerConnectivity, local-network prompts, or companion-profile permissions. The build scans the bytecode and includes the frameworks needed by the packages it finds.&lt;/p&gt;

&lt;p&gt;Use capability queries instead of platform checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Ranging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Offer precision finding.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CompanionDevices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Offer association.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Offer local peer transfer.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UWB hardware is absent on many current phones. The application still needs a useful state when ranging is unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranging starts after the peers exchange tokens
&lt;/h2&gt;

&lt;p&gt;Ultra-wideband measures radio time of flight. The practical target is roughly ten-centimeter ranging, while a Bluetooth signal-strength estimate can swing by meters when a hand covers the phone. UWB still does not discover or identify the peer by itself.&lt;/p&gt;

&lt;p&gt;Both devices first exchange a ranging token over a channel they already share. A Bluetooth LE GATT characteristic is the common choice. Once the other token arrives, the session can start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Ranging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareSession&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RangingRole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONTROLLER&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;showBluetoothFallback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;bluetoothCharacteristic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLocalToken&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toByteArray&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addRangingListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangingAdapter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RangingUpdate&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasDistance&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDistance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;RangingUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CENTIMETERS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" cm"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasDirection&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;arrow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAngle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAzimuth&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RangingToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromByteArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theirToken&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;RangingUpdate&lt;/code&gt; field except its timestamp is optional. A peer behind the phone can report distance without direction. At the edge of radio range, both can disappear temporarily. Check &lt;code&gt;hasDistance()&lt;/code&gt; and &lt;code&gt;hasDirection()&lt;/code&gt; independently.&lt;/p&gt;

&lt;p&gt;Azimuth is platform-dependent, with zero straight ahead and positive values to the right. Android reports -90 to 90 degrees, so azimuth alone cannot distinguish a peer in front from one behind. iOS reports a direction vector, and the port derives a -180 to 180 degree azimuth while retaining the original vector. Elevation runs from -90 to 90 on both platforms when it is available.&lt;/p&gt;

&lt;p&gt;One session tracks one peer. This reflects Apple's &lt;code&gt;NINearbyPeerConfiguration&lt;/code&gt; limit. Track several peers with several sessions.&lt;/p&gt;

&lt;p&gt;Accessory ranging has different handshakes. iOS uses the Nearby Interaction Accessory Protocol and returns bytes that must be forwarded to the accessory. Android joins the UWB address, channel, preamble, session id, and key named by the accessory. A token serialized on one platform is rejected on the other instead of being handed to an incompatible native API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Association asks the user which device belongs to the app
&lt;/h2&gt;

&lt;p&gt;Companion association is not Bluetooth pairing. The operating system draws a chooser, the user selects one accessory, and the app receives a durable association id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AssociationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssociationRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DeviceFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bleService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"180D"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;CompanionDevices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;associate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Preferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sensor"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;CompanionDevices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startObservingPresence&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The association survives restarts and reboots. &lt;code&gt;CompanionDevice.getAddress()&lt;/code&gt; is also the identifier accepted by &lt;code&gt;BluetoothLE.getPeripheral(String)&lt;/code&gt;, so the result connects directly to the existing BLE API.&lt;/p&gt;

&lt;p&gt;Android can report companion presence from API 31. Register the listener during &lt;code&gt;init()&lt;/code&gt; because the operating system can record a sighting while no form exists. Codename One replays the stored event when the app next initializes. It does not silently turn presence into unrestricted background execution.&lt;/p&gt;

&lt;p&gt;iOS 18 uses AccessorySetupKit for association. It does not provide the same live presence event. An application that needs proximity on iOS should scan with the Bluetooth API. The service UUIDs iOS may show must be declared through &lt;code&gt;ios.nearby.accessoryServices&lt;/code&gt; before the app is built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nearby transport is local, but not cross-ecosystem
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NearbyTransport&lt;/code&gt; maps to Google Nearby Connections on Android and MultipeerConnectivity on Apple platforms:&lt;/p&gt;

&lt;p&gt;When the application uses a service id such as &lt;code&gt;chat&lt;/code&gt;, declare it for the iOS build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.arg.ios.nearby.serviceType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hint accepts a comma-separated list when the application uses more than one service id. Without it, the iOS builder declares only the package-derived id, and the runtime rejects advertising or discovery for &lt;code&gt;chat&lt;/code&gt; before starting the native service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addTransportListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TransportAdapter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;endpointFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Endpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Shai's phone"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;connectionRequested&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IncomingConnection&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;showCodeOnScreen&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAuthenticationToken&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;connected&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Endpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;payloadReceived&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Endpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Payload&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startAdvertising&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Shai's phone"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransportStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CLUSTER&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;NearbyTransport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startDiscovery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TransportStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CLUSTER&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android derives a short authentication token from the connection's key exchange. Show it on both screens before accepting a sensitive connection. iOS supplies no equivalent token. The API returns an empty value there instead of constructing a decorative code a relay could reproduce.&lt;/p&gt;

&lt;p&gt;An Android phone discovers another Android endpoint. An iPhone discovers another Apple endpoint. The two native stacks do not share a wire protocol. Use BLE L2CAP or Bonjour and sockets when the product needs iPhone-to-Android communication.&lt;/p&gt;

&lt;p&gt;Byte payloads are intentionally small. Larger data uses a file payload and reports progress. A resolved send means the platform accepted the payload; &lt;code&gt;PayloadStatus.SUCCESS&lt;/code&gt; means the peer received it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Develop the failure states without UWB hardware
&lt;/h2&gt;

&lt;p&gt;The simulator, desktop ports, and JavaScript port implement local simulated sessions rather than returning a stub. They report &lt;code&gt;NearbyAvailability.LOCAL_ONLY&lt;/code&gt;, which lets the app label the peer as simulated.&lt;/p&gt;

&lt;p&gt;This covers the application logic that usually consumes the time: an arrow that loses direction, a distance label that becomes unavailable, an association chooser that is canceled, a connection that is rejected, and a payload that fails after it was accepted for sending.&lt;/p&gt;

&lt;p&gt;The simulator cannot prove antenna behavior, radio coexistence, entitlement signing, or real-device permission prompts. Run those on the target hardware before treating the feature as complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reach without pretending the platforms agree
&lt;/h2&gt;

&lt;p&gt;Nearby Devices expands the physical reach of a shared application without flattening three different system contracts into one unreliable call. Ranging admits that direction can disappear. Companion association names the versions and profiles that exist. Transport says that the Apple and Google protocols cannot find each other.&lt;/p&gt;

&lt;p&gt;The rest of this week's release keeps those boundaries visible. Desktop windows exist only where the port has a window manager. Document Provider publishes a named read-only tree. Watch complications lower the shared surface model into the constrained data a watch face actually accepts. Rootless jailbreak detection adds current signals, then leaves high-value authorization on the backend.&lt;/p&gt;

&lt;p&gt;Secure-by-default programming often starts with an API that can say no. Capability queries, optional ranging fields, a user-approved association, and a visible authentication boundary make failure part of the normal path instead of a surprise native exception.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/watch-complications-wear-companion/" rel="noopener noreferrer"&gt;next post closes the watch gaps we documented last week&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Put App Documents in the System File Browser</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 05 Sep 2026 16:02:40 +0000</pubDate>
      <link>https://dev.to/codenameone/put-app-documents-in-the-system-file-browser-2lo4</link>
      <guid>https://dev.to/codenameone/put-app-documents-in-the-system-file-browser-2lo4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5x2t6oidkzfvn2j90xl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx5x2t6oidkzfvn2j90xl.jpg" alt="Selected application documents appearing in a phone file browser" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An invoice, drawing, report, or project file can live inside an application for years without becoming a document the operating system understands. The user has to open the app before another app can preview or share it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5607" rel="noopener noreferrer"&gt;PR #5607&lt;/a&gt; adds &lt;code&gt;com.codename1.documents&lt;/code&gt;. It publishes a selected, read-only document tree as a location in the iOS Files app and Android storage picker. The application decides which nodes exist, what they are called, and whether their bytes are local or fetched from a server.&lt;/p&gt;

&lt;p&gt;For native desktop windows and the rest of this release, see the &lt;a href="https://www.codenameone.com/blog/native-desktop-windows/" rel="noopener noreferrer"&gt;weekly release overview&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish a tree, not a directory permission
&lt;/h2&gt;

&lt;p&gt;The root is a &lt;code&gt;DocumentNode&lt;/code&gt;. Folder and file nodes underneath it describe the view exposed to the system browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DocumentProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSharedDirectory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;writePdf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/august-1042.pdf"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoiceBytes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;DocumentNode&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DocumentNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"My Invoices"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;DocumentNode&lt;/span&gt; &lt;span class="n"&gt;august&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DocumentNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2026-08"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"August 2026"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;august&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DocumentNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inv-1042"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invoice 1042.pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"august-1042.pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceBytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLastModified&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceModified&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;august&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;DocumentProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The published tree does not have to mirror the application's internal storage. An internal object id can become a stable document id. A database title can become the visible filename. A nested folder can be a projection of records that are not folders on disk at all.&lt;/p&gt;

&lt;p&gt;The location is read-only. The file browser does not offer rename, delete, or save operations that the application cannot honor. Updating the view means publishing another tree.&lt;/p&gt;

&lt;p&gt;Publishing one selected tree is narrower than exposing the whole documents directory. The file browser sees only the nodes in the most recent index. Logging out can withdraw the whole location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;DocumentProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the requirement is simply to show the application's entire iOS documents directory, the existing &lt;code&gt;UIFileSharingEnabled&lt;/code&gt; and &lt;code&gt;LSSupportsOpeningDocumentsInPlace&lt;/code&gt; plist keys remain the smaller option. Document Provider is for a curated tree, a virtual layout, or content that does not live on the device yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The iOS extension cannot call the application
&lt;/h2&gt;

&lt;p&gt;Apple runs a File Provider extension in a separate process. The Files app can start it while the Codename One application is not running. The extension cannot call into the Java application to ask what a folder contains.&lt;/p&gt;

&lt;p&gt;Codename One therefore uses a publication model. &lt;code&gt;DocumentProvider.publish()&lt;/code&gt; serializes the tree into an App Group container shared by the application and the generated extension. The extension reads that index and serves the browser independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgR3JvdXAgYXMgQXBwIEdyb3VwIGNvbnRhaW5lcgogICAgcGFydGljaXBhbnQgRXh0IGFzIEdlbmVyYXRlZCBpT1MgZXh0ZW5zaW9uCiAgICBwYXJ0aWNpcGFudCBGaWxlcyBhcyBGaWxlcyBhcHAKICAgIEFwcC0-Pkdyb3VwOiBQdWJsaXNoIGRvY3VtZW50IGluZGV4IGFuZCBsb2NhbCBieXRlcwogICAgQXBwLS0-PkFwcDogTWF5IHRlcm1pbmF0ZQogICAgRmlsZXMtPj5FeHQ6IEJyb3dzZSBvciBvcGVuIGEgZG9jdW1lbnQKICAgIEV4dC0-Pkdyb3VwOiBSZWFkIHRoZSBsYXRlc3QgcHVibGlzaGVkIGluZGV4CiAgICBHcm91cC0tPj5FeHQ6IE1ldGFkYXRhIGFuZCBsb2NhbCBwYXRoIG9yIHJlbW90ZSBlbmRwb2ludAogICAgRXh0LS0-PkZpbGVzOiBGb2xkZXIgbGlzdGluZyBvciBkb2N1bWVudCBieXRlcw%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAgYXMgQ29kZW5hbWUgT25lIGFwcAogICAgcGFydGljaXBhbnQgR3JvdXAgYXMgQXBwIEdyb3VwIGNvbnRhaW5lcgogICAgcGFydGljaXBhbnQgRXh0IGFzIEdlbmVyYXRlZCBpT1MgZXh0ZW5zaW9uCiAgICBwYXJ0aWNpcGFudCBGaWxlcyBhcyBGaWxlcyBhcHAKICAgIEFwcC0-Pkdyb3VwOiBQdWJsaXNoIGRvY3VtZW50IGluZGV4IGFuZCBsb2NhbCBieXRlcwogICAgQXBwLS0-PkFwcDogTWF5IHRlcm1pbmF0ZQogICAgRmlsZXMtPj5FeHQ6IEJyb3dzZSBvciBvcGVuIGEgZG9jdW1lbnQKICAgIEV4dC0-Pkdyb3VwOiBSZWFkIHRoZSBsYXRlc3QgcHVibGlzaGVkIGluZGV4CiAgICBHcm91cC0tPj5FeHQ6IE1ldGFkYXRhIGFuZCBsb2NhbCBwYXRoIG9yIHJlbW90ZSBlbmRwb2ludAogICAgRXh0LS0-PkZpbGVzOiBGb2xkZXIgbGlzdGluZyBvciBkb2N1bWVudCBieXRlcw%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1203" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Android, the generated &lt;code&gt;DocumentsProvider&lt;/code&gt; runs inside the application process. It still consumes the same published model. Keeping one model avoids application code that works only because Android happens to have a different process boundary.&lt;/p&gt;

&lt;p&gt;Local files must be written under &lt;code&gt;DocumentProvider.getSharedDirectory()&lt;/code&gt;. The iOS extension cannot see a path under the application's ordinary home directory, even if Java code can.&lt;/p&gt;

&lt;p&gt;Do not overwrite bytes in a file that is already published. Another app may be reading it. Write the new version under another name, publish a tree that points to it, and remove the old bytes after existing readers have had time to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote nodes can appear before their bytes arrive
&lt;/h2&gt;

&lt;p&gt;A file node can carry a &lt;code&gt;remoteId&lt;/code&gt; instead of a local path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;DocumentProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRemoteEndpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.example.com/drive"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionToken&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;DocumentNode&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DocumentNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Example Drive"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DocumentNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report-52"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Quarterly Report.pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRemoteId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"server-object-52"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;487120&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLastModified&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reportModified&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;DocumentProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user opens that node, the extension sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /drive/fetch?id=server-object-52
Authorization: Bearer &amp;lt;published token&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is the extension's only application state. It cannot borrow an in-memory login session from a process that may not exist. Publish a new endpoint token when the session renews, and clear the provider on logout.&lt;/p&gt;

&lt;p&gt;A node can have both a local path and a remote id. The local copy wins, so a cached document opens without a round trip while the same index still describes how to fetch a file not cached on the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  iOS signing is part of the feature
&lt;/h2&gt;

&lt;p&gt;Referencing &lt;code&gt;com.codename1.documents&lt;/code&gt; generates the platform machinery. On iOS, set the explicit hint so the Certificate Wizard and preflight know to prepare extension signing before bytecode scanning occurs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;codename1.arg.ios.documentProvider.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;codename1.arg.ios.documentProvider.appGroup&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;group.com.example.myapp&lt;/span&gt;
&lt;span class="py"&gt;codename1.arg.ios.documentProvider.displayName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Example Drive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension has its own bundle id, &lt;code&gt;&amp;lt;your.package&amp;gt;.CN1Documents&lt;/code&gt;, and needs its own explicit App ID and provisioning profiles. Both the application and extension App IDs must carry the same App Group. The Certificate Wizard creates and installs these assets. A wildcard App ID cannot carry the App Group entitlement.&lt;/p&gt;

&lt;p&gt;Android needs no configuration beyond referencing the API. The build declares an authority at &lt;code&gt;&amp;lt;your package&amp;gt;.documents&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mac Catalyst cannot host Apple's FileProvider framework. &lt;code&gt;DocumentProvider.isSupported()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; there and in the simulator. Simulator publication still writes the generated index under the application home directory so you can inspect what a device extension would receive.&lt;/p&gt;

&lt;h2&gt;
  
  
  A week of narrower capabilities
&lt;/h2&gt;

&lt;p&gt;Document Provider gives the operating system access to a named tree, not to the application sandbox. The rest of this week's work keeps similarly narrow boundaries. Native desktop windows get independent render and input surfaces without changing the phone model. Nearby device features opt in by package. Watch complications publish a timeline to a small system surface rather than starting the whole app.&lt;/p&gt;

&lt;p&gt;Making the narrow path the ordinary API reduces security mistakes. A read-only provider, an explicit App Group, and a tree that disappears on logout reduce the amount of platform code an application team has to get right. Rootless jailbreak detection and backend attestation cover different parts of the threat model, but they follow the same rule: expose the signal, define its limit, and avoid pretending that local code grants trust.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/blog/uwb-nearby-devices/" rel="noopener noreferrer"&gt;next post measures the distance and direction to nearby devices&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
