Sensors in .NET MAUI #MAUIUIJuly

In this post, we will focus on how our phone's sensors work. We will explore six key sensors available in .NET MAUI, complete with real-time visualizations and code samples.

Sensors in .NET MAUI #MAUIUIJuly
🎉
This post is part of .NET MAUI UI July 2025 (https://goforgoldman.com/posts/mauiuijuly-25/) - a month-long community celebration of .NET MAUI user interfaces. #dotnetmaui #MAUIUIJuly

In this post, we will focus on how our phone's sensors work. We will explore how to use them for cool UI effects in a future post.

For today, we will explore six key sensors available in .NET MAUI, complete with real-time visualizations and practical UI patterns that will inspire your next app.


The Sensor Landscape in .NET MAUI

.NET MAUI provides built-in access to several sensors through the `Microsoft.Maui.Essentials` package:

Sensor Purpose UI Applications
Accelerometer Linear acceleration in 3D Shake gestures, tilt controls, step detection
Gyroscope Angular velocity/rotation Smooth camera controls, VR interfaces
Magnetometer Magnetic field detection Compass displays, metal detection games
Barometer Atmospheric pressure Weather widgets, altitude indicators
Compass Magnetic north heading Navigation UIs, AR overlays
Orientation Device position/rotation Auto-rotation, 3D scene controls

Setting Up Sensor Integration

Let's configure our project for sensor usage.

Project Setup

First, ensure your project includes the necessary permissions:
Android (Platforms/Android/AndroidManifest.xml):

<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="false" />
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="false" />
<uses-feature android:name="android.hardware.sensor.compass" android:required="false" />

iOS (Platforms/iOS/Info.plist):

<key>NSMotionUsageDescription</key>
<string>This app uses CMAltimeter.</string>

The Service Architecture

We'll use a clean service pattern that standardizes sensor logic from sensor-specific values:

public abstract class BaseBindableSensor_Service : BaseBindableAppCapability_Service
{
    public SensorSpeed SensorSpeed { get; set; } = SensorSpeed.UI;
    public bool IsMonitoring { get; set; }
    public string Status { get; protected set; }

    // Abstract methods for each sensor implementation
    protected abstract void StartSensor();
    protected abstract void StopSensor();
    protected abstract bool IsSensorMonitoring();
}
💡
I made a PR on MAUI to get a nice ISensor interface : https://github.com/dotnet/maui/pull/30135
Give it a 👍 if you'd like it too !

Sensor Implementations

Accelerometer: Motion-Responsive UI

The accelerometer measures acceleration forces, perfect for gesture-based controls.

public class Accelerometer_Service : BaseBindableSensor_Service
{
    public float XinG { get; set; }
    public float YinG { get; set; }
    public float ZinG { get; set; } // 1.0G when flat on table

    private void OnReadingChanged(object? sender, AccelerometerChangedEventArgs e)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            XinG = e.Reading.Acceleration.X;
            YinG = e.Reading.Acceleration.Y;
            ZinG = e.Reading.Acceleration.Z;
        });
    }
}

Units

  • Properties: X, Y, Z
  • Unit: g-force (g) — gravitational acceleration
  • Typical Range: ~-1.0 to +1.0 per axis (on a stationary device)
  • Meaning: Acceleration including gravity, in g units:
    • X: side-to-side
    • Y: top-to-bottom
    • Z: front-to-back (screen facing you = negative Z)
📝
Example: A phone lying flat, screen-up, should show : X ≈ 0, Y ≈ 0, Z ≈ -1
0:00
/0:08

Here is me shaking my phone around :


Gyroscope: Smooth Rotation Controls

Perfect for camera controls and VR-style interfaces:

public class Gyroscope_Service : BaseBindableSensor_Service
{
    public float XRadians { get; set; } // Pitch
    public float YRadians { get; set; } // Yaw
    public float ZRadians { get; set; } // Roll

    public double XDegrees { get; set; }
    public double YDegrees { get; set; }
    public double ZDegrees { get; set; }

