📱 Aula Completa

Câmera no React Native e a Ponte Nativa

🎯 Objetivos da Aula

Ao final desta aula, o aluno será capaz de:

  • Entender como o React Native acessa recursos do celular (Câmera, GPS, etc.)
  • Compreender o conceito de Native Modules e a arquitetura do React Native
  • Solicitar permissões corretamente para Android e iOS
  • Capturar fotos e alternar entre câmeras usando o Expo Camera

🌉 O Mistério: Como o JS acessa o Hardware?

O JavaScript roda em um ambiente "sandbox" (isolado). Ele não tem acesso direto à câmera, GPS ou bluetooth do celular.

Então, como o React Native faz isso?

A resposta está na Arquitetura de Comunicação.

🏗️ A Arquitetura do React Native

O React Native divide a aplicação em 3 camadas:

  1. Camada JS (React): Onde escrevemos nossos componentes (UI e Lógica).
  2. A Ponte (Bridge / JSI): O "tradutor" entre o JavaScript e o celular.
  3. Camada Nativa (Java/Kotlin/Swift): Onde o código realmente fala com o hardware (Câmera do iOS ou Android).

📡 A Ponte (The Bridge) vs JSI

Como funcionava antes (Bridge):

O JS enviava uma mensagem em texto (JSON) para o Nativo. O Nativo lia, executava a ação da câmera, e devolvia outro JSON.
Problema: Era lento (assíncrono por natureza).

Como funciona hoje (JSI - JavaScript Interface):

O JS e o Nativo compartilham a mesma memória. O JS pode chamar uma função em C++ que acessa a câmera diretamente, de forma síncrona e ultrarrápida.

📦 O que são as "APIs Nativas"?

Para acessar a câmera, a Apple (iOS) e o Google (Android) usam códigos completamente diferentes:

  • iOS: AVFoundation (Swift)
  • Android: android.hardware.camera2 (Kotlin)

Para não obrigarmos você a escrever em duas linguagens, o ecossistema cria Módulos Nativos (Native Modules): pacotes que já vêm com o código em Swift/Kotlin e uma "interface" em JavaScript para nós.

🛠️ Expo Modules: O Padrão Atual

No ecossistema Expo, esses módulos foram reescritos usando a tecnologia JSI.

Quando você instala o expo-camera, na verdade você está instalando:

  • Uma interface em JS (CameraView.ts)
  • Um código nativo em C++/Swift/Kotlin que fala com a câmera de verdade.

Por isso usamos expo-camera e não a API web navigator.mediaDevices!

🔄 O Fluxo de Capturar uma Foto

[ Seu Código JS ]
   │ (1) Você chama: cameraRef.current.takePictureAsync()
   ▼
[ Expo Module (JSI) ]
   │ (2) Traduz a chamada em tempo real via memória compartilhada
   ▼
[ Camada Nativa (Swift/Kotlin) ]
   │ (3) Aciona o hardware da câmera física
   ▼
[ Sistema Operacional ]
   │ (4) Captura a luz e gera um arquivo de imagem
   ▼
[ Seu Código JS ]
   │ (5) Retorna a URI (caminho) da foto para o seu estado
   ▼
[ Interface ]
   │ (6) O React re-renderiza mostrando a foto

⚠️ 1️⃣ Permissões: O Sistema Operacional no Controle

Como a câmera é um recurso de privacidade, o iOS e o Android exigem que o app peça permissão.

Se o módulo nativo tentar acessar a câmera sem permissão, o App vai crashar.
Por isso, pedimos primeiro via código e configuramos no manifest.

Android

Adicionar no android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

(No Expo gerenciado, isso é feito automaticamente no build).

iOS

Adicionar no app.json ou app.config.js:

{
  "expo": {
    "ios": {
      "infoPlist": {
        "NSCameraUsageDescription": "Este app precisa da câmera para tirar fotos"
      }
    }
  }
}

(Se não colocar isso, a Apple rejeita o app na loja).

📥 2️⃣ Instalação do Módulo Nativo

npx expo install expo-camera

Nota: O comando expo install garante que baixe a versão exata que é compatível com o JSI e a versão do seu projeto.

💻 3️⃣ Exemplo Básico (Código Atualizado)

Arquivo: App.tsx

