DEV Community

Tu codigo cotidiano
Tu codigo cotidiano

Posted on

How to Build a Smart Android Compass with Kotlin, Jetpack Compose, and Sensor Fusion

A compass app sounds simple: read an angle from the phone and rotate a needle.

Real sensors make the problem much more interesting.

The measurements contain noise, the phone can rotate between portrait and landscape, nearby objects can affect the magnetometer, and angles cannot always be processed like ordinary numbers.

I explored these challenges by building SmartCompass, an Android application created with Kotlin and Jetpack Compose.

SmartCompass Android application showing the compass heading, cardinal direction, magnetic field strength, and calibration status

What the application does

SmartCompass combines the phone's accelerometer and magnetometer to calculate its magnetic orientation.

The application can:

  • Calculate a magnetic heading between 0° and 359°.
  • Convert the heading into a cardinal direction.
  • Calculate pitch and roll.
  • Smooth accelerometer and magnetometer measurements.
  • Stabilize the final heading.
  • Correct the coordinate system when the screen rotates.
  • Measure magnetic field strength in microteslas.
  • Build a local magnetic reference.
  • Detect persistent changes relative to that reference.
  • Report the magnetometer accuracy and calibration state.
  • Handle devices without the required sensors.
  • Start and stop sensor collection according to the Android lifecycle.

It does not require GPS or location permissions because it displays magnetic north, not geographic north.

The circular-angle problem

One of the most interesting problems appears when the compass crosses north.

Consider these two measurements:

359°
1°
Enter fullscreen mode Exit fullscreen mode

A regular arithmetic mean produces:

(359 + 1) / 2 = 180°
Enter fullscreen mode Exit fullscreen mode

That result points south, even though both measurements are close to north.

Angles form a circle rather than an infinite line. After 359°, the next direction is 0°. A heading filter must therefore understand circular distance.

SmartCompass converts every angle into a two-dimensional unit vector:

x = cos(angle)
y = sin(angle)
Enter fullscreen mode Exit fullscreen mode

The vectors can be averaged, and the resulting direction can be recovered with atan2().

This produces a value close to 0° for samples around 359° and 1°, avoiding the sudden jump to 180°.

Filtering the sensor measurements

The application uses two different filtering strategies.

First, a low-pass filter smooths the X, Y, and Z components received from the accelerometer and magnetometer:

yₜ = αyₜ₋₁ + (1 − α)xₜ
Enter fullscreen mode Exit fullscreen mode

A larger alpha produces a smoother but slower response. A smaller value reacts faster but preserves more noise.

The filtered vectors are then used to calculate the device orientation.

A second circular filter stabilizes the final heading. Keeping these responsibilities separate prevents a linear filter from being incorrectly applied to circular values.

Understanding magnetic reliability

Displaying a number is not enough. The application should also communicate whether that number can be trusted.

SmartCompass uses two sources of information:

  1. The accuracy level reported by Android.
  2. Changes in magnetic field strength relative to a local baseline.

During the first samples, the application learns an approximate reference for the current environment. Later measurements are compared with this baseline.

A single unusual sample does not immediately trigger a warning. The change must persist across several readings. Recovery also requires multiple normal readings, preventing the interface from constantly switching between warning and normal states.

This mechanism is educational and adaptive. It cannot identify the source of a magnetic change or replace professional measuring equipment.

Keeping the domain logic independent

The mathematical and calibration components do not depend directly on Android or Jetpack Compose.

Classes responsible for tasks such as:

  • Normalizing angles.
  • Finding the shortest angular difference.
  • Converting headings into cardinal directions.
  • Filtering vectors.
  • Filtering circular values.
  • Evaluating calibration.
  • Analyzing magnetic measurements.

can be tested with local unit tests.

Sensor access is exposed through an abstraction, while the presentation layer receives application-specific models instead of raw SensorManager events.

This separation makes the code easier to understand, test, and extend.

A compass is more than a rotating needle

The most valuable lesson from this project is that applications working with physical measurements should expose uncertainty and state, not only a final value.

A useful compass should not merely say:

Heading: 23°
Enter fullscreen mode Exit fullscreen mode

It should also be able to explain:

Direction: Northeast
Accuracy: Medium
Magnetic field: 43.2 μT
Calibration: Acceptable
Reading status: Ready
Enter fullscreen mode Exit fullscreen mode

That additional context helps the user understand when the measurement is stable and when the environment or sensor may require attention.

I documented the complete implementation step by step, including the Android project structure, sensor processing, circular filters, calibration models, magnetic analysis, Compose interface, lifecycle management, and testing.

Read the complete tutorial

The full tutorial is available in Spanish:

👉 Build a Smart Compass on Android with Kotlin and Jetpack Compose

What other smartphone sensor would you use for your next Android experiment?

Top comments (0)