    private void OnReadingChanged(object? sender, GyroscopeChangedEventArgs e)
    {
        MainThread.BeginInvokeOnMainThread(() => {
            XRadians = e.Reading.AngularVelocity.X;
            XDegrees = RadianToDegree(e.Reading.AngularVelocity.X);
            YRadians = e.Reading.AngularVelocity.Y;
            YDegrees = RadianToDegree(e.Reading.AngularVelocity.Y);
            ZRadians = e.Reading.AngularVelocity.Z;
            ZDegrees = RadianToDegree(e.Reading.AngularVelocity.Z);
        });
    }
}

Units

  • Properties: X, Y, Z
  • Unit: radians per second (rad/s)
  • Typical Range: ~-10 to +10 rad/s (varies by device)
  • Meaning: Angular velocity around each axis:
    • X: pitch (tilting forward/backward)
    • Y: yaw (turning left/right)
    • Z: roll (tilting side-to-side)
0:00
/0:11

Here is me shaking my phone around :


Compass: Navigation & Orientation UI

Essential for location-based apps and AR experiences:

public class Compass_Service : BaseBindableSensor_Service
{
    public float HeadingInDegrees { get; set; } // 0-359°, 0=North

    private void OnReadingChanged(object? sender, CompassChangedEventArgs e)
    {
        MainThread.BeginInvokeOnMainThread(() => {
            HeadingInDegrees = e.Reading.HeadingMagneticNorth;
        });
    }
}
<Image Source="compass"
   WidthRequest="200"
   HeightRequest="200"
   HorizontalOptions="Center"
   VerticalOptions="Center"
   Rotation="{Binding HeadingInDegrees, Converter={StaticResource MathExpressionConverter}, ConverterParameter='-x'}" />

Units

  • Property: HeadingInDegrees
  • Unit: degrees (°)
  • Range: 0 to 359° (0° = North)
  • Meaning: Direction the device is facing relative to magnetic north.
ℹ️
Heading readings can be affected by magnetic interference and the device’s physical orientation.
0:00
/0:09

Pointing my phone at different corners of the room.


Barometer: Atmospheric Air Pressure

Great for weather apps and altitude-sensitive UIs:

public class Barometer_Service : BaseBindableSensor_Service
{
    public double PressureInHectopascals { get; set; } // 950-1050 hPa typical range

    private void OnReadingChanged(object? sender, BarometerChangedEventArgs e)
    {
        MainThread.BeginInvokeOnMainThread(() => {
            PressureInHectopascals = e.Reading.PressureInHectopascals;
        });
    }
}

Units

  • Property: PressureInHectopascals
  • Unit: hectopascals (hPa)
  • Typical Range: ~950 to 1050 hPa (varies with altitude and weather)
  • Meaning: Atmospheric pressure, used for altitude estimation and weather prediction.
    • Higher pressure = lower altitude and/or clear weather
    • Low pressure = higher altitude and/or stormy weather
0:00
/0:03

This one’s more observational — just turn it on and see the values fluctuate.


Magnetometer: Magnetic Field Detection

The magnetometer detects magnetic fields, essential for compass functionality and metal detection:

public class Magnetometer_Service : BaseBindableSensor_Service
{
    public float XInMicroTesla { get; set; }
    public float YInMicroTesla { get; set; }
    public float ZInMicroTesla { get; set; }

    // Total magnetic field strength
    public double TotalFieldStrength { get; set; }
        
    private void OnReadingChanged(object? sender, MagnetometerChangedEventArgs e)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            XInMicroTesla = e.Reading.MagneticField.X;
            YInMicroTesla = e.Reading.MagneticField.Y;
            ZInMicroTesla = e.Reading.MagneticField.Z;
            TotalFieldStrength = Math.Sqrt(
                        XInMicroTesla * XInMicroTesla +
                        YInMicroTesla * YInMicroTesla +
                        ZInMicroTesla * ZInMicroTesla);
        });
    }
}

