Using Java and Kotlin

When you want to create applications that are tightly integrated with the Android APIs, use the Java or Kotlin programming language with the Kanzi Android framework. See Developing with the Kanzi Android framework and Kanzi Java API reference.

Using properties

Kanzi API classes contain static members for built-in property types such as Node.OpacityProperty. To read and write these properties:

// Access a node from the view.
Node2D node = view.getRoot();

// Set property.
node.setProperty(Node.OpacityProperty, 0.5);

// Get property.
float opacity = node.getProperty(Node.OpacityProperty);
Copy to clipboard
// Access a node from the view.
val node = view.getRoot()

// Set property.
node.setProperty(Node.OpacityProperty, 0.5)

// Get property.
val opacity = node.getProperty(Node.OpacityProperty)
Copy to clipboard

To access a property by name, create an instance of DynamicPropertyType or AbstractPropertyType using a full name of a property type:

// Use DynamicPropertyType to access a property type when the type is known.
DynamicPropertyType<Boolean> visibleProperty =
    new DynamicPropertyType("Node.Visible", Boolean.class);
node.setProperty(visibleProperty, false);

// Use AbstractPropertyType to access a property type when the type is not known.
AbstractPropertyType visibleProperty = new AbstractPropertyType("Node.Visible");
node.setProperty(visibleProperty, false);
Copy to clipboard
// Use DynamicPropertyType to access a property type when the type is known.
val visibleProperty = DynamicPropertyType("Node.Visible", Boolean::class.java)
node.setProperty(visibleProperty, false)

// Use AbstractPropertyType to access a property type when the type is not known.
val visibleProperty = AbstractPropertyType("Node.Visible")
node.setProperty(visibleProperty, false)
Copy to clipboard

See Property system and Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.

Using messages

Kanzi API classes contain static members for built-in message types such as Button2D.ClickMessage. To dispatch these messages:

// Access a node from the view.
Node2D node = view.getRoot();

node.dispatchMessage(Button2D.ClickMessage, new MessageArguments());
Copy to clipboard
// Access a node from the view.
val node = view.getRoot()

node.dispatchMessage(Button2D.ClickMessage, MessageArguments())
Copy to clipboard

To dispatch a message by name, create an instance of AbstractMessageType using the full name of a message type:

// Get reference to a message type.
AbstractMessageType messageType = new AbstractMessageType("Message.MyMessage");
DynamicPropertyType<Boolean> argumentProperty =
    new DynamicPropertyType("MyMessage.Argument", Boolean.class);

// Access a node from the view.
Node2D node = view.getRoot();

// Construct message arguments.
MessageArguments args = new MessageArguments();

// Set message arguments.
args.setArgument(argumentProperty, false);

// Send the message.
node.dispatchMessage(messageType, args);
Copy to clipboard
// Get reference to a message type.
val messageType = AbstractMessageType("Message.MyMessage")
val argumentProperty = DynamicPropertyType("MyMessage.Argument", Boolean::class.java)

// Access a node from the view.
val node = view.getRoot()

// Construct message arguments.
val args = MessageArguments()

// Set message arguments.
args.setArgument(argumentProperty, false)

// Send the message.
node.dispatchMessage(messageType, args)
Copy to clipboard

To perform some functionality when a node receives a message, add a message handler to the node:

// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage,
             new Node.MessageSubscriptionFunction()
             {
                @Override
                public void handle(MessageArguments messageArguments)
                {
                    // Extract arguments.
                    int toggleState = messageArguments.getArgument(Button2D.ToggleStateProperty);
                }
             });
Copy to clipboard
// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage,
      MessageSubscriptionFunction { messageArguments -> // Extract arguments.
          val toggleState = messageArguments.getArgument(Button2D.ToggleStateProperty)
      })
Copy to clipboard

See Using messages.

Object lifetime

The ObjectRef class controls object lifetime in the Kanzi API. The native backing for the object is not reclaimed until the ObjectRef is either explicitly closed, or allowed to be garbage collected. Native objects can also have other native owners that are managed by the native portion of the Kanzi Engine. Therefore an ObjectRef does not exclusively control the lifetime of the object.

It is good practice to close all ObjectRef instances when the application no longer needs them.

Kanzi automatically wraps all newly created objects in an ObjectRef instance as the sole owner.

When you remove all references to an object, and no native references exist, the object is immediately reclaimed. If application code attempts to access the object in this state, Kanzi throws a StaleObjectException to indicate that the object is no longer valid. To prevent this, you can wrap the object in a new ObjectRef instance before that object becomes stale. The new instance extends the lifetime and prevents the native object from being reclaimed.

