Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

How to Implement Motion Sensor in a Kotlin App

Most Android-powered devices have a plethora of built-in Sensors allowing you to make more versatile applications. Among these sensors are those that measure motion, orientation, and various environmental conditions including air temperature and pressure.

These sensors can be applied in various situations, for example, for monitoring three-dimensional device movement/positioning, rotation, tilt, shake, swing. Additionally, they can measure a change in the ambient environment near a device such as a temperature, illumination, humidity, etc.

Sensors are crucial for mobile app development. For example, temperature and humidity sensors are necessary for developing weather applications. Accelerometer and geolocation sensors, in their turn, play a crucial role in developing travel applications that allow counting your steps and track location as well as providing a compass functionality.

In this article, we will cover a subset of hardware sensors, such as motion sensors. And build a simple application in Kotlin.

Types of sensors

First and foremost, let’s find out what the sensors are capable of:

  • Sensors are a great option for creating a cool feature as they allow your app to support complex gestures for the motion recognition
  • They allow monitoring three-dimensional device movement or positioning alongside with high-precision raw data
  • Movement sensors allow extending your app with a variety of fun new features (a great part of game industry use these sensors in their games development)
  • Sensors can make your app multifunctional. This means that you may replace a great number of devices by a single smartphone;

In short, there are three categories of sensors:

Motion sensors

These ones measure acceleration and rotation forces along three axes. This category includes accelerometers, gravity sensors, gyroscopes, and rotation vector sensors.

Environmental sensors

Environmental sensors measure various environmental parameters, such as ambient air temperature and pressure, illumination, and humidity. This category includes barometers, photometers, and thermometers.

Position sensors

These sensors measure the physical position of a device. This category includes orientation sensors and magnetometers.

Android sensor framework allows us to work with sensors available on a device as well as acquiring raw data they measure. It provides us with a set of classes and interfaces to perform such tasks as:

  • Determine the sensors that are available on a device.
  • Determine an individual sensor’s capabilities, such as its maximum range, manufacturer, power requirements, and resolution.
  • Acquire raw sensor data and define the minimum rate at which you acquire sensor data.
  • Register and unregister sensor event listeners that monitor the sensor changes.

Sensors can also be separated by the following parameters:

Hardware. Hardware-based sensors are physical components built into a handset or tablet device. They derive their data by directly measuring specific environmental properties, such as acceleration, geomagnetic field strength, or angular change.

Software. Software-based sensors are not physical devices, so they mimic hardware-based sensors. Software-based sensors derive their data from one or more of the hardware-based sensors. Sometimes, they are also called as virtual sensors or synthetic sensors. The linear acceleration sensor and the gravity sensor are examples of software-based sensors.

If you want to find out more about the sensors, check this article.

Sensors that we’ll use to build a Kotlin App

Now when we know the main categories of sensors, we need to determine the ones that we’ll be using in our app:

  • TYPE_ACCELEROMETER

Measures the acceleration force in m/s2 that is applied to a device on all three physical axes (x, y, and z), including the force of gravity.

Motion detection (shake, tilt, etc.).

  • TYPE_GRAVITY

Measures the force of gravity in m/s2 that is applied to a device on all three physical axes (x, y, z).

Motion detection (shake, tilt, etc.).

  • TYPE_GYROSCOPE

Measures a device’s rate of rotation in rad/s around each of the three physical axes (x, y, and z).

Rotation detection (spin, turn, etc.).

  • TYPE_LINEAR_ACCELERATION

Measures the acceleration force in m/s2 that is applied to a device on all three physical axes (x, y, and z), without the force of gravity.

Monitoring acceleration along a single axis.

  • TYPE_ROTATION_VECTOR

Measures the orientation of a device by providing the three elements of the device’s rotation vector.

Motion detection and rotation detection.

Prerequisites

For implementing a Kotlin app, you’ll need:

  • Android Studio 3.1.1
  • Configure project for Kotlin usage
  • ConstraintLayout 1.1.0-beta6. Last updates have a lot of useful features, particularly Barriers
  • You’ll definitely need an Android Data Binding, if you want more convenient views binding without boilerplate ‘findViewById()’ calls
  • Physical device with motion sensors. Remember that you won’t be able to test motion sensors on emulator. That’s why, you’ll definitely need a physical device.

