WellAlly Logo
WellAlly康心伴
Development

Project Showcase: Building a Smart Alarm in React Native to Wake You From Light Sleep

Learn how to build a mobile app that uses your phone's accelerometer to estimate sleep cycles and wakes you during your lightest sleep phase. A practical guide using React Native and Expo Sensors.

W
2025-12-12
9 min read

Ever jolted awake by a blaring alarm feeling groggy and disoriented? This feeling, known as sleep inertia, is often the result of being woken up during a deep sleep cycle. What if your alarm could intelligently wait for the optimal moment to wake you? That's the idea behind the "Smart Alarm" – a mobile app that uses your phone's accelerometer to estimate your sleep cycles and wake you during your lightest sleep phase.

In this project showcase, we'll walk through the creation of a mobile app that does just that. We'll explore the science behind sleep tracking with motion sensors, the challenges of interpreting accelerometer data, and the step-by-step implementation using React Native. This project is a fantastic blend of mobile development, data science, and health tech, offering a practical look at how we can use the power in our pockets to improve our well-being.

Prerequisites:

  • Familiarity with React Native and JavaScript (ES6+).
  • A physical device for testing (simulators don't have accelerometers).
  • Node.js and a React Native development environment set up.

Understanding the Problem

Traditional alarms are simple: they go off at a fixed time, regardless of your sleep state. However, our sleep is cyclical, moving between light, deep, and REM stages. Waking up from a light sleep stage feels much more natural and leaves you feeling more refreshed.

The challenge is to determine these sleep stages without the advanced equipment of a sleep lab. This is where the accelerometer comes in. By placing your phone on your bed, it can detect your movements throughout the night. The core idea is that we move more during lighter sleep phases and are relatively still during deep sleep.

While not as precise as clinical methods like polysomnography, accelerometer-based tracking is a reliable way to estimate sleep patterns for consumer applications. Our approach will be to capture this motion data, process it to identify periods of low and high activity, and use this information to trigger the alarm at the right time.

Prerequisites

Before we start coding, let's get our environment ready.

  • React Native Project: If you don't have a project, create one using Expo:

    code
    npx create-expo-app SmartAlarm
    cd SmartAlarm
    
    Code collapsed
  • Required Libraries: We'll need a library to access the accelerometer. expo-sensors is an excellent choice for this.

    code
    npx expo install expo-sensors
    
    Code collapsed
  • Version Compatibility: This tutorial uses Expo SDK 50 and expo-sensors v13.0.0.

Step 1: Accessing Accelerometer Data

What we're doing

First, we need to get a continuous stream of data from the phone's accelerometer. The accelerometer measures acceleration forces along the x, y, and z axes. We'll set up a listener that captures this data and stores it in our component's state.

Implementation

Create a new file src/SleepTracker.js. This component will handle all the logic for tracking sleep.

code
// src/components/SleepTracker.js
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Accelerometer } from 'expo-sensors';

const SleepTracker = () => {
  const [data, setData] = useState({ x: 0, y: 0, z: 0 });
  const [subscription, setSubscription] = useState(null);

  const _subscribe = () => {
    setSubscription(
      Accelerometer.addListener(accelerometerData => {
        setData(accelerometerData);
      })
    );
    // Set the update interval for the accelerometer
    Accelerometer.setUpdateInterval(1000); // 1 second
  };

  const _unsubscribe = () => {
    subscription && subscription.remove();
    setSubscription(null);
  };

  useEffect(() => {
    _subscribe();
    return () => _unsubscribe();
  }, []);

  const { x, y, z } = data;
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Accelerometer:</Text>
      <Text style={styles.text}>x: {Math.round(x)} y: {Math.round(y)} z: {Math.round(z)}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 18,
  },
});

export default SleepTracker;
Code collapsed