// Create an EmptyNode2D node.
ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node");

// Access the EmptyNode2D instance from within the ObjectRef.
EmptyNode2D node = nodeRef.get();

// Use the node.

// Close the ObjectRef.
nodeRef.close();

// If you try to access the node variable after closing its ObjectRef,
// Kanzi can throw a StaleObjectException.
node.setName("New Name");
Copy to clipboard
// Create an EmptyNode2D node.
val nodeRef = EmptyNode2D.create(domain, "My Empty Node")

// Access the EmptyNode2D instance from within the ObjectRef.
val node = nodeRef.get()

// Use the node.

// Close the ObjectRef.
nodeRef.close()

// If you try to access the node variable after closing its ObjectRef,
// Kanzi can throw a StaleObjectException.
node.setName("New Name")
Copy to clipboard

To automatically manage the ObjectRef instance, use the Java try-with-resources statement or Kotlin use function:

// Create an EmptyNode2D node.
try (ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node"))
{
    // Access the EmptyNode2D instance from within the ObjectRef.
    EmptyNode2D node = nodeRef.get();

    // Use the node.

    // Kanzi automatically closes the nodeRef at the end of the try block scope.
}
Copy to clipboard
// Create an EmptyNode2D node.
EmptyNode2D.create(domain, "My Empty Node").use { nodeRef ->
    // Access the EmptyNode2D instance from within the ObjectRef.
    val node = nodeRef.get()

    // Use the node.

    // Kanzi automatically closes the nodeRef at the end of the use block scope.
}
Copy to clipboard

Exceptions

Kanzi uses checked exceptions to indicate recoverable errors. For example, if the resource is not found when you call the Node.acquireResource method, Kanzi throws the ObjectNotFoundException exception.

Kanzi Engine exceptions thrown by native code are packagaged into instances of NativeException. It is not expected for Java code to handle these, instead allow them to propagate up the stack so that Kanzi retranslates it to a native exception that Kanzi Engine can handle them.

Platform-specific code

Java plugins can run on multiple platforms, such as Kanzi Android framework and in the Kanzi Studio Preview. Make sure that the application runs platform-specific code only when it is running on a compatible platform. For example, call the Android API only when the application is running in an Android environment.

To detect the environment, use the Platform methods:

if (Platform.isAndroid())
{
    // Initialize the code path for when the plugin runs on Android.
    Object context = domain.getPlatformContext();
}
else
{
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}
Copy to clipboard
if (Platform.isAndroid())
{
    // Initialize the code path for when the plugin runs on Android.
    val context = domain.getPlatformContext()
}
else
{
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}
Copy to clipboard

Creating a custom node type

You can use Java or Kotlin to extend the functionality of Kanzi Engine by creating a custom node type.

To create a custom node type:

  1. Create a class in a Java project.

    class MyNode2D extends Node2D
    {
    }
    
    Copy to clipboard
    class MyNode2D private constructor(domain: Domain, nativeNode: Long, metaclass: Metaclass)
        : Node2D(domain, nativeNode, metaclass)
    {
    }
    
    Copy to clipboard
  2. Kanzi requires these items for each custom node:

    • Metaclass. The metaclass must be present in this form:

      @Metadata
      public static final Metaclass metaclass = new Metaclass("Example.MyNode2D");
      
      Copy to clipboard
      companion object {
          @Metadata
          val metaclass = Metaclass("Example.MyNode2D")
      }
      
      Copy to clipboard

      You can provide additional metadata with the @EditorInfo annotation. See Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.

    • Construction. Construction of derived types must follow this pattern with a static create method and a private constructor with the specific arguments. The implementation of the create method is usually a call to the createDerived method on the type that you are extending.

    static public ObjectRef<MyNode2D> create(Domain domain, String name)
    {
        return Node2D.createDerived(domain, name, metaclass);
    }
    
    private MyNode2D(Domain domain, long nativeNode, Metaclass metaclass)
    {
        super(domain, nativeNode, metaclass);
    }
    
    Copy to clipboard
    companion object {
        ...
    
        @JvmStatic
        fun create(domain: Domain, name: String): ObjectRef<MyNode2D> {
            return Node2D.createDerived(domain, name, metaclass)
        }
    }
    
    Copy to clipboard
    • (Optional) Custom initialization. During the constructor Kanzi does not fully initialize custom Kanzi objects. Place custom initialization code in the initialize() method.

    @Override
    protected void initialize()
    {
        super.initialize();
    
        // Add custom initialization code after calling super.initialize().
    }
    
    Copy to clipboard
     override fun initialize() {
         super.initialize()
    
         // Add custom initialization code after calling super.initialize().
    }
    
    Copy to clipboard
  3. (Optional) Create custom property types.

    @Metadata
    @EditorInfo(displayName = "Custom Property")
    public static final PropertyType<String> CustomProperty =
        new PropertyType("MyNode2D.Custom", String.class);
    
    // Properties can also have an automatically generated name.
    // In this case 'MyNode2D.Other'.
    @Metadata
    public static final PropertyType<String> OtherProperty = new PropertyType(String.class);
    
    Copy to clipboard
    companion object {
        ...
    
        @Metadata
        @EditorInfo(displayName = "Custom Property")
        val CustomProperty = PropertyType("MyNode2D.Custom", String::class.java)
    
        // Properties can also have an automatically generated name.
        // In this case 'MyNode2D.Other'.
        @Metadata
        val OtherProperty = PropertyType(String::class.java)
    }
    
    Copy to clipboard
  4. (Optional) Create custom message types.

    • Define the message type.

      @Metadata
      public static final MessageType ExampleMessage =
          new MessageType("Message.MyNode2D.Example", MessageRouting.MessageRoutingBubbling);
      
      Copy to clipboard
      companion object {
          ...
      
          @Metadata
          val ExampleMessage =
              MessageType("Message.MyNode2D.Example", MessageRouting.MessageRoutingBubbling)
      }
      
      Copy to clipboard
    • Add a message handler.

      // Add message subscription.
      addMessageHandler(MyNode2D.ExampleMessage,
                new Node.MessageSubscriptionFunction()
                {
                   @Override
                   public void handle(MessageArguments messageArguments)
                   {
                       ...
                   }
                });
      
      Copy to clipboard
      // Add message subscription.
      addMessageHandler(KotlinNode2D.ExampleMessage,
           MessageSubscriptionFunction { messageArguments ->
              ...
           })
      
      Copy to clipboard
  5. (Optional) Define custom node behavior by overriding relevant methods.

    @Override
    protected void arrangeOverride(Vector2 actualSize)
    {
        super.arrangeOverride(actualSize);
    
        ...
    }
    
    Copy to clipboard
    override fun arrangeOverride(actualSize: Vector2) {
        super.arrangeOverride(actualSize)
    
        ...
    }
    
    Copy to clipboard

Creating a custom resource manager protocol

To load resources using Java or Kotlin, implement a custom resource manager protocol.

ResourceManager resourceManager = domain.getResourceManager();
resourceManager.registerProtocolHandler("MyProtocol",
    new ResourceManager.ProtocolHandler()
    {
        @Override
        public Result handle(ResourceManager resourceManager, String url, String protocol,
                             String hostname, String path) {
            if (some condition)
            {
                // Load a resource synchronously.
                Resource resource = ...;
                return new Result(resource);
            }
            else
            {
                // Use load task for asynchronous loading.
                return new Result(new ResourceManager.LoadTask() {
                    @Override
                    public void loadFunction() {
                        // Perform thread-independent loading.
                    }

                    @Override
                    public void finishFunction() {
                        // Create and store the resource.
                    }

                    @Override
                    public Resource getResult() {
                        // Return the created resource.
                    }
                });
            }
        }
    });
Copy to clipboard
val resourceManager = domain.resourceManager
resourceManager.registerProtocolHandler("MyProtocol")
{ resourceManager, url, protocol, hostname, path ->
    if (some condition) {
        // Load a resource synchronously.
        val resource = ...
        ProtocolHandler.Result(resource)
    } else {
        // Use load task for asynchronous loading.
        ProtocolHandler.Result( object: ResourceManager.LoadTask {
            override fun loadFunction(resourceManager: ResourceManager) {
                // Perform thread-independent loading.
            }

            override fun finishFunction(resourceManager: ResourceManager) {
                // Create and store the resource.
            }

            override fun getResult(): Resource {
                // Return the created resource.
            }
        })
    }
}
Copy to clipboard

In case of failure, the resource protocol handle can throw an exception, which the Kanzi Resource Manager handles.

By default the LoadTask will be call both the loadFunction and finishFunction. By passing ResourceLoadTaskType.FinishOnly to the constructor, the LoadTask will only call the finishFunction.

Creating a data source

A data source enables you to access third-party data from your Kanzi application.

To create a data source:

  1. Create the DataSource class.

    import com.rightware.kanzi.DataSource;
    
    class MyDataSource extends DataSource
    {
        @Metadata
        public static final Metaclass metaclass = new Metaclass("MyPlugin.MyDataSource");
    
        public static ObjectRef<MyDataSource> create(Domain domain, String name)
        {
            return DataSource.createDerived(domain, name, MyDataSource.metaclass);
        }
    
        private MyDataSource(Domain domain, long nativeHandle, Metaclass metaclass)
        {
            super(domain, nativeHandle, metaclass);
        }
    
        @Override
        protected void initialize()
        {
            super.initialize();
    
            createData();
        }
    }
    
    Copy to clipboard
    class MyDataSource private constructor(domain: Domain, nativeHandle: Long, metaclass: Metaclass)
        : DataSource(domain, nativeHandle, metaclass) {
    
        override fun initialize() {
            super.initialize()
            createData()
        }
    
        companion object {
            @Metadata
            val metaclass = Metaclass("MyPlugin.MyDataSource")
    
            @JvmStatic
            fun create(domain: Domain, name: String): ObjectRef<MyDataSource> {
                return createDerived(domain, name, metaclass)
            }
        }
    }
    
    Copy to clipboard
  2. Add the required members, accessors, and the method to initialize the data.

    private ObjectRef<DataObject> mDataRef;
    private DataObject mData;
    private DataObjectBool mMyBool;
    
    public DataObject getData()
    {
        return mData;
    }
    
    private void createData()
    {
        Domain domain = getDomain();
        mDataRef = DataObject.create(domain, "root");
        mData = mDataRef.get();
    
        // Add the remaining structure of the DataSource.
        try (ObjectRef<DataObjectBool> objRef = DataObjectBool.create(domain, "MyBoolean", true))
        {
            mMyBool = objRef.get();
            mData.addChild(mMyBool);
        }
    }
    
    Copy to clipboard
     private val mDataRef: ObjectRef<DataObject>? = null
     private val mData: DataObject? = null
     private val mMyBool: DataObjectBool? = null
    
     override fun getData(): DataObject? {
        return mData
     }
    
     private fun createData() {
        mDataRef = DataObject.create(domain, "root")
        mData = mDataRef.get()
    
        // Add the remaining structure of the DataSource.
        DataObjectBool.create(domain, "MyBoolean", true).use { objRef ->
            mMyBool = objRef.get()
            mData.addChild(mMyBool)
        }
    }
    
    Copy to clipboard

To learn more about the data sources in Kanzi, see Data sources.

Creating Kanzi Engine Java plugins

You can combine custom Java or Kotlin types into a Kanzi Engine plugin.

To create a Kanzi Engine plugin in Java or Kotlin:

  1. Create a class for the plugin.

    class TestPlugin extends Plugin
    {
        @Override
        public String getName()
        {
            return "testplugin";
        }
    }
    
    Copy to clipboard
    class TestPlugin : Plugin() {
        override fun getName(): String {
            return "testplugin"
        }
    }
    
    Copy to clipboard

    Tip

    The string returned from getName() must match the name of the Jar file.

  2. Add metadata for the custom types.

    Register the metadata of all the custom Kanzi types in that plugin.

    @Override
    public void registerClasses(MetaclassRegistry metaclassRegistry)
    {
        metaclassRegistry.registerClass(ExampleNode2D.class);
    }
    
    Copy to clipboard
    override fun registerClasses(metaclassRegistry: MetaclassRegistry) {
        metaclassRegistry.registerClass(ExampleNode2D::class.java)
    }
    
    Copy to clipboard

    Tip

    You can override an existing type by using the overrideClass instead of registerClass().

  3. Load the plugin in your Android application.

    For example, load the plugin during the initialization of an Activity.

    @Override
    protected void onCreate(Bundle icicle)
    {
        ...
    
        KanziRuntime.Reference runtimeRef = KanziRuntime.acquire(this);
        runtimeRef.Get().getDomain().registerPlugin(new TestPlugin());
    
        ...
    }
    
    Copy to clipboard
    override fun onCreate(icicle: Bundle) {
        ...
    
        val runtimeRef = KanziRuntime.acquire(this)
        runtimeRef.Get().domain.registerPlugin(new TestPlugin());
    
        ...
    }
    
    Copy to clipboard

To learn more about creating Kanzi Engine plugins with Java or Kotlin, see Creating a Java Kanzi Engine plugin manually.