Checking sensors’ availability

In a typical application, you use these sensor-related APIs to perform two basic tasks:

Identifying sensors and sensor capabilities. Identifying sensors and sensor capabilities at runtime is useful if your application has features that rely on specific sensor types or capabilities. For example, you may want to identify all of the sensors, which are present on a device. Additionally, you may disable any application’s feature that rely on sensors that are not present. Likewise, you may want to identify all of the sensors of a given type so you can choose the sensor implementation that has the optimum performance for your application.

Monitor sensor events. Monitoring sensor events is the way you acquire raw sensor data. A sensor event occurs every time when a sensor detects a change in the parameters that are being measured. A sensor event provides you with four pieces of information:

  • the name of the sensor that triggered the event;
  • he timestamp for the event;
  • the accuracy of the event;
  • the raw sensor data that triggered the event.

First of all, we need to check whether the sensors we need are available on the device or not. We obtain an instance of SensorManager:

this.sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

Notice we use Kotlin’s safe cast operator «as» to cast system service to the necessary type. This operator tries to cast a service instance to a SensorManger type and returns null if service is not an instance of SensorManager or getSystemService returned null itself.

You can use this class to create an instance of the sensor service. This class provides various methods for accessing and listing sensors, registering and unregistering sensor event listeners, and acquiring orientation information. Additionally, this class provides several sensor constants that are used to report sensor accuracy, set data acquisition rates, and calibrate sensors.

Hereafter, we ask the system for the available sensors that we need. In order to do it, use getDefaultSensor(type: Int) method. And don’t forget to get a reference to the sensor of the type you pass. We need to retrieve 5 different sensors. If sensor comes null, then the device does not have one. We check for this:

In order not to overload the code with null checks, we use standard Kotlin library function let{}. First of all, we check whether the returned sensor is not null and then let executes a block of code on the sensor with it as this particular sensor object.

Then we just store a reference to it. This also relates to all other sensors. The nice thing is that it can’t be null, as the block won’t be executed if getDefaultSensor() returns null.

SensorEventListener Implementation

Now it’s time to implement SensorEventListener. To collect data from any sensor, an app needs to register a SensorEventListener for receiving the sensor data. After this it can extract data from SensorEvent, which comes from a sensor. It can be implemented the following way:

onAccuracyChanged() is called when:

a sensor’s accuracy changes. In this case, the system invokes the onAccuracyChanged() method, providing you with a reference to the Sensor object, which has changed, and the new accuracy of the sensor. Accuracy is represented by one of four status constants:

  • SENSOR_STATUS_ACCURACY_LOW,
  • SENSOR_STATUS_ACCURACY_MEDIUM,
  • SENSOR_STATUS_ACCURACY_HIGH,
  • SENSOR_STATUS_UNRELIABLE.

In this app, we don’t need any accuracy indicators of the sensor. That’s why, we leave the method’s implementation empty. Just FYI, a valid way of using this method would be storing new accuracy values (in activity field, for example) and check it in onSensorChanged() callback. This can be especially useful if it’s necessary to respond to the sensors indicators (only when it’s accuracy is high or low).

onSensorChanged() is called when:

a sensor reports a new value. In this case, the system invokes the onSensorChanged() method, providing you with a SensorEvent object. A SensorEvent object contains information about the new sensor data, including:

  • new data that the sensor recorded
  • data accuracy
  • the sensor that generated the data
  • the timestamp at which the data was generated

During the implementation, we determine the type of the sensor that emits events. Consequently, we make the corresponding calculations:

when (event?.sensor?.type) {

}

We capture linear acceleration along with x, y, and z axes of the device:

Sensor.TYPE_LINEAR_ACCELERATION -> {
valuesLinAcceleration[0] = event.values[0]
valuesLinAcceleration[1] = event.values[1]
valuesLinAcceleration[2] = event.values[2]
  this.binding.tvLinearAccelX.text = event.values[0].formatValue()
this.binding.tvLinearAccelY.text = event.values[1].formatValue()
this.binding.tvLinearAccelZ.text = event.values[2].formatValue()
}