import { useRef, useState } from "react";
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  Alert,
  Image,
} from "react-native";
import { CameraView, CameraType, useCameraPermissions } from "expo-camera";
export default function App() {
  const [facing, setFacing] = useState<CameraType>('back');
  const [permission, requestPermission] = useCameraPermissions();
  const [photo, setPhoto] = useState<string | null>(null);
  const cameraRef = useRef<CameraView>(null); // Ref tipada corretamente

  // Tela de permissão
  if (!permission) return <View />;
  if (!permission.granted) {
    return (
      <View style={styles.center}>
        <Text style={styles.text}>Precisamos de acesso à câmera nativa</Text>
        <TouchableOpacity style={styles.button} onPress={requestPermission}>
          <Text style={styles.buttonText}>Permitir</Text>
        </TouchableOpacity>
      </View>
    );
  }

// Função que cruza a Ponte Nativa (JSI)
const takePicture = async () => {
  if (!cameraRef.current) return;
  try {
    // O método pertence ao módulo nativo da Câmera
    const result = await cameraRef.current.takePictureAsync();
    setPhoto(result.uri);
  } catch (error) {
    Alert.alert("Erro", "Falha ao se comunicar com a câmera");
  }
};

// ... (Continua no próximo slide)

💻 Exemplo Básico (Continuação)

  // ... (Código anterior)

  const toggleCamera = () => setFacing(facing === 'back' ? 'front' : 'back');
  const discardPhoto = () => setPhoto(null);

  // Preview da Foto Capturada
  if (photo) {
    return (
      <View style={styles.container}>
        <Image source={{ uri: photo }} style={styles.preview} />
        <View style={styles.previewButtons}>
          <TouchableOpacity style={[styles.button, styles.discard]} onPress={discardPhoto}>
            <Text style={styles.buttonText}>Descartar</Text>
          </TouchableOpacity>
          <TouchableOpacity style={[styles.button, styles.save]} onPress={discardPhoto}>
            <Text style={styles.buttonText}>Salvar</Text>
          </TouchableOpacity>
        </View>
      </View>
    );
  }


// Renderização da View da Câmera (Native UI Component)
return (
  <View style={styles.container}>
    <CameraView style={styles.camera} facing={facing} ref={cameraRef}>
      <View style={styles.controls}>
        <TouchableOpacity style={styles.iconButton} onPress={toggleCamera}>
          <Text style={styles.icon}>🔄</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.captureButton} onPress={takePicture}>
          <View style={styles.captureInner} />
        </TouchableOpacity>
        <View style={styles.placeholder} />
      </View>
    </CameraView>
  </View>
);
}
// ... (Estilos omitidos para caber no slide, iguais ao original)

🔍 4️⃣ Entendendo o Código à Luz da Arquitetura

Código O que acontece por baixo dos panos?
<CameraView /> O React renderiza uma Native View real do iOS/Android, não uma tag HTML.
useCameraPermissions() O Módulo Nativo checa os arquivos InfoPlist (iOS) ou Manifest (Android).
cameraRef.current É a nossa "ponte" direta para a instância da câmera que está rodando no C++.
await takePictureAsync() A JSI manda o comando síncrono pro C++, que aciona o hardware e nos devolve uma Promise com a URI.

🚀 5️⃣ Exercício Prático: Integrando outros Módulos

Agora que você entende que tudo é um Módulo Nativo, tente conectar outros:

  1. Salvar no Disco: Use o módulo expo-file-system para mover a foto da memória temporária para a pasta real do celular.
  2. Compartilhar: Use expo-sharing para abrir o menu nativo de compartilhamento do iOS/Android.
  3. Galeria: Use expo-media-library para salvar a foto nos álbuns do usuário (exige outra permissão!).

Dica: Todos seguirão o mesmo padrão: Instalar -> Pedir Permissão -> Importar -> Usar ref/async.

📚 Resumo dos Conceitos

  • 🌉 JS não toca no hardware: Precisamos de uma ponte.
  • ⚡ JSI: Tecnologia moderna que torna a comunicação nativa rápida e síncrona.
  • 📦 Native Modules: Pacotes que trazem o código Swift/Kotlin embrulhados para o JS (Ex: expo-camera).
  • 🔒 Permissões: Regras do SO (iOS/Android) para proteger o usuário.
  • 📸 CameraView: Componente de UI Nativo controlado via JS.

Próximos Passos

  • Estudar como criar o seu próprio Native Module (Nativo puro).
  • Explorar gravação de vídeo com a mesma API.
  • Entender o ciclo de vida de componentes pesados (câmeras consomem muita bateria e memória).

Dúvidas?