Health Application with React Native: Step Counter

Let’s build a Health&Fitness mobile app with React Native by integrating directly with Apple HealthKit and Android Health Connect.

By building this project, you will learn how to:

  • Build React Native apps with Expo
  • Create beautiful User Interfaces
  • Create animated UI components with SVG and Reanimated
  • Integrate with Apple HealthKit to fetch health data on iOS devices
  • Integrate with Android Health Connect to fetch health data on Android devices

Here you can see the demo of the application you will build in this tutorial running on both iOS and Android devices.

Video tutorial

If you prefer a video tutorial, you can follow this project on our youtube channel.

Source code

You can find the source code of the final version of this project on our GitHub.

Initialize a new Expo project

Let’s create a brand new Expo project using the create-expo-app tool.

BASH
npx create-expo-app@latest StepCounter -t

If you don’t have the create-expo-app already install, npx will do it for you. Just press Y if asked.

For the template, let’s choose "Blank (TypeScript)".

Initialize a new Expo app using npx create-expo-app

After the project is initialized, let’s open it up in our editor of choice.

Open up a terminal, and let’s run the development server with npm start

The next step is to run our app on a device. The easiest way is to download the Expo Go app (available both on Play Market and App Store), and then scan the QR you see in the terminal. That way, you can continue to develop the app and see the hot updates directly on your device.

Optionally, you can run the app on an iOS Simulator by pressing i or on Android Emulator by pressing a. But for this, you have to set up the emulators using Xcode or Android Studio.

Blank Expo project running on iOS Simulator

When you see the app running on your device, you can open the file App.tsx and change the text to "Hello world". If we see the app fast refresh and display the updated text, we are ready for the next step.

Step-counter UI

Let’s start building our user interface. Our app will look very similar to Apple Fitness. It has a minimalistic and beautiful UI.

Step Counter UI

The most exciting component on this page is the Animated Progress Ring in the middle of the screen. We will get into it in a moment, but first, let’s start with the values displayed on the bottom

UI of the Statistic values about Steps, Distance, and FLights Climbed

The values are displayed as a group of 2 texts: Lable and Value. Let’s create a reusable component, that will receive the label and the value as properties, and will render a component with its styles.

First, create the src and src/components folder, and a file for our new component src/components/Value.tsx

TYPESCRIPT
import { StyleSheet, Text, View } from 'react-native';
type ValueProps = {
label: string;
value: string;
};
const Value = ({ label, value }: ValueProps) => (
<View>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{value}</Text>
</View>
);
const styles = StyleSheet.create({
label: {
color: 'white',
fontSize: 20,
},
value: {
fontSize: 45,
color: '#AFB3BE',
fontWeight: '500',
},
});
export default Value;

Let’s import our newly created component inside App.tsx and render our values on the screen.

Also, make sure to change the backgroundColor of the container to black, and also add the styles for values container. This container will be displayed as a row, and we are making the space between items, using the gap property newly introduced in React Native.

TYPESCRIPT
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import Value from './src/components/Value';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.values}>
<Value label="Steps" value="1219" />
<Value label="Distance" value="0,75 km" />
<Value label="Flights Climbed" value="12" />
</View>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black',
justifyContent: 'center',
padding: 12,
},
values: {
flexDirection: 'row',
gap: 25,
flexWrap: 'wrap',
}
});

With this, we have a basic UI that can render the text information for our Step Counter app.

Animated Progress Ring

Follow the Animated Progress Ring in React Native using SVG and Reanimated tutorial to build the animated progress ring.

After successfully building the animated progress ring, let’s add it to our project.

Firstly, install the svg and reanimated library

TYPESCRIPT
npx expo install react-native-svg react-native-reanimated

Then, let’s create a new file src/components/RingProgress.tsx, and add the code of our newly created component.

TYPESCRIPT
import {View} from 'react-native';
import SVG, { Circle } from 'react-native-svg';
import Animated, {useSharedValue, useAnimatedProps, withTiming} from 'react-native-reanimated'
import {useEffect} from 'react';
import { AntDesign } from '@expo/vector-icons';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
type RingProgressProps = {
radius?: number;
strokeWidth?: number;
progress?: number;
}
const color = "#EE0F55";
const RingProgress = ({radius = 100, strokeWidth = 30, progress = 0.5}: RingProgressProps) => {
const innerRadius = radius - strokeWidth / 2;
const circumference = 2 * Math.PI * innerRadius;
const fill = useSharedValue(0);
useEffect(() => {
fill.value = withTiming(progress, {duration: 1500})
}, [progress]);
const animatedProps = useAnimatedProps(() => ({
strokeDasharray: [circumference * fill.value, circumference]
}))
return (
<View style={{width: radius * 2, height: radius * 2}}>
<SVG style={{flex: 1}}>
{/* Background */}
<Circle
r={innerRadius}
cx={radius}
cy={radius}
fill="transparent"
stroke={color}
strokeWidth={strokeWidth}
opacity={0.2}
/>
{/* Foreground */}
<AnimatedCircle
animatedProps={animatedProps}
r={innerRadius}
cx={radius}
cy={radius}
fill="transparent"
stroke={color}
strokeWidth={strokeWidth}
strokeDasharray={[circumference * progress, circumference]}
strokeLinecap="round"
rotation="-90"
originX={radius}
originY={radius}
/>
</SVG>
<AntDesign
name="arrowright"
size={strokeWidth * 0.8}
color="black"
style={{
position: 'absolute',
alignSelf: 'center',
top: strokeWidth * 0.1,
}}
/>
</View>
)
}
export default RingProgress;

