Tutorial: Data Sources for Android

In this tutorial you learn how to start developing a data source for Kanzi applications for Android. You learn how to:

  • Create a data source using Kanzi Java API.

  • Use Android APIs to back the data source.

  • Ensure compatibility with Kanzi Studio Preview where the Android APIs are not available.

This tutorial assumes that you are familiar with developing Android applications, Java programming language, and that you understand the basics of working with Kanzi Studio. The best entry points for getting familiar with Kanzi Studio are:

Before you start this tutorial, make sure that you have your Kanzi development environment for Android set up. For developing application logic with Kanzi Engine for Android, you need:

  • Android Studio 4.1.3 or newer

    You can download and install Android Studio from here.

  • Android Gradle plugin 4.1.3 or lower

  • Gradle 6.7.1 or lower

  • 64-bit JDK 8 (1.8.0) or newer

    We recommend using the JDK that is bundled with the Android Studio installation.

  • CMake 3.5.1 or newer

  • Android API level:

    • Kanzi Android framework (droidfw) requires Android API level 26 (Android version 8.0.0) or newer.

    • Kanzi application framework (appfw) requires Android API level 21 (Android version 5.0) or newer.

  • Java language version:

    • Kanzi Android framework (droidfw) requires Java 8 (1.8) or higher.

    • Kanzi application framework (appfw) requires Java 7 (1.7) or higher.

See Requirements for Android application development with Kanzi.

Assets for the tutorial

You can find the completed application of this tutorial in the <KanziWorkspace>/Tutorials/Android data source/Completed directory.

Create a project in Kanzi Studio

In this section you create a Kanzi application with an empty data source by creating a project in Kanzi Studio.

To create a project in Kanzi Studio:

  1. In Kanzi Studio create a project and in the New Project window set:

    • Name to Android data source

    • Template to Android application with Java plugin

      Android application with Java plugin template creates a Kanzi Studio project with a Kanzi Android framework-based application that contains a Kanzi Engine Java plugin.

      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.

    ../../_images/new-project-android-data-source.png
  2. In Android Studio open the Android data source/Application/configs/platforms/android_gradle project.

    ../../_images/android-studio-open-android-gradle.png

Configure one-way data source

In this section you create a widget that shows the battery level of the Android device.

