Using Java and Kotlin

Kanzi Java API implements platform-independent Java proxies for Kanzi classes, providing rich access to the Kanzi functionality in Java and Kotlin.

This enables you to create applications that are tightly integrated with the Android APIs. See Developing with the Kanzi Android framework (droidfw).

This topic provides an overview of the fundamentals of Kanzi Java API. For complete API reference, see Kanzi Java API reference.

Requirements

Kanzi Java API requires Java 8 (1.8) or higher.

Using properties

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

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

// Get property.
float opacity = node.getProperty(Node.OpacityProperty);
// Set property.
node.setProperty(Node.OpacityProperty, 0.5f)

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

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> dynamicProperty =
    new DynamicPropertyType<>("Node.Visible", Boolean.class);
node.setProperty(dynamicProperty, false);

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

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

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

Using messages

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

// Construct message arguments.
ButtonConceptMetadata.ToggleStateChangedMessageArguments args =
    new ButtonConceptMetadata.ToggleStateChangedMessageArguments();

// Set message arguments.
args.setToggleStateProperty(1);

// Dispatch message.
node.dispatchMessage(Button2D.ToggleStateMessage, args);
// Construct message arguments.
val args = ButtonConceptMetadata.ToggleStateChangedMessageArguments()

// Set message arguments.
args.toggleStateProperty = 1

// Dispatch message.
node.dispatchMessage(Button2D.ToggleStateMessage, args)

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);

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

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

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

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

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

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

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

// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage, messageArguments -> {
    // Extract arguments.
    Integer toggleState = messageArguments.getToggleStateProperty();
});
// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage)
{ messageArguments ->
    // Extract arguments.
    val toggleState = messageArguments.toggleStateProperty
}

See Using messages.

Using bindings

You can use bindings to set the value of a property or property field with the value from another property, property field, or a data source.

To create a one-way binding:

// Creates a one-way binding to the Opacity property of Node A.
AbstractBinding binding =
    BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty);
// Creates a one-way binding to the Opacity property of Node A.
val binding = BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty)

To set a binding to the property of a node:

// Sets the binding to Node B. Creates a binding runtime that you can use to remove the
// binding.
AbstractBindingRuntime runtime = nodeB.setBinding(binding, Node2D.OpacityProperty);
// Sets the binding to Node B. Creates a binding runtime that you can use to remove the
// binding.
val runtime = nodeB.setBinding(binding, Node2D.OpacityProperty)

To remove a binding from a node:

// Removes from Node B the binding to the Opacity property of Node A using the binding
// runtime.
nodeB.removeBinding(runtime);
// Removes from Node B the binding to the Opacity property of Node A using the binding runtime.
nodeB.removeBinding(runtime)

To create a callback binding processor to a binding and to set a two-way binding between properties of two nodes:

// Create a two-way binding to the Opacity property of Node A.
AbstractBinding binding =
    BindingBuilder.createTwoWay("../Node A", Node2D.OpacityProperty);

// Create a callback processor that accepts only float values greater than or equal to
// 0.5.
CallbackBindingProcessor processor =
    CallbackBindingProcessor.create(getDomain(), value -> {
        if (value instanceof Float)
        {
            return ((float) value >= 0.5f);
        }
        return false;
    });

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processor);

// Bind from the Opacity property of Node A to the Opacity property of Node B.
AbstractBindingRuntime runtime = nodeB.setBinding(binding, Node2D.OpacityProperty);

final float opacityAa = 0.333333f;
// Setting the opacity of Node A to less than 0.5 does not change the opacity of
// Node B.
nodeA.setOpacity(opacityAa);
final float opacityBa = nodeB.getOpacity();

final float opacityAb = 0.55555f;
// Setting the opacity of Node A to a value greater than or equal to 0.5 changes the
// opacity of Node B.
nodeA.setOpacity(opacityAb);
// Create a two-way binding to the Opacity property of Node A.
val binding = BindingBuilder.createTwoWay("../Node A", Node2D.OpacityProperty)

// Create a callback processor that accepts only float values greater than or equal to 0.5.
val processor = CallbackBindingProcessor.create(domain) { value: Any? ->
    if (value is Float) {
        return@create value >= 0.5f
    }
    false
}

// Add a callback processor to the binding. You must do this before adding the binding to a
// node.
binding.addProcessor(processor)

// Bind from the Opacity property of Node A to the Opacity property of Node B.
val runtime = nodeB.setBinding(binding, Node2D.OpacityProperty)
val opacityAa = 0.333333f
// Setting the opacity of Node A to less than 0.5 does not change the opacity of
// Node B.
nodeA.opacity = opacityAa
val opacityBa = nodeB.opacity
val opacityAb = 0.55555f
// Setting the opacity of Node A to a value greater than or equal to 0.5 changes the opacity of
// Node B.
nodeA.opacity = opacityAb

