Nessa aula iremos falar sobre periféricos, que são recursos do próprio dispositivo, como câmera, acelerômetro, GPS, etc. Esses recursos são muito utilizados em todo tipo de aplicação, como, por exemplo, para escanear um código QR ou de barras, saber a localização do dispositivo, vibrar o telefone, capturar os movimentos e rotações do telefone em jogos, etc.

Felizmente o Expo facilita bastante a utilização desses recursos, dispondo de várias bibliotecas oficiais para utilizá-los, como a expo-camera, o expo-sensors, o expo-location, etc. Não teríamos como abordar todos esses recursos no curso, mas você pode ver todos os que estão disponíveis no Expo através da página de Referência da API, disponível em https://docs.expo.dev/versions/latest/ .

Aqui nós iremos demonstrar como usar o acelerômetro, um recurso físico do dispositivo que consegue responder à movimentos em um espaço tridimensional, ou seja, conseguimos saber quando estamos “balançando” o nosso telefone e em qual direção.

  1. Vamos começar preparando um nova tela no nosso projeto como temos feito a cada aula. Primeiro, criamos uma tela simples em um novo arquivo chamado AccelerometerScreen.js na pasta “screens”:

    import { useNavigation } from "@react-navigation/native";
    import Container from "../components/Container";
    import StyledButton from "../components/StyledButton";
    import StyledTitle from "../components/StyledTitle";
    
    export default function UsingApisScreen() {
      const navigation = useNavigation()
    
      const navigateBack = () => navigation.goBack()
    
      return (
        <Container>
          <StyledTitle>Periféricos</StyledTitle>
          <StyledButton onPress={navigateBack}>Voltar</StyledButton>
        </Container>
      )
    }
    
  2. Depois nós a adicionamos o AccelerometerScreen como uma nova tela no StackNavigator:

    // ...
    import UsingApisScreen from "./screens/UsingApisScreen"
    import AccelerometerScreen from "./screens/AccelerometerScreen"
    
    const Stack = createNativeStackNavigator()
    
    // ...
    
            <Stack.Screen name="API" component={UsingApisScreen} />
    				<Stack.Screen name="Accelerometer" component={AccelerometerScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      )
    }
    
  3. E, por último, adicionamos um novo botão na tela inicial em HomeScreen.js para nos levar até essa nova tela:

    // ...
    
      const toApiScreen = () => {
        navigation.navigate('API')
      }
    
    	const toAccelerometerScreen = () => {
    		navigation.navigate('Accelerometer')
    	}
    
      return (
        <Container>
          <StyledTitle>Olá, mundo!</StyledTitle>
          <StatusBar style="auto" />
          <NavButton text="Aula de Navegação" onPress={toNavigationScreen} />
          <NavButton text="Aula de ScrollView" onPress={toScrollViewScreen} />
          <NavButton text="Aula de FlatList" onPress={toFlatListScreen} />
          <NavButton text="Aula de Styled Components" onPress={toStyledComponentsScreen} />
          <NavButton text="Aula de Consumo de APIs" onPress={toApiScreen} />
    			<NavButton text="Aula de Periféricos" onPress={toAccelerometerScreen} />
        </Container>
      )
    }
    
  4. Agora que temos tudo pronto podemos usar o acelerômetro. Para isso, a primeira coisa a fazer é instalar a biblioteca oficial do Expo responsável por interagir com ele (e com vários outros sensores do telefone), o expo-sensors. A instalamos com o comando:

    npx expo install expo-sensors
    
  5. Vamos começar adicionando alguns textos na tela que nos mostrarão a movimentação atual do dispositivo em cada uma das direções (x, y e z) e se o nosso componente está “ouvindo” o acelerômetro ou não:

    // ...
    import StyledTitle from "../components/StyledTitle";
    import { StyleSheet, Text, View } from 'react-native';
    
    // ...
    
      return (
        <Container>
          <StyledTitle>Periféricos</StyledTitle>
          <StyledButton onPress={navigateBack}>Voltar</StyledButton>
    			<View style={styles.container}>
    				<Text style={styles.text}>Acelerômetro</Text>
            <Text>(em g's sendo 1g = 9.81 m/s²)</Text>
    	      <Text style={styles.text}>x: </Text>
    	      <Text style={styles.text}>y: </Text>
    	      <Text style={styles.text}>z: </Text>
    				<StyledButton>
    					Desligado
    				</StyledButton>
    	    </View>
        </Container>
      )
    }
    
    const styles = StyleSheet.create({
    	container: {
    		flex: 1,
    		alignItems: 'center',
    		justifyContent: 'center'
    	},
    	text: {
    		fontSize: 20
    	}
    })
    
  6. Com a tela preparada, vamos criar dois estados importantes que precisamos ter para usar o acelerômetro. O primeiro será o accelerometerData, que armazenará os dados do acelerômetro, ou seja, os valores de x, y e z. O segundo será a subscription, que será a “inscrição” no sensor do acelerômetro, ou seja, dirá se estamos atualmente “ouvindo” as mudanças no sensor. Vamos fazer isso com o useState():

    Obs.: repare que estamos também definindo como valor inicial para accelerometerData um objeto contendo x, y e z todos iguais a 0, e também o valor inicial de subscription como null.

    // ...
    import { StyleSheet, Text, View } from 'react-native';
    import { useState } from "react";
    
    export default function UsingApisScreen() {
      const navigation = useNavigation()
    	const [accelerometerData, setAccelerometerData] = useState({ x: 0, y: 0, z: 0 })
    	const [subscription, setSubscription] = useState(null)
    
      const navigateBack = () => navigation.goBack()
    
    // ...
    
  7. Agora vamos criar duas funções essenciais, _subscribe() e _unsubscribe(). Vamos usar ela para controlar o processo de começar a “ouvir” pelas alterações no movimento do telefone e também parar de “ouvir” por essas alterações. É importante parar de ouvir pelo sensor quando nosso componente é destruído (ou desrenderizado da tela) para evitar problemas de performance no aplicativo. Na função de inscrição vamos usar o objeto Accelerometer da biblioteca expo-sensors.

    Nota somente para a gravação: tentar explicar com calma como funciona a função _subscribe, ela seta o estado subscription para ser igual ao retorno da função Accelerometer.addListener(), que por sua vez usa a função setAccelerometerData do outro estado como callback para que sempre que o acelerômetro detectar uma mudança ele próprio atualizar o nosso estado contendo os valores de x, y e z.

    Já a função _unsubscribe verifica se o estado subscription está ativo, ou seja, se estamos ouvindo o sensor, para então chamar o método .remove() que é do próprio listener (que adicionamos com o setSubscription da função anterior) e serve para parar de ouvir o sensor e depois nulifica o estado setSubscription manualmente, voltando ao valor inicial.

    // ...
    import { useState } from "react";
    import { Accelerometer } from 'expo-sensors';
    
    export default function UsingApisScreen() {
      const navigation = useNavigation()
    	const [accelerometerData, setAccelerometerData] = useState({ x: 0, y: 0, z: 0 })
    	const [subscription, setSubscription] = useState(null)
    
    	const _subscribe = () => {
        setSubscription(Accelerometer.addListener(setAccelerometerData));
      };
    
      const _unsubscribe = () => {
        if (subscription) subscription.remove();
        setSubscription(null);
      };
    
      const navigateBack = () => navigation.goBack()
    
    // ...
    
  8. Precisamos então utilizar o hook useEffect() para chamar a função _subscribe() no momento que nosso componente for renderizado e chamar a função _unsubscribe no momento que nosso componente for ser destruído:

    // ...
    import { useState, useEffect } from "react";
    import { Accelerometer } from 'expo-sensors';
    
    export default function UsingApisScreen() {
      const navigation = useNavigation()
    	const [accelerometerData, setAccelerometerData] = useState({ x: 0, y: 0, z: 0 })
    	const [subscription, setSubscription] = useState(null)
    
    	const _subscribe = () => {
        setSubscription(Accelerometer.addListener(setAccelerometerData));
      };
    
      const _unsubscribe = () => {
        if (subscription) subscription.remove();
        setSubscription(null);
      };
    
    	useEffect(() => {
    		_subscribe();
    		return () => _unsubscribe(); // o return do useEffect serve para "limpeza"
    	}, []); // um array de dependências vazio para que ele só seja executado uma vez
    
      const navigateBack = () => navigation.goBack()
    
    // ...
    
  9. Agora que temos tudo pronto podemos exibir os dados do acelerômetro na tela e vê-los mudando a medida que mexemos o telefone:

    Obs.: em um dispositivo físico podemos perceber claramente a força da gravidade sendo aplicada em cada eixo de acordo com a posição do telefone: se estivermos com ele “em pé” veremos que o eixo y se aproxima de 1g, se estivermos com ele “deitado” com a tela para cima o eixo z se aproxima de 1g e se estivermos com ele “deitado” com a tela de lado vemos que o eixo x se aproxima de 1g.

    Obs².: no dispositivo físico ao mexer rápido o telefone abrimos o menu do desenvolvedor do Expo, o que pode atrapalhar na hora de ver as mudanças na tela.

    // ...
    
    	const navigateBack = () => navigation.goBack()
    
      return (
        <Container>
          <StyledTitle>Periféricos</StyledTitle>
          <StyledButton onPress={navigateBack}>Voltar</StyledButton>
          <View style={styles.container}>
            <Text style={styles.text}>Acelerômetro</Text>
            <Text>(em g's sendo 1g = 9.81 m/s²)</Text>
            <Text style={styles.text}>x: {accelerometerData.x}</Text>
            <Text style={styles.text}>y: {accelerometerData.y}</Text>
            <Text style={styles.text}>z: {accelerometerData.z}</Text>
            <StyledButton onPress={subscription ? _unsubscribe : _subscribe}>
              {subscription ? 'Ativado' : 'Desativado'}
            </StyledButton>
          </View>
        </Container>
      )
    }
    
  10. Para experimentarmos um pouco mais com o acelerômetro podemos mudar a cor de fundo da tela se x for maior do que 1:

    // ...
    
      return (
        <Container>
          <StyledTitle>Periféricos</StyledTitle>
          <StyledButton onPress={navigateBack}>Voltar</StyledButton>
          <View style={accelerometerData.x < 1 ? styles.container : styles.altContainer}>
            <Text style={styles.text}>Acelerômetro</Text>
            <Text>(em g's sendo 1g = 9.81 m/s²)</Text>
            <Text style={styles.text}>x: {accelerometerData.x}</Text>
            <Text style={styles.text}>y: {accelerometerData.y}</Text>
            <Text style={styles.text}>z: {accelerometerData.z}</Text>
            <StyledButton onPress={subscription ? _unsubscribe : _subscribe}>
              {subscription ? 'Ativado' : 'Desativado'}
            </StyledButton>
          </View>
        </Container>
      )
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
      },
      altContainer: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#f64348'
      },
      text: {
        fontSize: 20
      }
    })