To create the battery level widget:

  1. In Android Studio:

    1. In androiddatasourceplugin > java > com.rightware.kanzi.androiddatasourceplugin in the AndroidDataSourceDataSource class:

      1. Add a constant that defines the name of the battery level data object.

        static String BATTERY_LEVEL = "BatteryLevel";
        
      2. In the initialize() method, replace the existing data source setup

        root.addChild(DataObjectInt.create(domain, "Integer value", 1).get());
        root.addChild(DataObjectString.create(domain, "String value", "dummy value").get());
        root.addChild(DataObjectReal.create(domain, "Real value", 0.5).get());
        

        with

        root.addChild(DataObjectReal.create(domain, BATTERY_LEVEL, 0.5f).get());
        
    2. To build the Kanzi Engine plugin JAR used by Kanzi Studio, in the Gradle tool window in androiddatasource > androiddatasourceplugin > Tasks > kanzi run exportJarDebug or exportJarRelease.

      ../../_images/androiddatasourceplugin-jar-build.png
  2. In Kanzi Studio in the Node Tree delete the Viewport 2D node.

    You can delete the Viewport 2D node because you do not create any 3D content in this tutorial.

    ../../_images/delete-viewport-2d.png
  3. In the Rootpage create a Flow Layout 2D node and name it Controls.

    ../../_images/create-flow-layout-2d.png ../../_images/controls-in-node-tree.png
  4. In the Properties add and set the Horizontal Alignment and Vertical Alignment properties to Center.

    ../../_images/controls-centered.png
  5. In the Node Tree in the Controls node create an Empty Node 2D node, name it Battery Widget, and in the Properties add and set:

    • Layout Width to 150

    • Layout Height to 200

    ../../_images/battery-widget-in-node-tree.png ../../_images/battery-widget-properties.png
  6. From the Asset Packages > Factory Content drag the Progress Indicator to the Battery Widget node in the Node Tree.

    ../../_images/progress-indicator.png ../../_images/progress-indicator-instantiated.png
  7. In the Node Tree select the Progress Indicator prefab placeholder and in the Properties add and set:

    • Layout Width to 150

    • Layout Height to 150

    • Ring Thickness to 0.3

    ../../_images/progress-indicator-properties.png
  8. In the Node Tree in the Battery Widget node create a Text Block 2D node and in the Properties add and set:

    • Horizontal Alignment to Center

    • Vertical Alignment to Bottom

    • Text to Battery

    ../../_images/battery-text-block-2d.png ../../_images/text-block-2d-properties.png ../../_images/battery-level-initial-preview.png
  9. In the Library > Kanzi Engine Plugins right-click the AndroidDataSource plugin and select Update Kanzi Engine Plugin.

    This way you update the plugin and take into use the changes that you made in the Android Studio plugin project.

    ../../_images/update-java-plugin-metadata.png
  10. In the Window main menu select Data Sources.

    Use the Data Sources window to create, set, and delete the data sources in your project, and to connect data objects from a data source to nodes and resources in your project.

    ../../_images/window-data-sources.png ../../_images/data-sources-window.png
  11. Add the data source to the Kanzi Studio project:

    1. In the Data Sources window select Create Data Source and set:

      • Name to Android data source

      • Data Source Type to AndroidDataSourceDataSource

      ../../_images/create-data-source.png

      You can see the BatteryLevel data object in the Data Sources window.

      ../../_images/data-source-battery-level.png
    2. In the Node Tree select the RootPage node, in the Properties add the Data Context > Data Context property, and set it to Android data source.

      By setting the Data Context property you tell your application from which data source it receives data. When you set the Data Context property for a node, all its child nodes inherit the value of the Data Context property. If you want to use a different data context for one of the child nodes, add the Data Context property to that node and set it to the data context you want to use.

      ../../_images/root-page-in-node-tree.png ../../_images/root-page-data-context.png
  12. In the Node Tree select the Progress Indicator node, in the Properties click + Add Binding, and in the Binding Editor set:

    • Property to Progress

    • Expression to

      {DataContext.BatteryLevel} * 100
      
    ../../_images/battery-progress-binding.png ../../_images/battery-level-50-preview.png
  13. In Kanzi Studio select File > Export > Export KZB.

    ../../_images/export-kzb-binary1.png
  14. Update the data source to get the battery level from the Android system APIs.

    It is possible to create the data source with a pattern that is simpler than presented here. However, if the data source itself uses the Android API, it causes the Kanzi Studio Preview to terminate when using this plugin on Windows. This happens because the Android APIs are not available. This tutorial demonstrates a pattern that allows the Android APIs to be conditionally used on Android, while still allowing the data source to return dummy data when run in the context of Kanzi Studio Preview.

    1. In Android Studio, create an interface named DataProvider.

      package com.example.androiddatasourceplugin;
      
      interface DataProvider
      {
         float getBatteryLevel();
      }
      
    2. To create a dummy implementation of the interface that is used on non-Android platforms, create a class named DummyDataProvider.

      package com.example.androiddatasourceplugin;
      
      class DummyDataProvider implements DataProvider
      {
          @Override
          public float getBatteryLevel()
          {
              return 0.5f;
          }
      }
      
    3. To create an Android implementation of the interface that is used on Android, create a class named AndroidDataProvider.

      package com.example.androiddatasourceplugin;
      
      import android.content.Context;
      import android.os.BatteryManager;
      
      class AndroidDataProvider implements DataProvider
      {
          private final BatteryManager mBatteryManager;
      
          public AndroidDataProvider(Object context)
          {
              Context androidContext = (Context) context;
      
              mBatteryManager = (BatteryManager) androidContext.getSystemService(Context.BATTERY_SERVICE);
          }
      
          @Override
          public float getBatteryLevel()
          {
              int batLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
              return ((float) batLevel) / 100.0f;
          }
      }
      
    4. Update the AndroidDataSourceDataSource to use the new providers.

      • Import the Kanzi classes that provide the functionality for the content that you want create.

        import com.rightware.kanzi.Platform;
        
      • Add a data member for the data provider:

        private DataProvider mDataProvider;
        
      • In the initialize() method select the data provider:

        // Construct the proper DataProvider for the current platform.
        if (Platform.isAndroid())
        {
            mDataProvider = new AndroidDataProvider(getDomain().getPlatformContext());
        }
        else
        {
            mDataProvider = new DummyDataProvider();
        }
        
      • Update the definition of the battery level data object to retrieve the value from the interface:

        root.addChild(DataObjectReal.create(domain, BATTERY_LEVEL, mDataProvider.getBatteryLevel()).get());
        
  15. In Android Studio, build and run the app.

    When the app is running on the Android device, the battery widget displays the current battery level of the device.

    ../../_images/battery-level-on-device.png