Gravity sensor provides us with the direction and magnitude of the gravity:

Sensor.TYPE_GRAVITY -> {
valuesGravity[0] = event.values[0]
valuesGravity[1] = event.values[1]
valuesGravity[2] = event.values[2]
  this.binding.tvGravity.text = event.values[0].formatValue()
this.binding.tvGravityY.text = event.values[1].formatValue()
this.binding.tvGravityZ.text = event.values[2].formatValue()
}

Android 4.0 TYPE_GRAVITY and TYPE_ACCELEROMETER sensors are available only if a device has a hardware gyroscope sensor. Both these sensors are software and depend on the readings from the hardware gyroscope.

However, we can do a workaround here. We’ll need to calculate linear acceleration and gravity using accelerometer.

Accelerometer measures the acceleration applied to the device, including the force of gravity.

With a high-pass filter we can filter out gravity acceleration values and subtract it from the raw sensor acceleration values:

Sensor.TYPE_ACCELEROMETER -> {
val alpha = 0.8f
  accelGravity[0] = alpha * accelGravity[0] + (1 - alpha) * event.values[0]
accelGravity[1] = alpha * accelGravity[1] + (1 - alpha) * event.values[1]
accelGravity[2] = alpha * accelGravity[2] + (1 - alpha) * event.values[2]
  accelLin[0] = event.values[0] - accelGravity[0]
accelLin[1] = event.values[1] - accelGravity[1]
accelLin[2] = event.values[2] - accelGravity[2]
  this.binding.tvAccelerometerX.text = accelLin[0].formatValue()
this.binding.tvAccelerometerY.text = accelLin[1].formatValue()
this.binding.tvAccelerometerZ.text = accelLin[2].formatValue()
}

If you want to find out more about filtering, take a look at this article or check out this cool answer on Stackoverflow.

Measure the rate of rotation in radians per second around (x, y, z) axes. All the data provided by gyroscope here is raw rotational data with no noise or bias filtering. As a result, this can introduce errors. If you want this process to be more accurate, you need to monitor other sensors, such as accelerometer or gravity. Still for this tutorial we are not digging too deep into calculations and display values as-is:

Sensor.TYPE_GYROSCOPE -> {
valuesGyroscope[0] = event.values[0]
valuesGyroscope[1] = event.values[1]
valuesGyroscope[2] = event.values[2]
  this.binding.tvGyroscope.text = event.values[0].formatValue()
this.binding.tvGyroscopeY.text = event.values[1].formatValue()
this.binding.tvGyroscopeZ.text = event.values[2].formatValue()
}

To get device rotation vector:

Sensor.TYPE_ROTATION_VECTOR -> {
valuesRotation[0] = event.values[0]
valuesRotation[1] = event.values[1]
valuesRotation[2] = event.values[2]
  this.binding.tvRotationVector.text = event.values[0].formatValue()
this.binding.tvRotationVectorY.text = event.values[1].formatValue()
this.binding.tvRotationVectorZ.text = event.values[2].formatValue()
}

Rotation here is implemented through a 3-dimensional vector which represents the orientation of the device as a combination of an angle and an axis, in which the device has rotated through an angle θ around an axis (x, y, or z).

When you run the app, you should see values from sensor appearing in realtime on the screen.

Conclusion

Now you’ve got the idea of the main types of the sensors and know how to implement some of them in an Android application. Additionally, you’ve learned how to check whether the sensors are available in the device or not. Most importantly, you know how to implement SensorEventListener that allows collecting data from any sensor.

P/S: If you’ve found the article useful, please support it with claps and by subscribing to our newsletter for more good stuff! Also, don’t forget to share your thoughts in the comment section below! Cheers!

How to Implement Motion Sensor in a Kotlin App was originally published in JetRuby on Medium, where people are continuing the conversation by highlighting and responding to this story.



This post first appeared on JetRuby Agency - Featured Technical Stories Based In Our Experience, please read the originial post: here

Share the post

How to Implement Motion Sensor in a Kotlin App

×

Subscribe to Jetruby Agency - Featured Technical Stories Based In Our Experience

Get updates delivered right to your inbox!

Thank you for your subscription

×