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:
- Create a user and set his information
- Discover nearby sensors
- Start a training with a selected Sensor
- Finish the training and upload to Server.
- Get the trainings uploaded to the Server
Procedure Overview
For the demo app we will:
- Create an empty app
- Use CLI
- Use Android Studio and Xcode for Apps generation
- 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
- On a terminal initialize your ReactNative project with typescript template
yarn create react-app rookmotion_reactnative_demo --template typescript
This will set up the default boilerplate for our Android and iOS development.
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
- Import
RookWrapper
- Wrap your components with
RookWrapper
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.
- We will interact with the user methods
- We will create a training combining the Bluetooth and Training modules
- Discover nearby sensors
- Start a training with the selected Sensor
- Finish the training
- Upload the training
- Download the last and previous trainings
User
user_uuid is going to be used all along the app to identify the user and execute actions
Process overview:
- Create
- Check User Status
- Set user information
- 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.
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
Physiological variables are required so that RookMotion can perform training calculations for each user. If they are not provided, training calculations can be inaccurate.
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
Once Physiological variables are set, RookMotion creates precalculated indexes or values for the SDK, so calculations aren't executed on each measurement.
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
- Discover nearby sensors
- Start a training with the selected Sensor
- Finish the training
- Upload the training
- Download the last and previous trainings