Units

  • Properties: X, Y, Z
  • Unit: microteslas (μT)
  • Typical Range: ~-50 to +50 μT (Earth's magnetic field)
  • Meaning: Magnetic field strength:
    • X: horizontal component (side-to-side)
    • Y: vertical component (up/down)
    • Z: depth component (front-to-back)
ℹ️
Values can vary widely based on device orientation and magnetic interference.
0:00
/0:05

Bringing my phone next to a magnet


Orientation: Device Positioning (Maths ahead!)

The orientation sensor provides quaternion data that can be converted to human-readable angles:

Conversion between quaternions and Euler angles - Wikipedia
// Raw quaternion components (W, X, Y, Z)
public float W { get; set; } // Scalar component (-1.0 to +1.0)
public float X { get; set; } // Pitch rotation (-1.0 to +1.0)
public float Y { get; set; } // Yaw rotation (-1.0 to +1.0)
public float Z { get; set; } // Roll rotation (-1.0 to +1.0)

// Human-readable Euler angles
public double PitchDegrees { get; private set; } // Forward/backward tilt
public double YawDegrees { get; private set; }   // Left/right rotation
public double RollDegrees { get; private set; }  // Left/right tilt

private void OnReadingChanged(object? sender, OrientationSensorChangedEventArgs e)
{
    MainThread.BeginInvokeOnMainThread(() =>
    {
        // Update raw quaternion values
        W = e.Reading.Orientation.W;
        X = e.Reading.Orientation.X;
        Y = e.Reading.Orientation.Y;
        Z = e.Reading.Orientation.Z;

        // Convert to human-readable values
        UpdateEulerAngles();
    });
}

#region Quaternion to Euler Conversion Methods

/// <summary>
/// Converts quaternion (W,X,Y,Z) to Euler angles (Pitch, Yaw, Roll) in degrees
/// </summary>
private void UpdateEulerAngles()
{
    // Convert quaternion to Euler angles using standard formulas
    var (pitch, yaw, roll) = QuaternionToEulerAngles(W, X, Y, Z);

    PitchInDegrees = RadiansToDegrees(pitch);
    YawInDegrees = RadiansToDegrees(yaw);
    RollInDegrees = RadiansToDegrees(roll);
}

/// <summary>
/// Converts quaternion to Euler angles (in radians)
/// Returns: (pitch, yaw, roll) in radians
/// </summary>
private static (double pitch, double yaw, double roll) QuaternionToEulerAngles(float w, float x, float y, float z)
{
    // Pitch (X-axis rotation) - forward/backward tilt
    var sinPitch = 2.0 * (w * x + y * z);
    var cosPitch = 1.0 - 2.0 * (x * x + y * y);
    var pitch = Math.Atan2(sinPitch, cosPitch);

    // Yaw (Y-axis rotation) - left/right turn
    var sinYaw = 2.0 * (w * y - z * x);
    var yaw = Math.Abs(sinYaw) >= 1
        ? Math.CopySign(Math.PI / 2, sinYaw) // Use 90 degrees if out of range
        : Math.Asin(sinYaw);

    // Roll (Z-axis rotation) - left/right tilt
    var sinRoll = 2.0 * (w * z + x * y);
    var cosRoll = 1.0 - 2.0 * (y * y + z * z);
    var roll = Math.Atan2(sinRoll, cosRoll);

    return (pitch, yaw, roll);
}

/// <summary>
/// Converts radians to degrees
/// </summary>
private static double RadiansToDegrees(double radians)
{
    return radians * (180.0 / Math.PI);
}

#endregion
<!-- Always pointing north and parallel to earth -->
<Image Source="compass"
    WidthRequest="200"
    HeightRequest="200"
    HorizontalOptions="Center"
    VerticalOptions="Center"
    RotationX="{Binding PitchInDegrees}"
    RotationY="{Binding YawInDegrees}"
    Rotation="{Binding RollInDegrees}" />

Units

  • Properties: X, Y, Z, W (quaternion)
  • Unit: None (quaternion representation)
  • Meaning: Orientation in 3D space:
    • X, Y, Z: vector components
    • W: scalar component
  • Note: Used for 3D orientation, not directly human-readable.

By mapping them onto this compass, we have a compass that not only points north but also tries to stay aligned with the Earth’s surface:

0:00
/0:12

Check out my MAUI playground for a full sample:

GitHub - framinosona/Maui-Developer-Sample: This is my playground, I try to make things as clean as possible so that I can reuse them.
This is my playground, I try to make things as clean as possible so that I can reuse them. - framinosona/Maui-Developer-Sample
🎉
This post is part of .NET MAUI UI July 2025 (https://goforgoldman.com/posts/mauiuijuly-25/) - a month-long community celebration of .NET MAUI user interfaces. #dotnetmaui #MAUIUIJuly