How to use Yoctopuce Android library with Kotlin

How to use Yoctopuce Android library with Kotlin

The annual Google IO conference was held last week. During the different keynotes for Android, there was a specific focus on Kotlin, the new programming language for Android. As this language is becoming more and more popular, we decided to devote a post on it: we are going to see how to use our Android library in an Android application written in Kotlin.




Note, this post is not a Kotlin tutorial. To be honest, we have very little experience with this language. The aim of this post is to show you that you can easily use our library in this type of project. We assume that you already have some notions of Kotlin. If it is not the case, you should start by reading the language documentation and test your knowledge with this example series.

In the same way, if you have never used Yoctopuce modules, you can start by reading our tutorials, and more particularly the one on the logical structure of Yoctopuce devices.

For this post, we are going to implement the same application as for our post "How to start with Yoctopuce modules on Android", but writing the code in Kotlin instead of in Java. It's a very basic application displaying the current value of the Yoctopuce sensor connected to the USB port.

To implement this application, you must use a least version 3.0 of Android Studio. When creating the project, simply select Kotlin as the programming language.

When creating the project, you must select Kotlin as the programming language
When creating the project, you must select Kotlin as the programming language



When you have created your project, you must add the Yoctopuce library, modify the interface, modify the manifest, and write the code which displays the sensor value.

What remains the same


As for an Android application written in Java, we start by adding the Yoctopuce library to the project. The method doesn't change: we add a reference to our library in the build.gradle file of the application. When compiling, Android Studio automatically downloads and adds the latest version of the Yoctopuce library.

...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    implementation 'com.yoctopuce.android:YoctoLib:+'
}



Then we need to modify the AndroidManifest.xml file in order for the application to gain access to the USB port. To do so, we must add a <uses-feature android:name="android.hardware.usb.host"/> line in the manifest section of the file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools" package="com.example.myapplication">

  <application
     android:allowBackup="true"
     android:icon="@mipmap/ic_launcher"
     android:label="@string/app_name"
     android:roundIcon="@mipmap/ic_launcher_round"
     android:supportsRtl="true"
     android:theme="@style/AppTheme"
     tools:ignore="GoogleAppIndexingWarning">
    <activity
       android:name=".MainActivity"
       android:label="@string/app_name"
       android:launchMode="singleInstance"
       android:theme="@style/AppTheme.NoActionBar">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
      <intent-filter>
        <action
           android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
      </intent-filter>
      <meta-data
         android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
         android:resource="@xml/device_filter"/>
      </activity>
  </application>

</manifest>



We must also create the device_filter.xml file with the content below in the /src/main/res/xml directory of our project for the application to automatically start when a Yoctopuce module is connected.

<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="9440" /> </resources>



We didn't discuss in details these steps because we already explained them in a previous post. It's the same, whether we implement an application in Java or in Kotlin.

What is different


The only thing that's different compared to our project in Java is the implementation of MainActivity.

Instead of modifying a MainActivity.java file, we work on the MainActivity.kt which is written in Kotlin. Note that Kotlin files (.kt) are located in the same sub-directory as .java source files (app/src/main/java/).

In the onStart() method, we call the YAPI.EnableUSBHost() method and then the RegisterHub("usb") method to start detecting modules on the USB port.

We then declare a Runnable object which periodically performs the following operations:

  1. Call YAPI.UpdateDeviceList once every three seconds, which forces the API to enumerate the Yoctopuce modules.
  2. If no sensor is already used, look for the first detected temperature sensor with the YTemperature.FirstTemperature() method.
  3. Update the text field with the current value of the temperature sensor. First, we check that the module is still connected with the isOnline() method and, if it is the case, we retrieve the current temperature with get_currentValue().
  4. Program the next execution of this method with the postDelayed method.


Finally, in the onStop() method, we call YAPI.FreeAPI() to stop the Yoctopuce library and free the resources.

In the end, the MainActivity.kt file is the following:

import com.yoctopuce.YoctoAPI.YAPI
import com.yoctopuce.YoctoAPI.YAPI_Exception
import com.yoctopuce.YoctoAPI.YSensor
import com.yoctopuce.YoctoAPI.YTemperature


