WellAlly Logo
WellAlly康心伴
Development

How I Built a Smart Water Bottle Companion App with React Native and BLE

A step-by-step guide on creating an IoT-powered smart water bottle. Learn to connect a React Native app with an ESP32 microcontroller using Bluetooth Low Energy (BLE) to track water intake and send reminders.

W
2025-12-21
9 min read

In a world of interconnected devices, the Internet of Things (IoT) has opened up endless possibilities for creating innovative and practical solutions to everyday problems. This project is a hands-on guide to building a smart water bottle and a companion mobile app using React Native and an ESP32 microcontroller. We'll explore the fascinating world of Bluetooth Low Energy (BLE) communication and learn how to create a seamless connection between hardware and software.

This tutorial will walk you through the entire process, from setting up the ESP32 and reading sensor data to building a cross-platform mobile app with React Native to track your water intake and send you reminders to stay hydrated. Whether you're a mobile developer curious about IoT or a hardware enthusiast looking to build a user-friendly interface, this project is for you.

Understanding the Problem

Forgetting to drink enough water is a common problem. While there are many apps to track water intake, they rely on manual input. A truly "smart" water bottle should automatically track your consumption and proactively remind you to drink more.

The challenge lies in creating a reliable and low-power communication channel between the water bottle and a smartphone. This is where Bluetooth Low Energy (BLE) comes in. BLE is a wireless communication protocol designed for short-range communication with low power consumption, making it ideal for battery-powered IoT devices like our smart water bottle.

Our approach will be to use an ESP32 microcontroller to read data from a water level sensor and transmit it to a React Native app via BLE. The app will then display the water intake, track progress towards a daily goal, and send push notifications as reminders.

Prerequisites

To follow this tutorial, you'll need the following:

Step 1: Setting up the ESP32 as a BLE Peripheral

First, we need to program the ESP32 to act as a BLE peripheral, which will advertise its presence and expose data to our mobile app. We'll define a BLE service with a characteristic for the water level.

What we're doing

We will write an Arduino sketch for the ESP32 that:

  1. Initializes a BLE server.
  2. Creates a BLE service and a characteristic to hold the water level data.
  3. Simulates reading from a water level sensor.
  4. Updates the characteristic with the new water level value.

Implementation

code
// ESP32_BLE_Water_Bottle.ino

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic;
float waterLevel = 100.0; // Initial water level

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("Smart Water Bottle");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_NOTIFY
                    );

  pCharacteristic->setValue(String(waterLevel).c_str());
  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // Simulate water level decreasing
  if (waterLevel > 0) {
    waterLevel -= 5.0;
  } else {
    waterLevel = 100.0;
  }

  pCharacteristic->setValue(String(waterLevel).c_str());
  pCharacteristic->notify();
  Serial.print("Water Level: ");
  Serial.println(waterLevel);
  delay(2000);
}
Code collapsed

How it works

  • We use the BLEDevice library to initialize the ESP32 as a BLE device named "Smart Water Bottle".
  • We create a BLE service with a unique UUID. Services are collections of characteristics.
  • We create a BLE characteristic with a unique UUID. Characteristics are what hold the actual data. We give it READ and NOTIFY properties so our app can read the value and subscribe to changes.
  • In the loop() function, we simulate a decrease in the water level and update the characteristic's value. The notify() function sends the new value to any subscribed devices.

Step 2: Building the React Native Companion App

Now let's create the mobile app that will connect to our smart water bottle and display the water level. We'll use the react-native-ble-plx library to handle the BLE communication.

What we're doing

  1. Set up a new React Native project.
  2. Install and configure react-native-ble-plx.
  3. Implement BLE scanning to find our ESP32 device.
  4. Connect to the device and read the water level characteristic.
  5. Display the water level in the app's UI.

Implementation

First, create a new React Native project and install the necessary library:

code
npx react-native init SmartWaterBottleApp
cd SmartWaterBottleApp
npm install react-native-ble-plx
Code collapsed

Next, let's create a custom hook to manage our BLE logic:

code
// src/hooks/useBLE.js
import { useState } from 'react';
import { BleManager } from 'react-native-ble-plx';

const bleManager = new BleManager();