Configure two-way data source

In this section you create a widget that you can use to control the WiFi connectivity state on the Android device.

To create a WiFi widget:

  1. In Android Studio update the data source to include the WiFi related information:

    1. In the DataProvider interface add:

      boolean isWifiEnabled();
      boolean isNetworkConnected();
      void setWifiEnabled(boolean enabled);
      
    2. In the DummyDataProvider class add:

      private boolean mWifiEnabled = false;
      
      @Override
      public boolean isWifiEnabled()
      {
         return mWifiEnabled;
      }
      
      @Override
      public boolean isNetworkConnected()
      {
          return mWifiEnabled;
      }
      
      @Override
      public void setWifiEnabled(boolean enabled)
      {
         mWifiEnabled = enabled;
      }
      
    3. In the AndroidDataProvider class:

      • Import the Android classes that provide the functionality for the content that you want create.

        import android.net.ConnectivityManager;
        import android.net.wifi.WifiManager;
        import android.net.NetworkCapabilities;
        
      • Add new members:

        private final ConnectivityManager mConnectivityManager;
        private final WifiManager mWifiManager;
        
      • Constructor changes:

        mConnectivityManager =
            (ConnectivityManager) androidContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        mWifiManager = (WifiManager) androidContext.getSystemService(Context.WIFI_SERVICE);
        
      • New methods:

        @Override
        public void setWifiEnabled(boolean enabled)
        {
            mWifiManager.setWifiEnabled(enabled);
        }
        
        @Override
        public boolean isWifiEnabled()
        {
            return mWifiManager.isWifiEnabled();
        }
        
        @Override
        public boolean isNetworkConnected()
        {
            NetworkCapabilities capabilities =
            mConnectivityManager.getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
            if (capabilities == null)
                return false;
            return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
        }
        
    4. In the AndroidDataSourceDataSource class:

      • Import the Kanzi classes that provide the functionality for the content that you want create.

        import com.rightware.kanzi.DataObjectBool;
        
      • Add new members:

        static String NETWORK_CONNECTED = "NetworkConnected";
        static String WIFI_ENABLED = "WifiEnabled";
        
      • In the initialize() function update the data source:

        root.addChild(
            DataObjectBool.create(domain, NETWORK_CONNECTED, mDataProvider.isNetworkConnected()).get());
        ObjectRef<DataObjectBool> enabledRef =
            DataObjectBool.create(domain, WIFI_ENABLED, mDataProvider.isWifiEnabled());
        root.addChild(enabledRef.get());
        
        // Register a Notification handler to update WiFi state when Data Source changes.
        DataObjectBool wifiEnabled = enabledRef.get();
        wifiEnabled.addModifiedNotificationHandler(new ModifiedSubscriptionFunction() {
            @Override
            public void handle()
            {
                mDataProvider.setWifiEnabled(wifiEnabled.getValue());
            }
        });
        
    5. In the androiddatasourceplugin > manifests > AndroidManifest.xml and app > manifests > AndroidManifest.xml files add these permissions:

      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
      <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      
    6. To build the Kanzi Engine plugin JAR used by Kanzi Studio, run the exportJarDebug or exportJarRelease Gradle target.

      ../../_images/androiddatasourceplugin-jar-build.png
  2. In Kanzi Studio in the Data Sources window click the Refresh button to read the updated data source contents from the plugin.

    ../../_images/refresh-data-source.png ../../_images/final-data-source.png
  3. Create the WiFi widget UI:

    1. In the Node Tree in the Controls node create an Empty Node 2D node, name it WiFi Widget, and in the Properties add and set:

      • Layout Width to 150

      • Layout Height to 200

      • Layout.Item > Horizontal Margin property Left property field to 40

      ../../_images/wifi-widget-in-node-tree.png ../../_images/wifi-widget-properties.png
    2. In the WiFi Widget node create an Image node, name it Icon Connected, in the Properties in the Image property click + Import Image... and select the <KanziWorkspace>/Tutorials/Android data source/Assets/WiFi-Connected.png file.

      • Layout Width to 150

      • Layout Height to 150

      ../../_images/icon-connected-in-node-tree.png ../../_images/icon-connected-properties.png
    3. In the Properties add a binding and set:

      • Property to Visible

      • Expression to

        BOOL({DataContext.NetworkConnected})
        

      With this binding you make the Icon Connected image visible when the value of the NetworkConnected data object is true.

      ../../_images/image-connected-binding.png
    4. In the Node Tree duplicate the Icon Connected node and rename it to Icon Disconnected.

      ../../_images/duplicate-icon-connected.png ../../_images/icon-disconnected-in-node-tree.png
    5. For the Icon Disconnected node:

      • Click the binding to edit it and in the Binding Editor add ! to the beginning of the binding expression.

        With this binding you make the Icon Disconnected image visible when the value of the NetworkConnected data object is false.

        ../../_images/image-disconnected-binding.png
      • Use the <KanziWorkspace>/Tutorials/Android data source/Assets/WiFi-Disconnected.png image.

        ../../_images/icon-disconnected-properties.png
    6. From the Asset Packages > Factory Content drag the Toggle Button to the WiFi Widget node in the Node Tree.

      ../../_images/toggle-button.png ../../_images/toggle-button-in-node-tree.png
    7. In the Node Tree select the Toggle Button node and in the Properties:

      • Add a binding and set:

        • Binding Mode to Two way

        • Property to Toggle State

        • Expression to

          {DataContext.WifiEnabled}
          

        You create a two-way connection between the toggle state of the Toggle Button node and the WifiEnabled data object.

        ../../_images/wifi-toggle-binding.png
      • Add the Vertical Alignment property and set it to Bottom

        ../../_images/toggle-button-properties.png
    ../../_images/battery-wifi-widget-preview.png
  4. In Kanzi Studio select File > Export > Export KZB.

  5. In Android Studio, build and run the app.

    When the app is running on the Android device, the WiFi widget displays the connected or disconnected icon based on the WiFi connectivity status of the device.