class MainActivity : AppCompatActivity() {
  lateinit var temperatureTextView: TextView
  lateinit var handler: Handler
  var hardwaredetect = 0
  var sensor: YSensor? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    temperatureTextView = findViewById(R.id.temperature)

    fab.setOnClickListener { view ->
        Snackbar.make(view,
            "Replace with your own action",
            Snackbar.LENGTH_LONG)
            .setAction("Action", null).show()
    }
    handler = Handler()
  }

  override fun onStart() {
    super.onStart()
    try {
        YAPI.EnableUSBHost(this)
        YAPI.RegisterHub("usb")
    } catch (e: YAPI_Exception) {
        Snackbar.make(temperatureTextView,
            "Error:" + e.localizedMessage,
              Snackbar.LENGTH_INDEFINITE).show()
    }
    handler.postDelayed(_periodicUpdate, 500)
  }

  override fun onStop() {
    handler.removeCallbacks(_periodicUpdate)
    YAPI.FreeAPI()
    super.onStop()
  }

  private val _periodicUpdate = object : Runnable {
    override fun run() {
      try {
        if (hardwaredetect == 0) {
            YAPI.UpdateDeviceList()
        }
        hardwaredetect = (hardwaredetect + 1) % 20
        if (sensor == null) {
            sensor = YTemperature.FirstTemperature()
        }
        sensor?.let {
            if (it.isOnline) {
                val text = String.format(Locale.US, "%.2f %s",
                    it.get_currentValue(), it.get_unit())
                temperatureTextView.text = text
            } else {
                temperatureTextView.text = "OFFLINE"
                sensor = null
            }

        }
      } catch (e: YAPI_Exception) {
        Snackbar.make(temperatureTextView,
            "Error:" + e.localizedMessage,
            Snackbar.LENGTH_INDEFINITE).show()
      }
      handler.postDelayed(this, 500)
    }
  }
}



Apart from the Kotlin syntax, the code is similar to what we wrote in Java.

When all these modifications are done, we can compile the application and use it with any phone or tablet with USB on the Go support.

The demo application, but written in Kotlin
The demo application, but written in Kotlin



The source code of this application is available at this address: https://github.com/yoctopuce-examples/android_template_kotlin.

Handling checked exceptions in Kotlin


Kotlin doesn't handle Checked Exceptions. By itself, this is a valid decision, but for a language which interacts with APIs and libraries written in Java, this can mislead the developer.

Let's take the get_currentValue() method of our library which raises an exception of type YAPI_Exception if the module is disconnected. This exception is declared as "checked" in the method signature with the "throws" keyword. The aim of these checked exceptions is to force the caller to handle this error. Therefore, in Java, the compiler returns an error if you use this method without handling the exception.

With Kotlin, it's different. The compiler doesn't check that this exception is handled by the caller. Therefore, if the developer forgets to handle this exception, the compiler won't say anything, but the application then crashes if the module is disconnected.

Most methods in the Yoctopuce library use "checked exceptions". If in doubt, read the documentation to know if a method can raise an exception or not. If it is the case, don't forget to handle the exception because the Kotlin compiler won't warn you.

Conclusion

As we just saw it, we can use our library in an Android application, whether it is written in Java or in Kotlin. To be honest, this is not really surprising because Google did everything it could to enable the two languages to coexist. There is no need to write a wrapper to use Java code or to transpile it. You can use the functions directly, they are simply written in another language.

On this topic, we must underline a very convenient feature of Android Studio: you can translate Java code into Kotlin and inversely. For example, you can copy code written in Java and paste it into your Kotlin source file. Android Studio automatically detects that it is code in Java and translates it on the fly. It's very convenient for beginners. And, incidentally, this is how we started to use Kotlin :->

However, to be honest, we are not really convinced by the Kotlin language. From our stand point, Kotlin is a difficult language which provides a false sense of simplicity. We are going to continue using Java for our Android developments while making sure that our library remains compatible with this new language.




Yoctopuce, get your stuff connected.