Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8d13514
Remove Locale dependency from native language validator
liannacasper Apr 1, 2026
cb238b5
Fix Android native stub by using Java implementation class
liannacasper Apr 1, 2026
90d9618
Support Kotlin native impls in generated Android native stubs
liannacasper Apr 1, 2026
d42de76
Avoid NoSuchMethodException in generated native stubs
liannacasper Apr 1, 2026
3162274
Scope reflective native stubs to Android builder only
liannacasper Apr 1, 2026
8497d18
Add missing MalformedURLException import in AndroidGradleBuilder
liannacasper Apr 1, 2026
09bb899
Fix AndroidGradleBuilder override compile issues
liannacasper Apr 1, 2026
2bce5fd
Harden iOS native class lookup for Swift module naming
liannacasper Apr 1, 2026
1d8b85c
Add CN1SS diagnostics for Swift/Kotlin native interface validation
liannacasper Apr 2, 2026
fb13946
Clarify null handling in native language validator
liannacasper Apr 2, 2026
2cff073
Emit compact swift diagnostic summary in CN1SS suite output
liannacasper Apr 2, 2026
968fb7d
Add Objective-C runtime scan fallback for Swift native classes
liannacasper Apr 2, 2026
a316764
Fail iOS UI test script when swift diag status is not OK
liannacasper Apr 2, 2026
acea4ec
Add Objective-C shim delegating Swift native interface implementation
liannacasper Apr 2, 2026
97cc0c6
Scan runtime class list for Swift bridge in iOS shim
liannacasper Apr 2, 2026
e520470
Use generated Swift header for direct bridge binding on iOS
liannacasper Apr 2, 2026
31c300b
Ensure Swift files are in compile phase for iOS main target
liannacasper Apr 2, 2026
d561375
Remove hardcoded Swift header include from iOS native shim
liannacasper Apr 2, 2026
0b40bdb
Add Objective-C shim fallback when Swift bridge is unavailable
liannacasper Apr 3, 2026
4d63da5
Require iOS diagnostics to confirm Swift bridge usage
liannacasper Apr 3, 2026
d29af2c
Document Swift/Kotlin native options and add optional stub generation…
liannacasper Apr 3, 2026
520cc18
Improve Swift build settings docs and add StubGenerator unit tests
liannacasper Apr 3, 2026
87010c0
Keep runner alive on swift diag failures and remove swift files from …
liannacasper Apr 3, 2026
607e3e5
Fix Swift resource phase cleanup in iOS project hook
liannacasper Apr 3, 2026
51aac0a
Harden Swift build phase correction for iOS projects
liannacasper Apr 3, 2026
40c9ab1
Stabilize Swift phase cleanup and bridging header placement
liannacasper Apr 3, 2026
a2bc4f0
Handle Swift files from source-folder references in Xcode fixup
liannacasper Apr 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CodenameOne/src/com/codename1/system/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// [support for making platform native API calls](https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html). Notice
/// that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
/// case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
/// message would be sent and so forth.
/// or Swift message would be sent and so forth.
///
/// Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
/// and PeerComponent values. Any other type of parameter/return type is prohibited. However, once in the native layer
Expand Down Expand Up @@ -59,9 +59,9 @@
/// These sources should be placed under the appropriate folder in the native directory and are sent to the
/// server for compilation.
///
/// For Objective-C, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. One would need to provide both a header and
/// an "m" file following this convention e.g.:
/// For iOS, one would need to define a class matching the name of the package and the class name
/// combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
/// (by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
///
/// ```java
/// @interface com_my_code_MyNative : NSObject {
Expand Down
10 changes: 5 additions & 5 deletions CodenameOne/src/com/codename1/system/package.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<a href="https://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html">
support for making platform native API calls</a>. Notice
that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
message would be sent and so forth.
case of Android the Java code will be invoked with full access to the Android API's, in case of iOS an Objective-C
or Swift message would be sent and so forth.
</p>
<p>
Native interfaces are designed to only allow primitive types, Strings, arrays (single dimension only!) of primitives
Expand Down Expand Up @@ -66,9 +66,9 @@
server for compilation.
</p>
<p>
For Objective-C, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. One would need to provide both a header and
an "m" file following this convention e.g.:
For iOS, one would need to define a class matching the name of the package and the class name
combined where the "." elements are replaced by underscores. This class can be implemented in Objective-C
(by providing both a header and an "m" file) or in Swift. Objective-C classes follow this convention e.g.:
</p>
<pre>
@interface com_my_code_MyNative : NSObject {
Expand Down
33 changes: 28 additions & 5 deletions docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ Sometimes you may wish to use an API that is unsupported by Codename One or inte

==== Introduction

Notice that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the case of Android the Java code will be invoked with full access to the Android API, in case of iOS an Objective-C message would be sent and so forth.
Notice that when we say "native" we do not mean C/C++ always but rather the platforms "native" environment. So in the case of Android, Java or Kotlin code can be invoked with full access to the Android API. In case of iOS, Objective-C or Swift code can be invoked and so forth.

TIP: You can still access C code under Android either by using JNI from the Android native code or by using a library

Expand Down Expand Up @@ -747,7 +747,7 @@ include::../demos/common/src/main/java/com/mycompany/myapp/MyNativeImplStub.java

The stub implementation always returns `false`, `null` or `0` by default. The `isSupported` also defaults to `false` thus allowing us to implement a `NativeInterface` on some platforms and leave the rest out without really knowing anything about these platforms.

We can implement the Android version using code similar to this:
We can implement the Android version in Java or Kotlin. Here is the Java version:

[source,java]
----
Expand All @@ -768,7 +768,27 @@ Codename One doesn't include the native platforms in its bundle e.g. the full An

TIP: When implementing a non-trivial native interface, send a server build with the "Include Source" option checked. Implement the native interface in the native IDE then copy and paste the native code back into Codename One

The implementation of this interface is nearly identical for Android, J2ME & Java SE.
The implementation of this interface is nearly identical for Android (Java/Kotlin), J2ME & Java SE.

===== Swift (iOS) and Kotlin (Android) options

For iOS native interfaces you can implement the generated `...Impl` class in Objective-C _or_ Swift. +
For Android native interfaces you can implement the generated `...Impl` class in Java _or_ Kotlin.

[cols="1,3",options="header"]
|===
| Platform
| Typical file locations for native interface implementations

| Android (Java/Kotlin)
| `android/src/main/java/com/mycompany/myapp/MyNativeImpl.java` +
`android/src/main/java/com/mycompany/myapp/MyNativeImpl.kt`

| iOS (Objective-C/Swift)
| `ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.h` +
`ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m` +
`ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.swift`
|===

===== Use the Android Main Thread (Native EDT)

Expand Down Expand Up @@ -834,9 +854,10 @@ android.sdkVersion=25
Once those were defined the native code for the Android implementation became trivial to write and the library was easy as there were no jars to include.


==== Objective-C (iOS)
==== Objective-C and Swift (iOS)

When generating the Objective-C code the "Generate Native Sources" tool produces two files: `com_mycompany_myapp_MyNativeImpl.h` & `com_mycompany_myapp_MyNativeImpl.m`.
When generating the Objective-C code the "Generate Native Sources" tool produces two files by default: `com_mycompany_myapp_MyNativeImpl.h` & `com_mycompany_myapp_MyNativeImpl.m`.
If you enable Swift stub generation in the Maven goal, it can also produce `com_mycompany_myapp_MyNativeImpl.swift`.

The `.m` files are the Objective-C equivalent of `.c` files and `.h` files contain the header/include information. In this case the `com_mycompany_myapp_MyNativeImpl.h` contains:

Expand All @@ -863,6 +884,8 @@ Here is a simple implementation similar to above:
include::../demos/ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m[tag=myNativeImplExample,indent=0]
----

If you prefer Swift for iOS native interfaces, keep the same class naming convention (`com_mycompany_myapp_MyNativeImpl`) and annotate the class with `@objc(...)` so the runtime can discover it.

===== Using the iOS Main Thread (Native EDT)

iOS has a native thread you should use for all calls just like Android. Check out the Native EDT on Android section above for reference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ After creating this (and possibly other) native interfaces in our project, run
mvn cn1:generate-native-interfaces
----

By default this generates Java/Objective-C stubs. You can optionally include Swift and Kotlin stubs (both off by default):

[source,bash]
----
mvn cn1:generate-native-interfaces \
-Dcn1.generateNativeInterfaces.swift=true \
-Dcn1.generateNativeInterfaces.kotlin=true
----

This will generate the following files (if they don't exist yet).

javase::
Expand All @@ -26,9 +35,12 @@ ios::
. `ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m`
android::
`android/src/main/java/com/mycompany/myapp/MyNativeImpl.java`
android (optional Kotlin)::
`android/src/main/java/com/mycompany/myapp/MyNativeImpl.kt`
javascript::
`javascript/src/main/javascript/com_mycompany_myapp_MyNativeImpl.js`
ios (optional Swift)::
`ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.swift`

Open and edit these files to implement your native interface methods as desired.


Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.*;
import java.lang.reflect.Method;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
Expand Down Expand Up @@ -3972,6 +3974,117 @@ static String xmlize(String s) {
}


@Override
protected String registerNativeImplementationsAndCreateStubs(ClassLoader parentClassLoader, File stubDir, File... classesDirectory) throws MalformedURLException, IOException {
Class[] discoveredNativeInterfaces = findNativeInterfaces(parentClassLoader, classesDirectory);
String registerNativeFunctions = "";
if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class n : discoveredNativeInterfaces) {
registerNativeFunctions += " NativeLookup.register(" + n.getName() + ".class, "
+ n.getName() + "Stub.class" + ");\n";
}
}

if (discoveredNativeInterfaces != null && discoveredNativeInterfaces.length > 0) {
for (Class currentNative : discoveredNativeInterfaces) {
File folder = new File(stubDir, currentNative.getPackage().getName().replace('.', File.separatorChar));
folder.mkdirs();
File javaFile = new File(folder, currentNative.getSimpleName() + "Stub.java");

String javaImplSourceFile = "package " + currentNative.getPackage().getName() + ";\n\n"
+ "import com.codename1.ui.PeerComponent;\n\n"
+ "public class " + currentNative.getSimpleName() + "Stub implements " + currentNative.getSimpleName() + "{\n"
+ " private final Object impl = createImpl();\n\n"
+ " private static Object createImpl() {\n"
+ " try {\n"
+ " return Class.forName(\"" + currentNative.getName() + getImplSuffix() + "\").newInstance();\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to instantiate native implementation for " + currentNative.getName() + "\", t);\n"
+ " }\n"
+ " }\n\n"
+ " private Object __cn1Invoke(String methodName, Object[] args) {\n"
+ " try {\n"
+ " java.lang.reflect.Method[] methods = impl.getClass().getMethods();\n"
+ " for (java.lang.reflect.Method method : methods) {\n"
+ " if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {\n"
+ " return method.invoke(impl, args);\n"
+ " }\n"
+ " }\n"
+ " throw new RuntimeException(methodName + \" with \" + args.length + \" args\");\n"
+ " } catch (Throwable t) {\n"
+ " throw new RuntimeException(\"Failed to invoke native method \" + methodName, t);\n"
+ " }\n"
+ " }\n\n";

for (Method m : currentNative.getMethods()) {
String name = m.getName();
if (name.equals("hashCode") || name.equals("equals") || name.equals("toString")) {
continue;
}

Class returnType = m.getReturnType();

javaImplSourceFile += " public " + returnType.getSimpleName() + " " + name + "(";
Class[] params = m.getParameterTypes();
String args = "";
if (params != null && params.length > 0) {
for (int iter = 0; iter < params.length; iter++) {
if (iter > 0) {
javaImplSourceFile += ", ";
args += ", ";
}
javaImplSourceFile += params[iter].getSimpleName() + " param" + iter;
if (params[iter].getName().equals("com.codename1.ui.PeerComponent")) {
args += convertPeerComponentToNative("param" + iter);
} else {
args += "param" + iter;
}
}
}
javaImplSourceFile += ") {\n";
String invocationExpression = "__cn1Invoke(\"" + name + "\", new Object[]{" + args + "})";
if (Void.class == returnType || Void.TYPE == returnType) {
javaImplSourceFile += " " + invocationExpression + ";\n }\n\n";
} else {
if (returnType.getName().equals("com.codename1.ui.PeerComponent")) {
javaImplSourceFile += " return " + generatePeerComponentCreationCode(invocationExpression) + ";\n }\n\n";
} else if (returnType.isPrimitive()) {
if (returnType == Boolean.TYPE) {
javaImplSourceFile += " return ((Boolean)" + invocationExpression + ").booleanValue();\n }\n\n";
} else if (returnType == Integer.TYPE) {
javaImplSourceFile += " return ((Integer)" + invocationExpression + ").intValue();\n }\n\n";
} else if (returnType == Long.TYPE) {
javaImplSourceFile += " return ((Long)" + invocationExpression + ").longValue();\n }\n\n";
} else if (returnType == Byte.TYPE) {
javaImplSourceFile += " return ((Byte)" + invocationExpression + ").byteValue();\n }\n\n";
} else if (returnType == Short.TYPE) {
javaImplSourceFile += " return ((Short)" + invocationExpression + ").shortValue();\n }\n\n";
} else if (returnType == Character.TYPE) {
javaImplSourceFile += " return ((Character)" + invocationExpression + ").charValue();\n }\n\n";
} else if (returnType == Float.TYPE) {
javaImplSourceFile += " return ((Float)" + invocationExpression + ").floatValue();\n }\n\n";
} else if (returnType == Double.TYPE) {
javaImplSourceFile += " return ((Double)" + invocationExpression + ").doubleValue();\n }\n\n";
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
} else {
javaImplSourceFile += " return (" + returnType.getSimpleName() + ")" + invocationExpression + ";\n }\n\n";
}
}
}

javaImplSourceFile += "}\n";

try (FileOutputStream out = new FileOutputStream(javaFile)) {
out.write(javaImplSourceFile.getBytes(StandardCharsets.UTF_8));
}
}
}

return registerNativeFunctions;
}

@Override
protected String generatePeerComponentCreationCode(String methodCallString) {
return "PeerComponent.create(" + methodCallString + ")";
Expand Down
Loading
Loading