Skip to content

Kotlin and Java Modules

s edited this page Aug 4, 2026 · 7 revisions

Kotlin and Java Modules

The CLI calls this type Native Module.

Use it when the feature needs Android APIs, an Android or JVM library, user-owned Kotlin or Java code, or performance-sensitive work that can be grouped into a few larger calls.

Returned values are exposed to JavaScript as Promises. Methods returning Unit or void are called without await.

Generate the module

supernote-module doctor --type native
supernote-module add local-calculator --type native --yes

The examples below use:

Package name:      local-calculator
JavaScript name:  Calculator
Android namespace: com.example.calculator

Where to write your code

Write Kotlin and Java files below:

local_modules/local-calculator/android/src/main/java/com/example/calculator/

The exact path follows the Android namespace chosen during generation.

The starter implementation is normally Example.kt. You can edit it, replace it, or add more Kotlin and Java files in the same user-owned source tree.

Do not place implementation inside generated annotation, processor, bridge, or registration packages. Update may replace those files.

Export a Kotlin function

Import the generated SupernoteExport annotation and place it on a public instance method:

package com.example.calculator

import com.example.calculator.nativemodule.annotation.SupernoteExport

class Example {
    @SupernoteExport
    fun add(left: Double, right: Double): Double {
        return left + right
    }
}

Call it from JavaScript or TypeScript:

import Calculator from 'local-calculator';

const total = await Calculator.add(20, 22);

The Android build scans the annotation and generates the React Native bridge and TypeScript declaration.

Export a Java function

package com.example.calculator;

import com.example.calculator.nativemodule.annotation.SupernoteExport;

public class Example {
    public Example() {}

    @SupernoteExport
    public double add(double left, double right) {
        return left + right;
    }
}

The JavaScript call is unchanged:

const total = await Calculator.add(20, 22);

Kotlin and Java can be used together in the same module.

Supported values

Kotlin or Java JavaScript or TypeScript Return behavior
Boolean / boolean boolean Promise
Double / double number Promise
String string Promise
Unit / void return void Direct call without await

The supported values can be used as parameters and return values.

The generated boundary does not currently support nullable values, arrays, collections, callbacks, or arbitrary objects.

Use richer types internally. Convert them to a small supported boundary, for example:

  • Encode structured data as a string.
  • Pass a few primitive parameters.
  • Store complex native state and expose small operations around it.

Rename the JavaScript function

By default, the JavaScript function uses the Kotlin or Java method name.

@SupernoteExport(name = "greet")
fun makeGreeting(name: String): String {
    return "Hello, $name"
}
const message = await Calculator.greet('Ziv');

Export names must be unique in the module and valid JavaScript property names.

Use Android APIs

An exported class can request Android context through one of these public constructors, checked in this order:

  1. ReactApplicationContext
  2. android.content.Context
  3. A constructor with no arguments

Example:

package com.example.calculator

import android.content.Context
import com.example.calculator.nativemodule.annotation.SupernoteExport

class DeviceInfo(private val context: Context) {
    @SupernoteExport
    fun packageName(): String {
        return context.packageName
    }
}

This is the main reason to choose a Native Module: implementation code is normal Kotlin or Java running as part of the Android application.

Activity injection and arbitrary constructor parameters are not supported. Use the provided context to reach the Android service or API required by the feature.

Methods that return no value

@SupernoteExport
fun setEnabled(enabled: Boolean) {
    // Apply the setting.
}
Calculator.setEnabled(true);

A Unit or void method has no Promise to reject. If it throws, the generated module logs the exception but cannot return it to JavaScript.

Use a value-returning method when JavaScript needs to know whether the operation succeeded.

Exceptions from a value-returning method reject the generated Promise with the error code:

LOCAL_NATIVE_MODULE_ERROR

Export restrictions

The generator exports ordinary public instance methods on public concrete classes.

The exported boundary does not support:

  • suspend, inline, operator, or static methods.
  • Generic or extension methods.
  • Varargs.
  • Nullable parameters or returns.
  • Arrays, collections, callbacks, or arbitrary objects.
  • Unsupported constructor parameters.

These features can still be used internally. Keep the exported method simple and call the more complex implementation behind it.

Performance and batching

The expensive part is often not Kotlin or Java itself. It is repeatedly crossing between the isolated JavaScript and Android runtimes.

Prefer:

One JavaScript call
→ Native code processes a complete stroke, page, file, or operation
→ One result returns

Avoid one native call for every point, byte, or tiny state update.

Measure the complete feature on the target Supernote before moving more code into native code. A batched Native Module is often simpler and fast enough.

Add Kotlin or Java libraries

The generated package is a normal Android library, so dependencies can be added through Gradle.

The generated Gradle files are generator-owned and may be replaced by Update. Save the change in version control or a patch and be prepared to reapply it after updating the module.

The generator does not prove that a third-party library is compatible with the target Supernote Android version, permissions, firmware, or PluginHost.

Validate after changing exports

supernote-module validate local-calculator --build

For complete output:

supernote-module validate local-calculator --build --verbose

Run a build whenever an exported method is added, removed, renamed, or changes signature.

Update and remove

See Managing Modules.

In summary:

supernote-module update local-calculator
supernote-module remove local-calculator

Update preserves the user-owned Kotlin and Java implementation tree, excluding generator-owned subpackages. Remove deletes the complete package, including the implementation.