Developing with the Kanzi Android framework (droidfw)

Use the Kanzi Android framework (droidfw) when you want to create an application for the Android platform, and you intend to use Android APIs and services extensively.

Kanzi Android framework (droidfw) is a framework dedicated for developing Kanzi applications for Android. It exposes the Kanzi Java API, which allows you to write application and plugin code entirely in Java or Kotlin. You do not need to write any C++ or JNI code, but you can still use native Kanzi plugins. Kanzi Android framework (droidfw) provides strong integration with the Android UI, including support for multiple simultaneous Kanzi-based Views and flexible composition of Kanzi and Android UI elements. Kanzi Android framework (droidfw) integrates with the Android Choreographer which runs the Kanzi main loop tasks in the Android UI thread. This enables you to use the Kanzi Java API from your application code without the need for dispatcher mechanisms.

Learn how to start developing Kanzi applications for Android with the Kanzi Android framework (droidfw) by completing the Tutorial: Getting started with Kanzi Android framework (droidfw).

Creating an Android application in Kanzi Studio

In Kanzi Studio in the New Project window, select the template for your Android application:

When you create a project with one of these templates, Kanzi Studio creates a complete Android application project in the <ProjectName>/Application/configs/platforms/android_gradle directory.

To learn how to build and deploy your application, see Deploying Kanzi applications to Android.

Adding the Kanzi Android framework (droidfw) to an existing Android application

This procedure assumes that your Android application project uses the default directory structure.