Now, we can import the Ring progress inside our App.tsx, and render it above our values.

TYPESCRIPT
import RingProgress from './src/components/RingProgress';
...
<RingProgress progress={0.8} />

Apple HealthKit integration

It’s time to integrate our app with Apple HealthKit in order to fetch Health data on iOS devices.

Follow the React-Native Apple HealtKit tutorial, and then come back for the next steps.

Android Health Connect integration

To make our application cross-platform, we have to get health data on Android devices as well. On Android, there is a new app called Health Connect that will serve as a central Health Data Hub on Android devices. This app is still in Beta, but the idea is to have it preinstalled on Android devices in the future.

Follow the React-Native Google Health Connect tutorial and then come back for the next steps.

Date picker

At this moment, our application is displaying the correct Fitness data (steps, distance, flights) for today both on iOS and Android. If we want to allow users to see the data for previous days, we will have to do some small changes and make the date for which we query health data dynamic.

Let’s start with rendering the date picker and storing the data in state inside our App.tsx

TYPESCRIPT
export default function App() {
...
const [date, setDate] = useState(new Date());
const changeDate = (numDays) => {
const currentDate = new Date(date); // Create a copy of the current date
// Update the date by adding/subtracting the number of days
currentDate.setDate(currentDate.getDate() + numDays);
setDate(currentDate); // Update the state variable
};
return (
<View style={styles.container}>
<View style={styles.datePicker}>
<AntDesign
onPress={() => changeDate(-1)}
name="left"
size={20}
color="#C3FF53"
/>
<Text style={styles.date}>{date.toDateString()}</Text>
<AntDesign
onPress={() => changeDate(1)}
name="right"
size={20}
color="#C3FF53"
/>
</View>
...
</View>
)
};

Now, we would be able to change the date using this picker at the top of the screen, but at this moment, the health data will not update yet.

We have to send the date variable to the useHealthData hook, and then use it inside our hook for the data fetching filtering.

Inside App.tsx, change to:

TYPESCRIPT
const { steps, flights, distance } = useHealthData(date);

And inside, useHealthData.tsx we will have to do a couple of changes. First, we will receive the date as a parameter of the hook:

TYPESCRIPT
const useHealthData = (date: Date) => {

Use this date when querying the data from Apple HealthKit, and make sure to add the date as a dependency of that useEffect, in order to fetch new data whenever the date changes.

TYPESCRIPT
useEffect(() => {
if (!hasPermissions) {
return;
}
const options: HealthInputOptions = {
date: date.toISOString(),
includeManuallyAdded: false,
};
...
}, [hasPermissions, date]);

Use the same date when fetching the data from Android Health Connect.

TYPESCRIPT
const timeRangeFilter: TimeRangeFilter = {
operator: 'between',
startTime: new Date(date.setHours(0, 0, 0, 0)).toISOString(),
endTime: new Date(date.setHours(23, 59, 59, 999)).toISOString(),
};

And also make sure to add the date as a dependency of the useEffect that fetches Android data.

Now, give it a try. When changing the date from the date picker, your app should show the updated health data for that date.

Congratulations 🎉

If you reached this point, that means you managed to build this Health Application.

I hope you enjoyed it and also learned something new.

If you did, consider sharing this project with someone that might benefit from it as well. I would appreciate your help 🙏

Episodes

Animated Progress Ring in React Native using SVG and Reanimated

Animated Progress Ring in React Native using SVG and Reanimated

Let’s build an animated progress ring, similar to Apple’s Fitness Rings. We will use react-native-svg to render the ring and React Native Reanimated to animate it 🤩

React-Native Apple HealtKit

React-Native Apple HealtKit

Integrate your React Native application with Apple HealthKit to access Health & Fitness data 🏃

React-Native Google Health Connect

React-Native Google Health Connect

Integrate your React Native application with Android Health Connect to access Health & Fitness data 🏃


Vadim Savin profile picture

Vadim Savin

Hi 👋 Let me introduce myself

I started my career as a Fullstack Developer when I was 16 y.o.

In search of more freedom, I transitioned to freelancing, which quickly grew into a global software development agency 🔥

Because that was not challenging enough, I started my startup which is used by over 20k users. This experience gave another meaning to being a (notJust) developer 🚀

I am also a proud ex-Amazon SDE and Certified AWS Architect, Developer and SysOps. You are in good hands 👌