Update data sources based on external changes

Until now, the data read from Android at application startup is constant. This step updates the data source to repond to Android callbacks and update the datasource when the WiFi or battery level are changed.

  1. Create a new interface named DataChangeListener to accept data changes when they occur.

    interface DataChangeListener {
        void updateBatteryLevel(float batteryLevel);
        void updateNetworkConnection(boolean isConnected);
        void updateWifiEnabled(boolean isEnabled);
    }
    
  2. Modify AndroidDataProvider to receive the updated data from Android.

    • Import the Android classes that provide the functionality for the content that you want create.

      import android.content.BroadcastReceiver;
      import android.net.Network;
      import android.content.Intent;
      import android.content.IntentFilter;
      
    • Add a member that holds a reference to a DataChangeListener.

      private final DataChangeListener mDataChangeListener;
      
    • Modify the AndroidDataProvider constructor to receive and store a DataChangeListener.

      public AndroidDataProvider(Object context, DataChangeListener listener)
      {
         mDataChangeListener = listener;
      
    • Add listener callbacks for the WiFi state and battery level:

      // Battery level listener
      private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent)
          {
              int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
              int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
              float batteryLevel = level / (float) scale;
      
              mDataChangeListener.updateBatteryLevel(batteryLevel);
          }
      };
      
      // WiFi state listener that indicates whether WiFi is enabled.
      private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent)
          {
              int wifiStateExtra =
                  intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
              switch (wifiStateExtra)
              {
                  case WifiManager.WIFI_STATE_ENABLED:
                      mDataChangeListener.updateWifiEnabled(true);
                      break;
                  case WifiManager.WIFI_STATE_DISABLED:
                      mDataChangeListener.updateWifiEnabled(false);
                      break;
              }
          }
      };
      
      // Network state listener that indicates whether an Internet connection is availabile.
      private final ConnectivityManager.NetworkCallback mNetworkCallback =
          new ConnectivityManager.NetworkCallback() {
              @Override
              public void onAvailable(Network network)
              {
                  super.onAvailable(network);
                  NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
                  if (capabilities != null)
                  {
                     mDataChangeListener.updateNetworkConnection(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
                  }
              }
      
              @Override
              public void onLost(Network network)
              {
                  super.onLost(network);
                  mDataChangeListener.updateNetworkConnection(false);
              }
          };
      
    • To register the callbacks with Android, add this code to the AndroidDataProvider constructor:

      // To get the WiFi connectivity state changes, register the WiFi state receiver.
      IntentFilter intentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
      androidContext.registerReceiver(mWifiStateReceiver, intentFilter);
      
      // Listen to connectivity state changes.
      mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback);
      
  3. Modify AndroidDataSourceDataSource to handle data updates:

    • Change the class to implement the DataChangeListener interface.

      public class AndroidDataSourceDataSource extends DataSource implements DataChangeListener
      
    • Add an implementation of the DataChangeListener interface.

      private boolean mUpdateInProgress = false;
      
      @Override
      public void updateBatteryLevel(float batteryLevel)
      {
          DataObjectReal batteryObject =
              (DataObjectReal) mRoot.get().lookupDataContext(BATTERY_LEVEL);
          if (batteryObject != null)
          {
              mUpdateInProgress = true;
              batteryObject.setValue(batteryLevel);
              mUpdateInProgress = false;
          }
      }
      
      @Override
      public void updateNetworkConnection(boolean isConnected)
      {
          DataObjectBool networkConnected =
              (DataObjectBool) mRoot.get().lookupDataContext(NETWORK_CONNECTED);
          if (networkConnected != null)
          {
              mUpdateInProgress = true;
              networkConnected.setValue(isConnected);
              mUpdateInProgress = false;
          }
      }
      
      @Override
      public void updateWifiEnabled(boolean isEnabled)
      {
          DataObjectBool wifiEnabled = (DataObjectBool) mRoot.get().lookupDataContext(WIFI_ENABLED);
          if (wifiEnabled != null)
          {
              mUpdateInProgress = true;
              wifiEnabled.setValue(isEnabled);
              mUpdateInProgress = false;
          }
      }
      
    • In the initialize() method, add this as an argument to the AndroidDataProvider constructor.

      mDataProvider = new AndroidDataProvider(getDomain().getPlatformContext(), this);
      
    • In the initialize() method, modify the existing notification handler to add the mUpdateInProgress variable to prevent recursive updates.

      wifiEnabled.addModifiedNotificationHandler(new ModifiedSubscriptionFunction() {
          @Override
          public void handle()
          {
              if (!mUpdateInProgress)
              {
                  mDataProvider.setWifiEnabled(wifiEnabled.getValue());
              }
          }
      });
      
  4. In Android Studio, build and run the app.

    When the app is running on the Android device, the WiFi widget updates if the network connection state changes while the app is running. To test this, enable and disable network connections through the device UI, or use the toggle button in the WiFi widget.

See also

To learn more about developing Kanzi applications for Android, see Developing Kanzi applications for Android.

To learn more about using Java Kanzi Engine API, see Using Java and Kotlin.