How it works

  • We import Accelerometer from expo-sensors.
  • The useEffect hook is used to subscribe to the accelerometer when the component mounts and unsubscribe when it unmounts. This is crucial to prevent memory leaks.
  • Accelerometer.addListener registers a callback function that receives the accelerometer data.
  • We store the latest sensor reading in the component's state using useState.

Step 2: Processing Movement Data

What we're doing

Raw x, y, and z values are hard to work with directly. We need a single value that represents the magnitude of movement. We can calculate this by finding the vector magnitude of the acceleration. This will give us a simple number that indicates how much the phone is moving.

Implementation

Let's modify our SleepTracker component to calculate and store the movement magnitude.

code
// src/components/SleepTracker.js (Updated)
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { Accelerometer } from 'expo-sensors';

const SleepTracker = () => {
  const [movementData, setMovementData] = useState([]);
  const [isTracking, setIsTracking] = useState(false);
  const [subscription, setSubscription] = useState(null);

  const startTracking = () => {
    setIsTracking(true);
    setSubscription(
      Accelerometer.addListener(({ x, y, z }) => {
        // Calculate magnitude of movement
        const magnitude = Math.sqrt(x ** 2 + y ** 2 + z ** 2);
        // We are interested in the change from the force of gravity (1)
        const movement = Math.abs(magnitude - 1);
        setMovementData(prevData => [...prevData, { movement, timestamp: new Date() }]);
      })
    );
    Accelerometer.setUpdateInterval(2000); // 2 seconds
  };

  const stopTracking = () => {
    setIsTracking(false);
    subscription && subscription.remove();
    setSubscription(null);
    // Here you would typically process the movementData
    console.log('Final Movement Data:', movementData);
  };

  useEffect(() => {
    return () => {
      subscription && subscription.remove();
    };
  }, [subscription]);

  return (
    <View style={styles.container}>
      <Text style={styles.text}>
        {isTracking ? 'Tracking Sleep...' : 'Ready to Track'}
      </Text>
      <Button title={isTracking ? "Stop Tracking" : "Start Tracking"} onPress={isTracking ? stopTracking : startTracking} />
    </View>
  );
};

// Styles remain the same
const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
    },
    text: {
      fontSize: 18,
      marginBottom: 20,
    },
  });

export default SleepTracker;
Code collapsed

How it works

  • We've added startTracking and stopTracking functions to control when we're collecting data.
  • Inside the accelerometer listener, we calculate the magnitude of the acceleration vector.
  • Since gravity exerts a force of approximately 1g, we subtract 1 from the magnitude to isolate movement. We take the absolute value as we're interested in any deviation.
  • Each movement value is stored with a timestamp in an array.

Step 3: The Smart Alarm Algorithm

What we're doing

This is the core logic of our smart alarm. The alarm will have a "wake-up window" (e.g., 30 minutes before the set alarm time). During this window, the app will monitor the user's movement. If the movement exceeds a certain threshold, indicating a light sleep phase, the alarm will sound.

Implementation

Let's create a new component for the alarm logic. For this example, we'll simulate the alarm window with a button press.

code
// src/components/SmartAlarm.js
import React, { useState, useEffect } from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { Accelerometer } from 'expo-sensors';

const WAKE_UP_WINDOW_MINUTES = 30;
const MOVEMENT_THRESHOLD = 0.1; // This will need tuning