To add the Kanzi Android framework (droidfw) to an existing Android application:

  1. Copy the contents of <ProjectName>/Application/bin directory of your Kanzi Studio project to the app/src/main/assets directory of your Android Studio project.

    This way you import your kzb files into an Android package.

  2. Introduce cmake to your project. This way you can link native Kanzi Engine plugins to your application.

    1. Add to the app/src/main/cpp/CMakeLists.txt file:

      cmake_minimum_required(VERSION 3.5.1)
      project(<ProjectName>)
      
      if(NOT KANZI_ENGINE_BUILD)
          find_package(Kanzi REQUIRED CONFIG CMAKE_FIND_ROOT_PATH_BOTH
              HINTS "$ENV{KANZI_HOME}/Engine/lib/cmake")
      endif()
      
      include(kanzi-common)
      
      add_executable(<ProjectName>
                            # This file is only needed because cmake does not allow an executable without any sources.
                            <ProjectName>.cpp)
      
      target_link_libraries(<ProjectName> -Wl,--whole-archive Kanzi::kzdroidfw -Wl,--no-whole-archive)
      
      target_link_libraries(<ProjectName> Kanzi::kzui Kanzi::kzcoreui Kanzi::kzjava Kanzi::kzinterop)
      
      target_link_font_engine_backends(<ProjectName>)
      
      set_target_properties(<ProjectName> PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
      set_target_properties(<ProjectName> PROPERTIES VS_DEBUGGER_ENVIRONMENT "${KANZI_VS_DEBUGGER_ENVIRONMENT}")
      
      install_kanzi_libs_to_output_directory()
      install_kzbs_to_output_directory(${CMAKE_SOURCE_DIR}/bin)
      install_target_to_output_directory(<ProjectName>)
      
    2. Add to the app/src/main/cpp directory an empty <ProjectName>.cpp file.

  3. Copy the getkanzi.gradle file from the <KanziWorkspace>/Templates/Android_application_template/Application/configs/platforms/android_gradle directory to your Android project root directory.

    Gradle uses the getkanzi.gradle file to find Kanzi Engine and Kanzi Gradle plugins.

  4. In the build.gradle file in the Android project root directory, get the Kanzi Gradle plugins and add Kanzi as a dependency:

    buildscript {
        apply from: 'getkanzi.gradle'
    
        repositories {
            google()
            mavenCentral()
            flatDir { dirs getKanziPlugins().toString() }
        }
    
        dependencies {
              classpath "com.android.tools.build:gradle:4.1.3"
              classpath "com.rightware.gradle:kanzi:0.7.1"
        }
    
  5. In the app/build.gradle file of your Android Studio project, add the Kanzi Gradle plugin, and import the Kanzi Android framework (droidfw) and its dependencies:

    apply plugin: 'com.rightware.gradle.kanzi'
    ...
    
    android {
        ...
    
        defaultConfig {
          ...
    
            externalNativeBuild {
                cmake {
                    arguments "-DKANZI_LINK_FREETYPE=ON"
                    arguments "-DKANZI_LINK_ITYPE=OFF"
                }
            }
        }
        ...
    
        externalNativeBuild {
            cmake {
                path file('src/main/cpp/CMakeLists.txt')
            }
        }
    }
    ...
    
    kanzi {
        appFramework 'kanziruntime-droidfw'
    }
    
    dependencies {
        ...
        implementation 'com.rightware.kanzi:kzjava@aar'
    }
    
  6. In the app/build.gradle file of your Android Studio project, ensure that the Java language version is set to 8 (1.8) or higher:

    android {
        ...
    
        compileOptions {
           sourceCompatibility JavaVersion.VERSION_1_8
           targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    

Adding a Kanzi view

Kanzi exposes the KanziSurfaceView and KanziTextureView view types derived from the Android SurfaceView and TextureView respectively.

  • KanziSurfaceView renders directly to a surface obtained from hardware composer, which makes its performance better. But it comes with the restriction that an application can render it only behind or on the top of the rest of the views in an Activity.

  • KanziTextureView renders to a texture like a regular view. This allows an application to mix its content freely with Android content and you can move, transform, animate, and even make it translucent.

See Android SurfaceView vs TextureView or Creating a transparent view.

You can declare a KanziView in the Android layout and inflate it anywhere in the same way as a regular Android view.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">

   <com.rightware.kanzi.KanziSurfaceView
      android:id="@+id/view1"
      app:name="View1"
      app:kzbPathList="project1.kzb"
      app:startupPrefabUrl="kzb://project1/Prefabs/View1" />

Configuring a Kanzi view

These view types expose various configurations that you can assign directly from a layout.

Attribute

Use

Format

Default value

name

Name of the view.

string

Kanzi View

kzbPathList

Comma-separated list of kzb files. When set, Kanzi automatically loads these files when the view is attached. You can also pass a cfg file that contains a list of kzb files that you want to load.

string

null

startupPrefabUrl

URL of the startup prefab. When set, Kanzi asynchronously loads and instantiates this prefab as a child of the view.

string

null

clearColor

Color used to clear the surface.

color

0xff000000

clearEnabled

Flag to enable the clearing of the surface.

boolean

True

Alternatively, you can assign these configuration options with setters after inflation.

public class Activity1 extends FragmentActivity {

    @Override
    protected void onCreate(Bundle) {
        ...

        // Inflate the content from the layout.
        setContentView(R.layout.activity1);

        // Get the reference to the view.
        KanziView view = findViewById(R.id.view1);

        // Set the name.
        view.setName("View1");

        // Set the kzb paths
        view.setKzbPathList(new String[]{"project1.kzb"});

        // Set the startup prefab.
        view.setStartupPrefabUrl("kzb://project1/Prefabs/View1");
class CarMenu : FragmentActivity() {

    override fun onCreate(icicle: Bundle?) {
        ...

        // Inflate the content from the layout.
        setContentView(R.layout.activity1)

        // Get the reference to the view.
        val view: KanziView = findViewById(R.id.view1)

        // Set the name.
        view.setName("View1")

        // Set the kzb paths
        view.setKzbPathList(arrayOf("project1.kzb"));

        // Set the startup prefab.
        view.setStartupPrefabUrl("kzb://project1/Prefabs/View1")

It is also possible to explicitly load kzb files during the activity onCreate function.

@Override
protected void onCreate(Bundle) {
   ...

   // Acquire a reference to the KanziRuntime.
   mRuntimeRef = KanziRuntime.acquire(this);

   // Load the kzb.
   try {
       mRuntimeRef.get().loadKzb("project1.kzb");
   } catch (FileNotFoundException e) {
       Log.e(e.getMessage());
       return;
   }

   // Inflate the content from the layout.
   setContentView(R.layout.activity1);
override fun onCreate(icicle: Bundle?) {
   ...

   // Acquire a reference to the KanziRuntime.
   mRuntimeRef = KanziRuntime.acquire(this)

   // Load the kzb.
   try {
       mRuntimeRef.get().loadKzb("project1.kzb")
   } catch (FileNotFoundException e) {
       Log.e(e.getMessage())
       return
   }

   // Inflate the content from the layout.
   setContentView(R.layout.activity1)

Accessing the Kanzi Java API

When the node tree of your Kanzi view is initialized, Kanzi checks whether the root node of that node tree is a Screen node. If the root node is not a Screen node, Kanzi creates a Screen node and attaches the node tree to that Screen node. Kanzi uses the Screen node to handle input and focus in the Kanzi view.

In a Kanzi view, to get the child of the Screen node, use the getRoot method. From this Root node, you have access to the node tree with Kanzi Java API.

// Get root.
Node root = view.getRoot();

// Look up a node using an alias.
Node button = root.lookupNode("#Button");

// Set property.
button.setProperty(ButtonConceptMetadata.ToggleStateProperty, 0);

// Add message handler.
button.addMessageHandler(ButtonConceptMetadata.ToggleStateMessage,
                           new Node.MessageSubscriptionFunction() {
                           @Override
                           public void handle(MessageArguments messageArguments) {
                              int toggleState = messageArguments.getArgument(ButtonConceptMetadata.ToggleStateProperty);
                              Log.i("Button State is " + toggleState);
                           }});
// Get root.
val root = view.getRoot();

// Look up a node using an alias.
val button = root.lookupNode("#Button");

// Set property.
button!!.setProperty(ButtonConceptMetadata.ToggleStateProperty, 0);

// Add message handler.
button.addMessageHandler(ButtonConceptMetadata.ToggleStateMessage,
    Node.MessageSubscriptionFunction { messageArguments ->
        val toggleState =
            messageArguments.getArgument(ButtonConceptMetadata.ToggleStateProperty)
        Log.i(TAG, "Button State is $toggleState")
    })

Because the Kanzi Android framework (droidfw) runs in the Android UI thread, it is safe to invoke the Kanzi Java API interfaces directly from your application code. If you must use Kanzi from a different thread, you can post a task to the Android UI thread using View.post(Runnable) or similar functionality. To learn more about Kanzi Java API, see Using Java and Kotlin.

Observing state changes

You can implement KanziViewListener to listen for state changes in KanziView. Use this to inject custom initialization and un-initialization logic.

For example:

  • Use the onAttachedToWindow() callback to access domain, register Kanzi Engine Java plugins or manually setup the view's node tree.

view.addListener(new KanziViewListener() {
   ...

   @Override
   public void onAttachedToWindow(View view, Domain domain)
   {
         // Register a plugin.
         domain.registerPlugin(new MyPlugin());

         // Manually setup the node tree.
         // Instantiate a node.
         ObjectRef<TextBlock2D> text = TextBlock2D.create(domain, "text");
         // Set as root of the view.
         ((KanziView) view).setRoot(text.get());
         // Set properties on a node.
         text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!");
view.addListener(object : KanziViewListener {
   ...

   override fun onAttachedToWindow(view: View, domain: Domain) {
         // Register a plugin.
         domain.registerPlugin(MyPlugin())

         // Manually setup the node tree.
         // Instantiate a node.
         val text = TextBlock2D.create(domain, "text")
         // Set as root of the view.
         (view as KanziView).root = text.get()
         // Set properties on a node.
         text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!")
  • Use the onStartupPrefabLoaded() callback to access and modify the node tree instantiated from the startup prefab that you have configured.

view.addListener(new KanziViewListener() {
   ...

   @Override
   public void onStartupPrefabLoaded(View view, Domain domain)
   {
         // Look up a child node starting from the root.
         TextBox2D text = ((KanziView)view).getRoot().lookupNode("#Text");
         // Set properties on a node.
         text.setProperty(TextBox2D.TextProperty, "Hello world!");
view.addListener(object : KanziViewListener {
   ...

   override fun onStartupPrefabLoaded(view: View, domain: Domain) {
         // Look up a child node starting from the root.
         val text = (view as KanziView).root.lookupNode<TextBox2D>("#Text")
         // Set properties on a node.
         text.setProperty(TextBox2D.TextProperty, "Hello world!")

To learn more about Kanzi Engine Java plugins, see Creating Kanzi Engine Java plugins.

Attaching Kanzi to an existing view

You can instantiate and attach a KanziViewAdapter to an existing Android SurfaceView or TextureView.

@Override
protected void onCreate(Bundle) {
   ...

   // Get reference to the view.
   SurfaceView view = findViewById(R.id.view1);

   // Instantiate an adapter and assign a view.
   mAdapter = new KanziViewAdapter(view, "View1");

   // Assign a startup prefab.
   view.setStartupPrefabUrl("kzb://project1/Prefabs/View1");
override fun onCreate(icicle: Bundle?) {
    ...

    // Get reference to the view.
    val view = findViewById(R.id.view1)

    // Instantiate an adapter and assign a view.
    mAdapter = KanziViewAdapter(view, "View1");

    // Assign a startup prefab.
    view!!.setStartupPrefabUrl("kzb://project1/Prefabs/View1");

Using Kanzi from view-less contexts

You can also instantiate and use a KanziViewAdapter without a view. For example, from a WallpaperService.

class WallpaperEngine1 extends WallpaperService.Engine {
    KanziViewAdapter mAdapter;

    @Override
    public void onCreate(SurfaceHolder) {
        ...
        // Instantiate an adapter.
        mAdapter = new KanziViewAdapter(getApplicationContext(), "WallpaperEngine1");

        // Assign a startup prefab.
        mAdapter.setStartupPrefabUrl("kzb://project1/Prefabs/Wallpaper");
private inner class WallpaperEngine1 : Engine() {
    private var mAdapter: KanziViewAdapter? = null

    override fun onCreate(surfaceHolder: SurfaceHolder) {
        ...
        // Instantiate an adapter.
        mAdapter = KanziViewAdapter(applicationContext, "WallpaperEngine1")

        // Assign a startup prefab.
        mAdapter!!.setStartupPrefabUrl("kzb://project1/Prefabs/Wallpaper")

Handling input

While KanziSurfaceView and KanziTextureView handle input events by default, an arbitrary view using KanziViewAdapter must manually forward any input events that it wants Kanzi to process, through the handleTouchEvent(), handlekeyEvent(), and handleFocusChange() interfaces.

All Kanzi views by default set focusableInTouchMode to True, unless otherwise set programmatically or through layout attributes. This makes them gain and keep focus when clicked on. See Android touch mode.

Handling orientation change

When the screen orientation changes, an Android application, by default, destroys and recreates the activity. This allows an application to use an alternate layout for portrait and landscape screens, but it also means that you lose all internal Kanzi states.

You can override this by adding the android:configChanges="orientation" property to the AndroidManifest.xml file. With this option, the activity is no longer destroyed on orientation changes, instead onConfigurationChanged() is called on the activity. Forward this event manually to handleOrientationChange(). This option avoids recreating the Kanzi controls and therefore all internal state is retained. See Android configuration changes.

Handling the Android lifecycle

While KanziSurfaceView and KanziTextureView handle window and surface events by default, an arbitrary view using KanziViewAdapter must manually forward the window and surface events it wants Kanzi to process, through the handleAttachedToWindow(), handleSurfaceCreated(), handleSurfaceChanged(), handleSurfaceDestroyed(), and handleDetachedFromWindow() interfaces.

Handling the user-visibility of views

Kanzi Android framework (droidfw) automatically and continuously tracks whether each connected view is visible to the user, and does not render the surface of hidden views.

To check whether a view is visible to the user, Kanzi Android framework (droidfw) considers:

  • The visibility state of the view

  • The visibility state of all ancestors of the view

  • The alpha of the view

  • The effectively visible portion of the view computed through View.getGlobalVisibleRect()

An arbitrary view using KanziViewAdapter must manually forward visibility events that it wants Kanzi to process through the handleVisibilityChanged() interface.

Creating a transparent view

A KanziView can show transparent content that allows the content behind that view to be visible.

To create a transparent view:

  1. To enable transparency for a KanziView, edit the onCreate method of the view activity:

    • For KanziSurfaceView call the setZOrderOnTop and setFormat methods.

      @Override
      protected void onCreate(Bundle) {
          ...
      
          // Get reference to the view.
          KanziSurfaceView view = findViewById(R.id.view1);
      
          // Add transparency support to the view.
          view.setZOrderOnTop(true);
          view.getHolder().setFormat(PixelFormat.TRANSPARENT);
      
      override fun onCreate(icicle: Bundle?) {
          ...
      
          // Get reference to the view.
          val view = findViewById(R.id.view1);
      
          // Add transparency support to the view.
          view!!.setZOrderOnTop(true);
          view!!.getHolder().setFormat(PixelFormat.TRANSPARENT);
      
    • For KanziTextureView call the setOpaque method.

      @Override
      protected void onCreate(Bundle) {
          ...
      
          // Get reference to the view.
          KanziTextureView view = findViewById(R.id.view1);
      
          // Add transparency support to the view.
          view.setOpaque(false);
      
      override fun onCreate(icicle: Bundle?) {
          ...
      
          // Get reference to the view.
          val view = findViewById(R.id.view1);
      
          // Add transparency support to the view.
          view!!.setOpaque(false);
      
  2. In the Android layout file, update the KanziView clear color to have transparency.

    app:clearColor="@android:color/transparent"
    
  3. Edit the application.cfg to require a surface with alpha bits. See Surface properties.

    SurfaceBitsAlpha = 8
    
  4. In Kanzi Studio select the node that shows the transparent content and in the Properties set the Background Brush property to < No Brush >.

    ../../_images/background-no-brush.png

Creating multiple views

You can activate multiple Kanzi-based views simultaneously within an app and access them across different Activities or Fragments.

Sharing resources between views

All views in the same app share a single KanziRuntime along with all the loaded kzb files and resources. KanziRuntime exposes the acquire() interface to access the current instance or create a new instance, if it has not already been created.

Unloading Kanzi Engine

When there are no active views that contain Kanzi content, you can unload Kanzi Engine from the memory.

The KanziRuntime.Reference class performs reference counting on the active KanziRuntime instance. Each attached KanziView automatically keeps a Reference to KanziRuntime, which is closed only when the view gets detached.

When all the Reference instances have been closed the KanziRuntime is unloaded.

If the KanziRuntime has been unloaded, the next call to acquire() initializes a new instance of KanziRuntime, requiring the reload of all kzb files and resources.

Using Kanzi Engine Java plugins

To learn about creating Kanzi Engine Java plugins, see Creating Kanzi Engine Java plugins.

To use a plugin in your Android application, either:

  • 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());
    
        ...
    }
    
  • Load the plugin when your application attaches a Kanzi view that uses the plugin.

    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())
    
             ...
       }
       ...
    }
    

Using Kanzi Studio generated plugins

Kanzi Studio automatically generates plugins for some features, such as Code Behind, and adds the registration for these plugins to the KanziGenerated.java.

To use these plugins, during the initialization of your activities or the attachment of your Kanzi views, call KanziGenerated.registerPlugins(domain). This is similar to the registration of individual plugins in Using Kanzi Engine Java plugins.

Using Kanzi Engine C++ plugins

To use a Kanzi Engine C++ plugin:

  1. Export the Kanzi Engine plugin:

    1. In Kanzi Studio open the project that contains the plugin code.

      For example, open <KanziWorkspace>/Tutorials/Data sources/Completed/Tool_project/XML data source.kzproj.

    2. In the Library > Build Configurations select the plugin and in the Properties set:

      • CPU Architecture to match the architecture of your device

      • Build Profile to the configuration that you want to use

      ../../_images/library-build-configurations.png ../../_images/properties-build-configuration.png
    3. Select File > Export > Build Android Package.

      Kanzi Studio builds the Kanzi Engine plugin in <ProjectName>/Application/output/cmake/<PluginProjectName>/cmake/release/arm64-v8a/lib<PluginProjectName>.so.

      For example, if you use the XML data source plugin, Kanzi Studio builds the plugin in <KanziWorkspace>/Tutorials/Data sources/Completed/Application/output/cmake/XML_data_source/cmake/release/arm64-v8a/libXML_data_source.so.

      ../../_images/export-build-android-package.png
  2. Add the built Kanzi Engine plugin to an application:

    1. In Kanzi Studio select File > Close All projects and open or create a Kanzi Studio project using the Android application template.

      Android application template creates a Kanzi Studio project with a Kanzi Android framework-based application.

      ../../_images/close-all-projects.png ../../_images/new-project-android-application.png
    2. In the Library right-click Kanzi Engine Plugin, select Import Kanzi Engine Plugin, and select the dll of the built plugin.

      For example, select the <KanziWorkspace>/Tutorials/Data sources/Completed/Application/lib/Win64/GL_vs2019_Release_DLL/XML_data_source.dll plugin.

      This way you add Kanzi Engine plugin to a Kanzi Studio project.

      ../../_images/import-kanzi-engine-plugin1.png
    3. In Windows Explorer in the project to which you want to add this Kanzi Engine plugin, create this directory structure <ProjectName>/Application/lib/Android/release/arm64-v8a.

    4. Copy the Kanzi Engine so plugin file from <ProjectName>/Application/output/cmake/<PluginProjectName>/cmake/release/arm64-v8a/lib<PluginProjectName>.so to the <ProjectName>/Application/lib/Android/release/arm64-v8a directory.

      For example, copy the <KanziWorkspace>/Tutorials/Data sources/Completed/Application/output/cmake/XML_data_source/cmake/release/arm64-v8a/libXML_data_source.so file.

    5. Edit the <ProjectName>/Application/CMakeLists.txt file to add the Kanzi Engine plugin as an imported library dependency:

      add_library(<PluginProjectName> SHARED IMPORTED)
      set_target_properties(<PluginProjectName> PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/lib/Android/release/${ANDROID_ABI}/lib<PluginProjectName>.so")
      target_link_libraries(<ProjectName> <PluginProjectName>)
      

      For example, to add the XML data source plugin, use:

      add_library(XML_data_source SHARED IMPORTED)
      set_target_properties(XML_data_source PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/lib/Android/release/${ANDROID_ABI}/libXML_data_source.so")
      target_link_libraries(<ProjectName> XML_data_source)
      

      This way you add the built native Kanzi Engine plugin to your Android application.

  3. In Kanzi Studio select File > Export > Build Android Package.

    Kanzi Studio builds and deploys your application to an Android device. When application starts running on your Android device, in the logcat you can see when the application loads the plugin.

    For example, if you use the XML data source plugin, the logcat shows:

    I/Kanzi: [info:generic] Loading plugin 'libXML_data_source.so'
    
    ../../_images/export-build-android-package.png
  4. Use the plugin in the Kanzi Android framework (droidfw) application.

    For example, to learn how to use a Kanzi Engine data source plugin, see Using a data source.

Loading kzb files from a custom location

By default, Kanzi reads kzb files from the assets directory of an APK.

To set a different location, call KanziRuntime.setResourceDirectory(String path).

After this call, Kanzi reads kzb files from the specified location across all views of the application.

For example, to load kzb files from the external storage directory:

mRuntimeRef.get().setResourceDirectory(Environment.getExternalStorageDirectory().toPath());

To revert to the default location, call the interface with an empty string.