To listen to changes in a property's value using a callback processor:

// Create a one-way binding to an object's own Opacity property.
AbstractBinding binding = BindingBuilder.createOneWay(".", Node2D.OpacityProperty);

// Implement the action to be performed on change in the property as a callback
// processor.
CallbackBindingProcessor processor =
    CallbackBindingProcessor.create(getDomain(), value -> {
        Log.debug("Opacity value changed to: " + value);

        // The return does not matter here.
        return true;
    });

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processor);

// Add binding to the node you want to observe.
// Note the lack of target property and field.
node.setBinding(binding);
// Create a one-way binding to an object's own Opacity property.
val binding = BindingBuilder.createOneWay(".", Node2D.OpacityProperty)

// Implement the action to be performed on change in the property as a callback
// processor.
val processor = CallbackBindingProcessor.create(domain) { value: Any ->
    Log.debug("Opacity value changed to: $value")

    // The return does not matter here.
    true
}

// Add a callback processor to the binding. You must do this before adding the binding
// to a node.
binding.addProcessor(processor)

// Add binding to the node you want to observe.
// Note the lack of target property and field.
node.setBinding(binding)

To create, set, and remove bindings with an owner:

// Create two bindings.
AbstractBinding binding1 =
    BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty);
AbstractBinding binding2 =
    BindingBuilder.createOneWay("../Node A", Node2D.BackgroundBrushProperty);
// Bind to opacity and background brush properties, use Node A as owner.
AbstractBindingRuntime runtime1 =
    nodeB.setBindingWithOwner(binding1, nodeA, Node2D.OpacityProperty);
AbstractBindingRuntime runtime2 =
    nodeB.setBindingWithOwner(binding2, nodeA, Node2D.BackgroundBrushProperty);
// Remove bindings at the same time using Node A as owner.
nodeB.removeBindingsWithOwner(nodeA);
// Create two bindings.
val binding1 = BindingBuilder.createOneWay("../Node A", Node2D.OpacityProperty)
val binding2 = BindingBuilder.createOneWay("../Node A", Node2D.BackgroundBrushProperty)
// Bind to opacity and background brush properties, use Node A as owner.
val runtime1 = nodeB.setBindingWithOwner(binding1, nodeA, Node2D.OpacityProperty)
val runtime2 =
    nodeB.setBindingWithOwner(binding2, nodeA, Node2D.BackgroundBrushProperty)
// Remove bindings at the same time using Node A as owner.
nodeB.removeBindingsWithOwner(nodeA)

To create and set a modifier binding:

// Create a binding to Render Transformation property Translation X property field of
// Node A.
AbstractBinding binding = BindingBuilder.createOneWay("../Node A",
    Node2D.RenderTransformationProperty, PropertyField.PropertyFieldTranslationX);
// Bind it to Translation Y property field of the same node.
AbstractBindingRuntime runtime = nodeA.setModifierBinding(binding,
    Node2D.RenderTransformationProperty, PropertyField.PropertyFieldTranslationY);
// Create a binding to Render Transformation property Translation X property field of Node A.
val binding = BindingBuilder.createOneWay(
    "../Node A",
    Node2D.RenderTransformationProperty,
    PropertyField.PropertyFieldTranslationX
)
// Bind it to Translation Y property field of the same node.
val runtime = nodeA.setModifierBinding(
    binding,
    Node2D.RenderTransformationProperty,
    PropertyField.PropertyFieldTranslationY
)

See Bindings.

Creating instances

Concrete Kanzi Java API classes come with create methods that you can use to create instances.

Object lifetime

The ObjectRef class controls lifetime of instances of classes derived from KanziObject. The native backing for objects is not reclaimed until the ObjectRef is either explicitly closed, or allowed to be garbage collected. This native backing can also have other native owners that are managed by the native Kanzi Engine, therefore an ObjectRef does not exclusively control the lifetime of an 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");
// 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.name = "New Name"

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.
}
val domain = domain
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.
}

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.

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
    {
    }
    
    class MyNode2D private constructor(domain: Domain, nativeNode: Long, metaclass: Metaclass)
        : Node2D(domain, nativeNode, metaclass)
    {
        companion object {
        }
    }
    
  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");
      
      // You must place this inside the "companion object".
      
      @Metadata
      val metaclass = Metaclass("Example.MyNode2D")
      

      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);
      }
      
      // You must place this inside the "companion object".
      
      @JvmStatic
      fun create(domain: Domain, name: String): ObjectRef<MyNode2D> {
          return Node2D.createDerived(domain, name, metaclass)
      }
      
    • (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().
      }
      
      override fun initialize() {
          super.initialize()
      
          // Add custom initialization code after calling super.initialize().
      }
      
  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);
    
    // You must place this inside the "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)
    
  4. (Optional) Create custom message types.

    1. (Optional) Define a message argument class that holds the argument property types of your message. The message argument class that you define must derive from base class MessageArguments or another message argument class.

      You do not have to define a message argument class. Instead, you can use an existing message argument class, including the base class MessageArguments.

      @Metadata
      public static class ExampleMessageArguments extends MessageArguments
      {
          // Define metaclass.
          @Metadata
          public static Metaclass metaclass =
              new Metaclass("Example.MyNode2D.ExampleMessageArguments");
      
          // Define properties.
          @Metadata
          public static PropertyType<Integer> SomeProperty = new PropertyType<>(Integer.class);
      
          // Optionally, define property accessors. You can instead use the generic argument
          // accessors.
          public Integer getSomeProperty()
          {
              return getArgument(SomeProperty);
          }
          public void setSomeProperty(Integer value)
          {
              setArgument(SomeProperty, value);
          }
      
          // Define constructors.
          // A constructor with a long argument, which Kanzi uses to wrap native arguments.
          public ExampleMessageArguments(long nativeObject)
          {
              super(nativeObject);
          }
          // Any additional constructors for you to use when dispatching messages.
          public ExampleMessageArguments()
          {
              super();
          }
      }
      
      @Metadata
      class ExampleMessageArguments : MessageArguments {
          companion object {
              // Define metaclass.
              @Metadata
              var metaclass = Metaclass("Example.MyNode2D.ExampleMessageArguments")
      
              // Define properties.
              @Metadata
              val SomeProperty = PropertyType(Int::class.java)
          }
      
          // Optionally, define property accessors. You can instead use the generic argument accessors.
      
          fun getSomeProperty(): Int? {
              return getArgument(SomeProperty)?.toInt()
          }
      
          fun setSomeProperty(value: Int) {
              setArgument(SomeProperty, value)
          }
      
          // Define constructors.
      
          // A constructor with a long argument, which Kanzi uses to wrap native arguments.
          constructor(nativeObject: Long) : super(nativeObject)
      
          // Any additional constructors for you to use when dispatching messages.
          constructor() : super()
      }
      
    2. Define the message type.

      @Metadata
      public static final MessageType<ExampleMessageArguments> ExampleMessage =
          new MessageType<>("Message.MyNode2D.Example", MessageRouting.MessageRoutingBubbling,
              ExampleMessageArguments.class);
      
      // You must place this inside the "companion object".
      
      @Metadata
      val ExampleMessage = MessageType(
          "Message.MyNode2D.Example",
          MessageRouting.MessageRoutingBubbling,
          ExampleMessageArguments::class.java
      )
      
    3. Add a message handler.

      addMessageHandler(MyNode2D.ExampleMessage, messageArguments -> {
          // Extract arguments through custom accessors..
          Integer value = messageArguments.getSomeProperty();
          // .. or through generic accessors.
          value = messageArguments.getArgument(ExampleMessageArguments.SomeProperty);
      });
      
      addMessageHandler(UsingJavaSnippet.MyNode2D.ExampleMessage) { messageArguments ->
          // Extract arguments through custom accessors..
          var value = messageArguments.someProperty
          // .. or through generic accessors.
          value = messageArguments.getArgument(ExampleMessageArguments.SomeProperty)
      }
      
  5. (Optional) Define custom node behavior by overriding relevant methods.

    @Override
    protected void arrangeOverride(Vector2 actualSize)
    {
        super.arrangeOverride(actualSize);
    
        // Define custom behavior.
    }
    
    override fun arrangeOverride(actualSize: Vector2?) {
        super.arrangeOverride(actualSize)
        // Define custom behavior.
    }
    

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 (shouldLoadSynchronously)
            {
                // Load a resource synchronously.
                Resource resource = loadSynchronousResource();
                return new Result(resource);
            }
            else
            {
                // Use load task for asynchronous loading.
                return new Result(new ResourceManager.LoadTask() {
                    Resource resource = null;
                    @Override
                    public void loadFunction()
                    {
                        // Perform thread-independent loading.
                    }

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

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

            override fun finishFunction() {
                // Create and store the resource.
                resource = createResource()
            }

            override fun getResult(): Resource {
                // Return the created resource.
                return resource!!
            }
        })
    }
}

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

By default the LoadTask calls both the loadFunction and the finishFunction. To set the LoadTask to call only finishFunction, pass ResourceLoadTaskType.FinishOnly to the constructor.

Resource protocols can support the reload operation by implementing the ReloadProtocolHandler. Kanzi calls the reload handler to reinitialize and existing resource when the OpenGL context is invalidated.