const useBLE = () => {
  const [device, setDevice] = useState(null);
  const [waterLevel, setWaterLevel] = useState(0);

  const requestPermissions = async () => {
    // Handle Android permissions
  };

  const scanForDevices = () => {
    bleManager.startDeviceScan(null, null, (error, scannedDevice) => {
      if (error) {
        console.error(error);
        return;
      }
      if (scannedDevice && scannedDevice.name === 'Smart Water Bottle') {
        bleManager.stopDeviceScan();
        connectToDevice(scannedDevice);
      }
    });
  };

  const connectToDevice = async (device) => {
    try {
      const connectedDevice = await bleManager.connectToDevice(device.id);
      setDevice(connectedDevice);
      await connectedDevice.discoverAllServicesAndCharacteristics();
      startStreamingData(connectedDevice);
    } catch (error) {
      console.error(error);
    }
  };

  const startStreamingData = (device) => {
    device.monitorCharacteristicForService(
      '4fafc201-1fb5-459e-8fcc-c5c9c331914b', // Service UUID
      'beb5483e-36e1-4688-b7f5-ea07361b26a8', // Characteristic UUID
      (error, characteristic) => {
        if (error) {
          console.error(error);
          return;
        }
        const decodedValue = atob(characteristic.value);
        setWaterLevel(parseFloat(decodedValue));
      }
    );
  };

  return {
    scanForDevices,
    device,
    waterLevel,
  };
};

export default useBLE;
Code collapsed

Now, let's use this hook in our main App.js component:

code
// App.js
import React, { useEffect } from 'react';
import { SafeAreaView, Text, Button, View, StyleSheet } from 'react-native';
import useBLE from './src/hooks/useBLE';

const App = () => {
  const { scanForDevices, device, waterLevel } = useBLE();

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>Smart Water Bottle</Text>
      <Button title="Scan for Devices" onPress={scanForDevices} />
      {device && (
        <View style={styles.deviceContainer}>
          <Text>Connected to: {device.name}</Text>
          <Text style={styles.waterLevel}>Water Level: {waterLevel}%</Text>
        </View>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  // Add your styles here
});

export default App;
Code collapsed

How it works

  • The useBLE hook encapsulates all the BLE logic.
  • scanForDevices starts scanning for BLE devices and filters for our "Smart Water Bottle".
  • Once the device is found, connectToDevice establishes a connection.
  • startStreamingData subscribes to the water level characteristic. Whenever the ESP32 sends a notification with a new value, the waterLevel state in our app is updated, and the UI re-renders to show the current water level.

Putting It All Together

Now, when you run the ESP32 code and then launch the React Native app, you can press the "Scan for Devices" button. The app will find your "Smart Water Bottle", connect to it, and start displaying the simulated water level in real-time.

For a real-world application, you would replace the simulated data in the ESP32 code with readings from an actual water level sensor. There are various types of sensors you can use, such as resistive or ultrasonic sensors. The principle remains the same: read the sensor value, map it to a percentage, and update the BLE characteristic.

Performance and Security Considerations

  • Power Consumption: BLE is designed for low power consumption. To further optimize, you can adjust the advertising interval on the ESP32 and the connection interval in your React Native app.
  • Security: For a production application, you should implement BLE security features like pairing and bonding to prevent unauthorized devices from connecting to your smart water bottle. The react-native-ble-plx library provides APIs for handling these security measures.
  • Error Handling: In a real-world scenario, you need robust error handling for cases like connection drops, permission denials, and data transmission errors.

Conclusion

In this tutorial, we've successfully built a functional prototype of a smart water bottle and a companion app using React Native and an ESP32. We've learned the fundamentals of BLE communication and how to bridge the gap between hardware and software.

This project is just the beginning. You can extend it by adding features like:

  • Tracking daily water intake history.
  • Setting customizable hydration goals.
  • Integrating with health and fitness apps.
  • Designing and 3D-printing a custom enclosure for the electronics.

The world of IoT is vast and full of exciting possibilities. I encourage you to experiment with this project, explore different sensors, and build your own innovative connected devices.

Resources

#

Article Tags

reactnativeiotblehealthtech
W

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

Expertise

Healthcare TechnologySoftware DevelopmentUser ExperienceAI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey

© 2024 康心伴 WellAlly · Professional Health Management