Skip to main content

SDK ReactNative Demo app

React Native Demo app Tutorial

Functional Overview

In this tutorial we will create a demo app using the React Native SDK, the app will be able to:

  1. Create a user and set his information
  2. Discover nearby sensors
  3. Start a training with a selected Sensor
  4. Finish the training and upload to Server.
  5. Get the trainings uploaded to the Server

Procedure Overview

For the demo app we will:

  1. Create an empty app
  2. Use CLI
  3. Use Android Studio and Xcode for Apps generation
  4. Use TypeScript template

For a complete reference follow the official ReactNative docs

Required tools:

  • Android Studio
  • Xcode
  • NodeJS
  • React Native CLI
  • Yarn

Project Setup

Project creation

  1. On a terminal initialize your ReactNative project with typescript template

yarn create react-app rookmotion_reactnative_demo --template typescript

note

This will set up the default boilerplate for our Android and iOS development.

note

From now on unless otherwise specified, all commands will run on project' root folder

app Components

Our demo app will have the following views

  • User
    • Create
    • Set and edit information
    • Physiological variables
      • Set
      • Retrieve history data
  • Training
    • Get user trainings history
    • Training
      • Discover sensors
      • Create a training
      • Upload the training
  • Summaries
    • Get summaries types
    • Get trainings summaries history

app Configuration

  1. Import RookWrapper
  2. Wrap your components with RookWrapper
app.tsx
import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {NavigationContainer, DarkTheme, DefaultTheme} from '@react-navigation/native';
import {useColorScheme} from 'react-native';
import {NativeBaseProvider, Icon} from 'native-base';

// RookMotion
import {RookWrapper} from 'rook-core';

// Screens
import {Home} from './src/modules/users';
import {Summaries} from './src/modules/summaries';
import {Sensors} from './src/modules/sensors';

// Navigation
const Tab = createBottomTabNavigator();

// Light Theme
const light = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: '#000',
card: '#ffda00',
border: '#ffda00',
background: '#fff',
text: '#1a202c',
},
};

// Dark Theme
const dark = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: '#edf2f7',
card: '#1a202c',
border: '#1a202c',
background: '#2d3748',
text: '#edf2f7',
},
};

const App = () => {
const scheme = useColorScheme();

return (
<RookWrapper
config={{
authorization: 'eml1shx7FXNy5LMx6MlMpkVztCGR0WQYt8xa74jY',
tokenLevel: 'client_gDJp26XzExr7XjDhvxpacnXIuYnja',
url: 'https://api2.rookmotion.rookeries.dev',
}}>
<NativeBaseProvider>
<NavigationContainer theme={scheme === 'dark' ? dark : light}>
<Tab.Navigator
screenOptions={({route}) => ({
tabBarIcon: ({focused}) => {
let iconName;
switch (route.name) {
case 'Home':
iconName = focused ? 'home' : 'home';
break;
case 'Add':
iconName = focused
? 'ios-add-circle'
: 'ios-add-circle-outline';
break;
case 'Graphics':
iconName = focused ? 'stats-chart' : 'stats-chart-outline';
break;
default:
break;
}

return <Icon name={iconName} />;
},
})}>
<Tab.Screen
name="Home"
component={Home}
options={{
title: 'Users',
}}
/>
<Tab.Screen
name="Summaries"
component={Summaries}
options={{
title: 'Summaries',
}}
/>
<Tab.Screen
name="Trainings"
component={Sensors}
options={{
title: 'Trainings',
}}
/>
</Tab.Navigator>
</NavigationContainer>
</NativeBaseProvider>
</RookWrapper>
);
};

export default App;

SDK Modules

Now, we will go through each SDK component and create some code.

  1. We will interact with the user methods
    1. Create
    2. Check User Status
    3. Set user information
    4. Physiological variables
      1. Set user Physiological variables
      2. Retrieve user Physiological variables
  2. We will create a training combining the Bluetooth and Training modules
    1. Discover nearby sensors
    2. Start a training with the selected Sensor
    3. Finish the training
    4. Upload the training
    5. Download the last and previous trainings

User

note

user_uuid is going to be used all along the app to identify the user and execute actions

Process overview:

  1. Create
  2. Check User Status
  3. Set user information
  4. Physiological variables
    1. Set user Physiological variables
    2. Retrieve user Physiological variables

Create user