const SmartAlarm = () => {
  const [targetAlarmTime, setTargetAlarmTime] = useState(null);
  const [inWakeUpWindow, setInWakeUpWindow] = useState(false);
  const [subscription, setSubscription] = useState(null);

  useEffect(() => {
    // Check every second if we are in the wake up window
    const interval = setInterval(() => {
      if (targetAlarmTime) {
        const now = new Date();
        const windowStart = new Date(targetAlarmTime.getTime() - WAKE_UP_WINDOW_MINUTES * 60000);
        if (now >= windowStart && now <= targetAlarmTime) {
          if (!inWakeUpWindow) {
            console.log("Entering wake-up window. Starting to monitor for movement.");
            setInWakeUpWindow(true);
            startMonitoring();
          }
        } else if (now > targetAlarmTime) {
            console.log("Target alarm time passed. Firing alarm.");
            fireAlarm();
        }
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [targetAlarmTime, inWakeUpWindow]);

  const setAlarm = () => {
    const targetTime = new Date();
    targetTime.setMinutes(targetTime.getMinutes() + 1); // For demo, set alarm 1 minute from now
    setTargetAlarmTime(targetTime);
    Alert.alert("Alarm Set", `Will look for light sleep between ${new Date(targetTime.getTime() - WAKE_UP_WINDOW_MINUTES * 60000).toLocaleTimeString()} and ${targetTime.toLocaleTimeString()}`);
  };

  const startMonitoring = () => {
    setSubscription(
      Accelerometer.addListener(({ x, y, z }) => {
        const magnitude = Math.sqrt(x ** 2 + y ** 2 + z ** 2);
        const movement = Math.abs(magnitude - 1);

        if (movement > MOVEMENT_THRESHOLD) {
          console.log(`Movement detected (${movement.toFixed(2)}). Firing smart alarm!`);
          fireAlarm();
        }
      })
    );
    Accelerometer.setUpdateInterval(2000);
  };

  const fireAlarm = () => {
    stopMonitoring();
    Alert.alert("Good Morning!", "Woke you up during a light sleep phase.");
    setTargetAlarmTime(null);
    setInWakeUpWindow(false);
  };

  const stopMonitoring = () => {
    subscription && subscription.remove();
    setSubscription(null);
  };

  return (
    <View>
      <Button title="Set Smart Alarm for 1 Minute from Now" onPress={setAlarm} />
      {targetAlarmTime && <Text>Alarm set for: {targetAlarmTime.toLocaleTimeString()}</Text>}
    </View>
  );
};

export default SmartAlarm;
Code collapsed

How it works

  1. Setting the Alarm: The user sets a target time. We calculate a "wake-up window" that starts 30 minutes before this time.
  2. Entering the Window: A setInterval checks if the current time is within this window.
  3. Monitoring Movement: Once inside the window, we start listening to the accelerometer.
  4. Threshold Check: We calculate the movement magnitude. If it surpasses our MOVEMENT_THRESHOLD, it suggests the user is in a lighter sleep stage.
  5. Firing the Alarm: The alarm is triggered, and the monitoring stops.
  6. Failsafe: If no significant movement is detected by the target alarm time, the alarm goes off anyway.

Challenges and Considerations

  • Interpreting Sensor Data: The biggest challenge is the inherent noise in accelerometer data. A cough, a pet jumping on the bed, or the phone shifting can all create false positives. More sophisticated algorithms might use a moving average of movement to smooth out the data.
  • Threshold Tuning: The MOVEMENT_THRESHOLD is a critical value. It will vary depending on the phone's sensitivity, its placement on the bed, and the individual's sleep habits. A production-ready app would need a calibration phase.
  • Battery Life: Continuously polling the accelerometer can drain the battery. In a real app, you would use background processing and optimize the polling frequency.
  • Device Placement: The app's effectiveness depends on the phone being placed on the bed in a way that it can detect the user's movements.

Conclusion

We've successfully built the core of a "Smart Alarm" app in React Native. We learned how to access and process accelerometer data to estimate sleep phases and created an algorithm to wake a user during their lightest sleep. While this is a simplified implementation, it serves as a strong foundation for a more advanced sleep-tracking application.

The next steps could involve adding a user interface for setting alarms, persisting alarm data, and refining the sleep detection algorithm. This project demonstrates the exciting potential of using mobile sensors to create personalized health and wellness experiences.

Resources

#

Article Tags

reactnative
project
healthtech
datascience
W

WellAlly's core development team, comprised of healthcare professionals, software engineers, and UX designers committed to revolutionizing digital health management.

Expertise

Healthcare Technology
Software Development
User Experience
AI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey