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. On Android, you can use the Kanzi Java API directly from the Android UI thread without the need for dispatcher mechanisms. 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 metadata.NodeMetadata.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
with a full name of the 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)
To define a new property type, create a public static member instance of PropertyType
in your class, and annotate with Metadata
:
@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)
To learn more about the metadata that you can set for your property type, see Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.
See Property system.
Using enums¶
Using Kanzi Java API enums¶
Kanzi Java API comes with the set of enums associated with the built-in properties.
In Java code, for setter and getter functions, access Kanzi Java API enum values like this:
textNode.setProperty(TextBlock2D.FontHintingPreferenceProperty, FontHintingPreference.NativeHinting);
Node.HorizontalAlignment value = node.getProperty(Node.HorizontalAlignmentProperty);
Because of differences between Java and Kotlin in how interface inheritance works, accessing of the Kanzi Java API enums requires using a corresponding enum interface.
For example, with the metadata.NodeMetadata.HorizontalAlignmentProperty
property:
This works in both Java and Kotlin:
NodeEnums.HorizontalAlignment.Center;
This works only in Java:
Node.HorizontalAlignment.Center;
Using custom enums¶
If you declare a custom enum, to be able to set this enum as the data type of your custom property, the enum type must inherit Kanzi enum:
// Define a custom enum and inherit from Kanzi Enum. Make sure to import Kanzi Enum.
enum CustomEnum implements Enum
{
// Add enum values. Make sure to initialize them.
Value1(0),
Value2(1);
// Add a getter function. Make sure that it is public.
@Override
public int getValue()
{
return mValue;
}
// Add a constructor.
CustomEnum(int value)
{
mValue = value;
}
// The actual value.
int mValue;
}
@Metadata
@EditorInfo(displayName = "Custom Enum Property")
public static final PropertyType<CustomEnum> CustomEnumProperty =
new PropertyType<>("MyNode2D.CustomEnum", CustomEnum.class);
// Define a custom enum and inherit from Kanzi Enum. Make sure to import Kanzi Enum.
// Declare an integer value in the primary constructor.
enum class CustomEnum(private val mValue: Int) : Enum
{
// Add enum values. Make sure to initialize them.
Value1(0),
Value2(1);
// Add a getter function.
override fun getValue(): Int
{
return mValue;
}
}
@Metadata
@EditorInfo(displayName = "Custom Enum Property")
val CustomEnumProperty = PropertyType<CustomEnum>("MyNode2D.CustomEnum", CustomEnum::class.java);
Using messages¶
Kanzi Java API classes contain static members for built-in message types, such as metadata.ButtonConceptMetadata.ToggleStateChangedMessageArguments
.
To dispatch a message:
// Construct message arguments.
ToggleButton2D.ToggleStateChangedMessageArguments args =
new ToggleButton2D.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 react when a node receives a message, add a message handler to the node:
// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage, messageArguments -> {
// Extract arguments through accessors..
Integer toggleState = messageArguments.getToggleStateProperty();
// .. or through generic accessors
toggleState = messageArguments.getArgument(
ButtonConceptMetadata.ToggleStateChangedMessageArguments.ToggleStateProperty);
});
// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage)
{ messageArguments ->
// Extract arguments.
val toggleState = messageArguments.toggleStateProperty
}
To use a message by name, create an instance of AbstractMessageType
with the full name of the 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 define a new message type:
(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 the 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() }
Create a public static member instance of
MessageType
in your class, and annotate withMetadata
:@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 )
To learn more about the metadata that you can set for your message and message argument type, see Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.
See Using messages.
Creating instances¶
Concrete Kanzi Java API types come with create
methods that you can use to create instances.
The create
methods of object types, classes derived from KanziObject
, return an ObjectRef
instance that is an owning reference to the new object.
Managing instance lifetime¶
The ObjectRef
type enables shared ownership of an instance of KanziObject
derived type between the native backend and Java ObjectRef
instances. When all ObjectRef
instances are closed and no native references exist, the object is reclaimed. If application code attempts to access the object in this state, Kanzi throws a StaleObjectException
. To prevent this, maintain an ObjectRef
instance for the objects that you use.
Although ObjectRef
instances are automatically garbage collected, to have predictable memory consumption, you can manually close such instances.
// 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 throws 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 throws a StaleObjectException.
node.name = "New Name"
You can also associate the lifetime of an ObjectRef
instance to a scope using the Java try-with-resources statement or the 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.
}
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.
}
You can also create an owning ObjectRef
instance from a non-owning reference.
// Access an EmptyNode2D instance.
EmptyNode2D node = getNode(domain);
// Construct a Reference to the existing object.
// If the node is already stale, throws a StaleObjectException.
ObjectRef<EmptyNode2D> ref1 = new ObjectRef<>(node);
// Try to construct a Reference to the existing object.
// If the node is already stale, returns null.
ObjectRef<EmptyNode2D> ref2 = node.tryCreateObjectRef(EmptyNode2D.class);
// Access an EmptyNode2D instance.
val node = getNode(domain)
// Construct a Reference to the existing object.
// If the node is already stale, throws a StaleObjectException.
val ref1 = ObjectRef(node)
// Try to construct a Reference to the existing object.
// If the node is already stale, returns null.
val ref2 = node.tryCreateObjectRef<EmptyNode2D>(EmptyNode2D::class.java)
Multiple owning references¶
When ObjectRef
instances are passed between blocks of code, it can become unclear when to close an instance. Because Java passes instances by reference, what appears to be independent instances, could all refer to the same strong reference.
To avoid these issues, Kanzi uses the following guidelines with ObjectRef
instances and recommends the users to do the same:
When returning an
ObjectRef
instance as a return value from a method, the ownership is passed with the instance. Methods must either return a clone of the instance, or an unclosed local-only reference. This also means thatObjectRef
instances that are received from a method return, are owned by the calling code and can be closed when no longer needed or left for garbage collection.// When returning a member ObjectRef, return a clone. ObjectRef<EmptyNode2D> getNode1() { return mNode.clone(); } // You can return a local ObjectRef directly, but do not close it explicitly. ObjectRef<EmptyNode2D> getNode2() { ObjectRef<EmptyNode2D> localRef = EmptyNode2D.create(getDomain(), "My Empty Node"); return localRef; // Here, do not close the localRef. }
// When returning a member ObjectRef, return a clone. fun getNode1(): ObjectRef<EmptyNode2D> { return mNode.clone() } // You can return a local ObjectRef directly, but do not close it explicitly. fun getNode2(): ObjectRef<EmptyNode2D> { var localRef = EmptyNode2D.create(domain, "My Empty Node") return localRef; // Here, do not close the localRef. }
When passing an
ObjectRef
instance as an argument to a method, the ownership of that instance is retained with the caller. When implementing a method that takes anObjectRef
as an argument, the ownership is temporary. If the method stores the instance in a member variable, or otherwise wants to extend the lifetime of the object beyond the scope of the method, it must clone theObjectRef
instance.void myFunction(ObjectRef<Node2D> node) { // You can use the node as-is, except for closing or storing the node. // If you need to store the reference, make a clone. mNode = node.clone(); }
fun myFunction(node: ObjectRef<Node2D?>) { // You can use the node as-is, except for closing or storing the node. // If you need to store the reference, make a clone. mNode = node.clone() }
Creating a custom type¶
You can use Java or Kotlin to extend the functionality of Kanzi Engine by creating a custom type. You can either derive from one of the extensible Kanzi types or you can define an abstract type.
For example, to create a custom 2D node:
Derive a class from
Node2D
.static class MyNode2D extends Node2D { private MyNode2D(Domain domain, long nativeNode, Metaclass metaclass) { super(domain, nativeNode, metaclass); }
class MyNode2D private constructor(domain: Domain, nativeNode: Long, metaclass: Metaclass) : Node2D(domain, nativeNode, metaclass) { companion object {
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.Factory. Derived types must provide a static public
create
method with the specific arguments. The implementation of thecreate
method is usually a call to thecreateDerived
method on the type that you are extending.static public ObjectRef<MyNode2D> create(Domain domain, String name) { return Node2D.createDerived(domain, name, 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 your custom initialization code in your override of 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(). }
(Optional) Create custom property types.
(Optional) Create custom message arguments, types, and handlers.
(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. }
Implementing custom resource loading¶
To implement custom resource loading in Java or Kotlin, define 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.
ObjectRef<Resource> resource = loadSynchronousResource();
return new Result(resource);
}
else
{
// Use load task for asynchronous loading.
return new Result(new ResourceManager.LoadTask() {
ObjectRef<Resource> mResource = null;
@Override
public void loadFunction()
{
// Perform thread-independent loading.
}
@Override
public void finishFunction()
{
// Create and store the resource.
mResource = createResource();
}
@Override
public ObjectRef<? extends Resource> getResult()
{
// Return the created resource.
return mResource.clone();
}
@Override
public void close()
{
mResource.close();
}
});
}
}
});
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 mResource: ObjectRef<Resource>? = null
override fun loadFunction() {
// Perform thread-independent loading.
}
override fun finishFunction() {
// Create and store the resource.
mResource = createResource()
}
override fun getResult(): ObjectRef<out Resource>? {
// Return the created resource.
return mResource!!.clone()
}
override fun close() {
mResource!!.close()
}
})
}
}
In case of failure, the resource protocol ResourceManager.ProtocolHandler.handle
can throw an exception, which the Kanzi Resource Manager handles.
By default the ResourceManager.LoadTask
calls both the ResourceManager.LoadTask.loadFunction
and the ResourceManager.LoadTask.finishFunction
. To set the ResourceManager.LoadTask
to call only ResourceManager.LoadTask.finishFunction
, pass ResourceLoadTaskType.FinishOnly
to the constructor.
Resource protocols can support the reload operation by implementing the ResourceManager.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);
}
},
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)
},
{ 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:
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) } } }
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() { 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 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.
ObjectRef<CallbackBindingProcessor> processorRef =
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(processorRef.get());
// 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 processorRef = 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(processorRef.get())
// 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 property value changes 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.
ObjectRef<CallbackBindingProcessor> processorRef =
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(processorRef.get());
// 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 processorRef = 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(processorRef.get())
// 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 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:
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.Register your custom types with Kanzi.
@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 ofregisterClass()
.Load the plugin into your Android application.
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.
}
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 packaged into instances of NativeException
. It is not expected for Java code to handle these exceptions. Instead, allow the exceptions to propagate up the stack so that Kanzi retranslates them to a native exceptions that Kanzi Engine can handle.
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.
See also¶
Extending the functionality of Kanzi Engine with Java
Creating a Java Kanzi Engine plugin using a template