To create a user, an email is required with a valid format. When the user is created a user_uuid is generated

// noinspection JSAnnotator

import React, {useState} from 'react';
import {StyleSheet, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Text, Input, Button, Flex, Box, useToast} from 'native-base';
import {useRookUser} from 'rook-core';

export const RegisterUser = () => {
const isDarkMode = useColorScheme() === 'dark';
const toast = useToast();
const [email, setEmail] = useState('');
const {registerUserInApi} = useRookUser();

const verifyStatus = async ()
:
Promise < any >
=>
{
if (!email.trim()) {
toast.show({description: 'Enter a valid email',});
return;
}

try {
const response = await registerUserInApi({email});

if (response) {
toast.show({description: `User added correctly UUID: ${response}`,});
} else {
toast.show({description: 'User addition failed',});
}
} catch (error) {
toast.show({description: "We can't register your email"});
}
}
;

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Register User
</Text>

<Input
mx="3"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
placeholder="user email"
onChangeText={text => setEmail(text)}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Flex alignItems="center" marginTop={5}>
<Button size="sm" colorScheme="indigo" w="50%" onPress={verifyStatus}>
Register User
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

User status

Before creating a user, you can verify if the user already exists and save a creating request. If it exists, his UUID will be returned. When you need to decide between SignIn a user and SignUp, you can use this method.

import React, {useState} from 'react';
import {StyleSheet, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Text, Input, Button, Flex, Box, useToast} from 'native-base';
import {useRookUser} from 'rook-core';
import {If} from 'react-haiku';

export const UserStatus = () => {
const isDarkMode = useColorScheme() === 'dark';
const toast = useToast();
const [email, setEmail] = useState('');
const [uuid, setUUID] = useState('');
const {getUserStatusFromApi} = useRookUser();

const verifyStatus = async (): Promise<any> => {
if (!email.trim()) {
toast.show({description: 'Enter a valid email',});
return;
}

try {
const response = await getUserStatusFromApi({email});
if (response.active) {
console.log(response.user_uuid);
setUUID(response.user_uuid);
toast.show({description: `User exists. UUID: ${response.user_uuid}`,});
} else {
toast.show({description: 'The user does not exists',});
}
} catch (error) {
toast.show({description: "We can't verify the email"});
}
};

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Get user status
</Text>

<If isTrue={!!uuid}>
<Text
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
uuid: {uuid}
</Text>
</If>

<Input
mx="3"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
placeholder="user email"
onChangeText={text => setEmail(text)}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Flex alignItems="center" marginTop={5}>
<Button size="sm" colorScheme="indigo" w="50%" onPress={verifyStatus}>
Verify Status
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

User information

You can set and update user' information.

caution

Sex and birthday are key for training calculations, so real values must be used.

  • name
  • pseudonym
  • lastName1
  • lastName2
  • birthday
  • sex
import React, {useState} from 'react';

import moment from 'moment';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {StyleSheet, useColorScheme} from 'react-native';
import {Text, Input, Button, Flex, Box, useToast, HStack, Switch,} from 'native-base';
import {useRookUser} from 'rook-core';
import DatePicker from 'react-native-date-picker';

export const UpdateUserInformation = () => {
const isDarkMode = useColorScheme() === 'dark';
const toast = useToast();
const [data, setData] = useState({
uuid: '',
name: '',
pseudonym: '',
lastName1: '',
lastName2: '',
birthday: new Date(),
sex: false,
});
const {updateInformation} = useRookUser();

const verifyStatus = async (): Promise<any> => {
if (
!data.uuid.trim() ||
!data.name.trim() ||
!data.pseudonym.trim() ||
!data.lastName1.trim()
) {
toast.show({
description: 'Enter a valid variables',
});
return;
}

toast.show({description: 'updating', duration: 3000});

try {
const response = await updateInformation({
user_uuid: data.uuid,
attributes: {
name: data.name,
pseudonym: data.pseudonym,
last_name_1: data.lastName1,
last_name_2: data.lastName2 || '---',
birthday: moment(data.birthday).format('YYYY-MM-DD'),
sex: data.sex ? 'male' : 'female',
},
});

if (response) {
toast.show({
description: `the user was register updated with the next uuid: ${response.user_uuid}`,
});
} else {
toast.show({
description: 'We cannot update de user',
});
}
} catch (error) {
console.log(error);
console.log((error as any)?.response);
toast.show({description: "We can't update the information"});
}
};

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Update Variables
</Text>
<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
user uuid
</Text>
<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text => setData(prev => ({...prev, uuid: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>
<Text
marginTop={4}
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Name
</Text>
<Input
mx="3"
placeholder="Rook"
onChangeText={text => setData(prev => ({...prev, name: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>
<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Pseudonym
</Text>
<Input
mx="3"
placeholder="80"
onChangeText={text => setData(prev => ({...prev, pseudonym: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>
<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Last Name 1
</Text>
<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text => setData(prev => ({...prev, lastName1: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>
<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Last Name 2
</Text>
<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text => setData(prev => ({...prev, lastName2: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>
<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Birthday
</Text>
<DatePicker
date={data.birthday}
mode="date"
onDateChange={selectedDate => {
setData(prev => ({...prev, birthday: selectedDate || new Date()}));
}}
/>
<HStack
justifyContent="center"
alignItems="center"
space={4}
marginTop={4}>
<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Female
</Text>
<Switch
size="sm"
isChecked={data.sex}
onToggle={checked => setData(prev => ({...prev, sex: checked}))}
/>
<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Male
</Text>
</HStack>
;
<Flex alignItems="center" my={5}>
<Button size="sm" colorScheme="indigo" w="50%" onPress={verifyStatus}>
Update User
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

User Physiological variables

caution

Physiological variables are required so that RookMotion can perform training calculations for each user. If they are not provided, training calculations can be inaccurate.

note

When physiological variables are updated, a new entry is made on RookMotion Database instead of being replaced, allowing retrieval of an historical list of values.

Available variables are:

  • Height
  • Weight
  • RestingHR
Set User Physiological variables
import React, {useState} from 'react';
import {StyleSheet, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Text, Input, Button, Flex, Box, useToast} from 'native-base';
import {useRookUser} from 'rook-core';

export const UpdateUserVariables = () => {
const isDarkMode = useColorScheme() === 'dark';
const toast = useToast();
const [data, setData] = useState({
user: '',
height: '',
weight: '',
restingHR: '',
});
const {updatePhysiologicalVariables} = useRookUser();

const verifyStatus = async (): Promise<any> => {
if (
!data.user.trim() ||
Number.isNaN(Number(data.height)) ||
Number.isNaN(Number(data.weight)) ||
Number.isNaN(Number(data.restingHR))
) {
toast.show({
description: 'Enter a valid variables',
});
return;
}

toast.show({description: 'updating', duration: 3000});

try {
const response = await updatePhysiologicalVariables({
user_uuid: data.user,
physiological_variables: {
weight: data.weight,
height: data.height,
resting_heart_rate: data.restingHR,
},
});

if (response) {
toast.show({
description: `Variables updated for user uuid: ${response.user_uuid}`,
});
} else {
toast.show({
description: 'Variables update failed',
});
}
} catch (error) {
console.log(error);
toast.show({description: "We can't update variables"});
}
};

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Update Variables
</Text>

<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
user uuid
</Text>

<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text => setData(prev => ({...prev, user: text}))}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Text
marginTop={4}
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Weight
</Text>

<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text =>
setData(prev => ({...prev, weight: Number(text).toString()}))
}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Height
</Text>

<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text =>
setData(prev => ({...prev, height: Number(text).toString()}))
}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Text
fontSize="md"
marginTop={4}
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Resting HR
</Text>

<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text =>
setData(prev => ({...prev, restingHR: Number(text).toString()}))
}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Flex alignItems="center" my={5}>
<Button size="sm" colorScheme="indigo" w="50%" onPress={verifyStatus}>
Update User
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

Retrieve User Physiological variables
import React, {useState} from 'react';
import {Box, Text, Flex, Button, Input, useToast} from 'native-base';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {useColorScheme, StyleSheet} from 'react-native';
import DatePicker from 'react-native-date-picker';
import moment from 'moment';
import {useRookUser, PhysiologicalVariableType} from 'rook-core';

export const UserVariablesInPeriod = () => {
const isDarkMode = useColorScheme() === 'dark';

const [date, setDate] = useState(new Date());
const [secondDate, setSecondDate] = useState(new Date());
const [start, setStart] = useState('');
const [end, setEnd] = useState('');
const [id, setID] = useState('');
const [trunk, setTrunk] = useState<any>({});

const {getPhysiologicalVariablesByPeriod} = useRookUser();

const toast = useToast();

const verifyVariables = async (): Promise<any> => {
try {
console.log(start);
console.log(end);

const props = {
user_uuid: id,
from_date: start,
to_date: end,
page: 1,
};

toast.show({description: 'fetching . . .', duration: 3000});

const [w, h, r] = await Promise.all([
getPhysiologicalVariablesByPeriod({
...props,
type: PhysiologicalVariableType.WEIGHT,
}),
getPhysiologicalVariablesByPeriod({
...props,
type: PhysiologicalVariableType.HEIGHT,
}),
getPhysiologicalVariablesByPeriod({
...props,
type: PhysiologicalVariableType.RESTING_HEART_RATE,
}),
]);

console.log(w.data, h.data, r.data);

setTrunk({
weight: w.data[0]?.value || 0,
height: h.data[0]?.value || 0,
hr: r.data[0]?.value || 0,
});
} catch (error) {
console.log((error as any)?.response?.data);
toast.show({
description: 'We cannot show the info',
});
}
};

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Get variables in period
</Text>

<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
user uuid
</Text>

<Input
mx="3"
keyboardType="numeric"
placeholder="80"
onChangeText={text => setID(text)}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Starting date
</Text>

<DatePicker
mode="date"
date={date}
onDateChange={selectedDate => {
setStart(moment(selectedDate).format('YYYY-MM-DD'));
setDate(selectedDate || new Date());
}}
/>

<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
End date
</Text>

<DatePicker
date={secondDate}
mode="date"
onDateChange={endDate => {
setEnd(moment(endDate).format('YYYY-MM-DD'));
setSecondDate(endDate || new Date());
}}
/>

<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
weight: {trunk.weight}
</Text>
<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
height: {trunk.height}
</Text>
<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
hr: {trunk.hr}
</Text>

<Flex alignItems="center" marginTop={5}>
<Button
size="sm"
colorScheme="indigo"
w="50%"
onPress={verifyVariables}>
Verify Status
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

User Indexes

note

Once Physiological variables are set, RookMotion creates precalculated indexes or values for the SDK, so calculations aren't executed on each measurement.

note

User indexes are stored in the SDK and require to be synced from RookMotion servers. It's recommended to sync them on each app start to keep them as updated as possible.

import React, {useState} from 'react';
import {StyleSheet, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Text, Input, Button, Flex, Box, useToast} from 'native-base';
import {useRookUser} from 'rook-core';

export const SyncIndexes = () => {
const isDarkMode = useColorScheme() === 'dark';
const toast = useToast();
const [uuid, setUUID] = useState('');
const {syncIndexes} = useRookUser();

const verifyStatus = async (): Promise<any> => {
if (!uuid.trim()) {
toast.show({
description: 'Enter a valid email',
});
return;
}

toast.show({description: 'syncing', duration: 3000});

try {
const response = await syncIndexes({user_uuid: uuid});

console.log(response);

if (response.includes('SUCCESS')) {
toast.show({
description: 'the user account was synced',
});
} else {
toast.show({
description: 'cannot sync the user account',
});
}
} catch (error) {
toast.show({description: "We can't verify your uuid"});
}
};

return (
<Box>
<Text
fontSize="xl"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
Sync Indexes
</Text>

<Text
fontSize="md"
style={[
{
color: isDarkMode ? Colors.white : Colors.black,
},
styles.textCenter,
]}>
User uuid
</Text>

<Input
mx="3"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
placeholder="user uuid"
onChangeText={text => setUUID(text)}
style={{color: isDarkMode ? Colors.white : Colors.black}}
/>

<Flex alignItems="center" marginTop={5}>
<Button size="sm" colorScheme="indigo" w="50%" onPress={verifyStatus}>
Sync
</Button>
</Flex>
</Box>
);
};

const styles = StyleSheet.create({
textCenter: {
textAlign: 'center',
marginBottom: 8,
},
});

Training

In this section we will create the Trainings functionality for the demo app, the steps will be: 3. We will create a training combining the Bluetooth and Training modules

  1. Discover nearby sensors
  2. Start a training with the selected Sensor
  3. Finish the training
  4. Upload the training
  5. Download the last and previous trainings

Discover sensors nearby