ResourceManager resourceManager = domain.getResourceManager();
resourceManager.registerProtocolHandler("MyReloadProtocol",
    new ResourceManager.ProtocolHandler() {
        @Override
        public Result handle(ResourceManager resourceManager, String url, String protocol,
            String hostname, String path)
        {
            TextureBuilder builder = new TextureBuilder()
                                         .setName("Test Empty Texture")
                                         .setWidth(128)
                                         .setHeight(128);

            ObjectRef<Texture> texRef = builder.build(getDomain());
            return new Result(texRef.get());
        }
    },
    new ResourceManager.ReloadProtocolHandler() {
        @Override
        public void handle(ResourceManager resourceManager, String url, String protocol,
            String hostname, String path, Resource resource)
        {
            if (resource instanceof Texture)
            {
                TextureBuilder builder = new TextureBuilder()
                                             .setName("Reloaded Empty Texture")
                                             .setWidth(128)
                                             .setHeight(128);
                builder.recreate((Texture) resource);
            }
        }
    });
domain.resourceManager.registerProtocolHandler("MyReloadProtocol",
    { resourceManager, url, protocol, hostname, path ->
        val builder = TextureBuilder()
            .setName("Test Empty Texture")
            .setWidth(128)
            .setHeight(128)
        val texRef = builder.build(getDomain())
        ProtocolHandler.Result(texRef.get())
    },
    { resourceManager, url, protocol, hostname, path, resource ->
        if (resource is Texture) {
            val builder = TextureBuilder()
                .setName("Reloaded Empty Texture")
                .setWidth(128)
                .setHeight(128)
            builder.recreate(resource)
        }
    })

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.

    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();
        }
    
        private void createData() {}
    }
    
    class MyKotlinDataSource private constructor(
        domain: Domain,
        nativeHandle: Long,
        metaclass: Metaclass
    ) :
        DataSource(domain, nativeHandle, metaclass) {
        override fun initialize() {
            super.initialize()
            createData()
        }
    
        private fun createData() {}
    
        companion object {
            @Metadata
            val metaclass = Metaclass("MyPlugin.MyDataSource")
    
            @JvmStatic
            fun create(domain: Domain?, name: String?): ObjectRef<MyKotlinDataSource> {
                return createDerived(domain, name, metaclass)
            }
        }
    }
    
  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);
        }
    }
    
    private var mDataRef: ObjectRef<DataObject>? = null
    private var mData: DataObject? = null
    private var mMyBool: DataObjectBool? = null
    override fun getData(): DataObject {
        return mData!!
    }
    
    private fun createData() {
        val domain = domain
        mDataRef = DataObject.create(domain, "root")
        mData = mDataRef?.get()
        DataObjectBool.create(domain, "MyBoolean", true).use { objRef ->
            mMyBool = objRef.get()
            mData?.addChild(mMyBool)
        }
    }
    

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

Creating Kanzi Engine Java plugins

You can separate your Java or Kotlin types into Kanzi Engine Java plugins. This allows you to use these types from Kanzi Studio and reuse them between multiple applications.

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";
        }
    }
    
    class TestPlugin : Plugin() {
        override fun getName(): String {
            return "testplugin"
        }
    }
    

    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(MyDataSource.class);
    }
    
    override fun registerClasses(metaclassRegistry: MetaclassRegistry) {
        metaclassRegistry.registerClass(MyKotlinDataSource::class.java)
    }
    

    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 MyPlugin());
    
        ...
    }
    
    override fun onCreate(icicle: Bundle) {
        ...
    
        val runtimeRef = KanziRuntime.acquire(this)
        runtimeRef.get().domain.registerPlugin(new MyPlugin());
    
        ...
    }
    

    Or, when a Kanzi view that uses it is attached.

    view.addListener(new KanziViewListener() {
       ...
    
       @Override
       public void onAttachedToWindow(View view, Domain domain)
       {
             domain.registerPlugin(new MyPlugin());
    
    view.addListener(object : KanziViewListener {
       ...
    
       override fun onAttachedToWindow(view: View, domain: Domain) {
             domain.registerPlugin(MyPlugin())
    

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

Platform-specific code

Java plugins can run on multiple platforms, such as Kanzi Android framework (droidfw) and in the Kanzi Studio Preview. Make sure that the plugin runs platform-specific code only when it is running on a compatible platform. For example, call the Android API only when the plugin 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.
}
if (Platform.isAndroid()) {
    // Initialize the code path for when the plugin runs on Android.
    val context = domain.platformContext
} else {
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}

Using custom JNI extensions

You can implement custom JNI code to access areas of Kanzi Engine C++ API not yet covered by Kanzi Java API. To ease integration with JNI code, all relevant Kanzi Java API classes come with method long getNative(), which returns a pointer to their C++ counterpart. For the exact C++ type of the pointer, see the API reference for respective Kanzi Java API class.