Aula 12 PDM II • TCC / Técnico

Câmera Nativa com Capacitor & React

Material Didático para Sala de Aula

Rompa a Sandbox Web: Câmera Nativa no Celular

Aprenda como conectar o ecossistema React + Vite com o hardware físico dos dispositivos Android e iOS utilizando os plugins de Native Bridge do Capacitor 6.x.

@capacitor/camera v6.0

$ npm install @capacitor/camera

$ npx cap sync android

const image = await Camera.getPhoto({
  quality: 90,
  resultType: CameraResultType.Uri,
  source: CameraSource.Prompt
});
React Hooks Compatible Android Manifest Ready

1. Conceito e Arquitetura "Capacitor Bridge"

Como o código JavaScript da WebView consegue acionar a lente e o sensor físico do dispositivo Android/iOS.

A Sandbox da WebView

Por razões de segurança, o navegador web tradicional roda isolado (Sandbox). Ele não possui permissão direta para interagir com o hardware do telefone sem uma mediação do Sistema Operacional.

Ponte Oficial

Capacitor Bridge

O plugin @capacitor/camera expõe métodos em JS que convertem comandos da web para chamadas nativas em Java/Kotlin (Android) ou Swift (iOS).

Hardware Nativo

O aplicativo padrão de câmera do Android toma o controle da interface, tira a foto em resolução máxima, aplica pós-processamento e devolve o arquivo seguro via URI para a WebView.

Fluxo de Execução Completo da Câmera Nativa

1

Clique do Usuário

Dispara evento de clique no botão React onClick.

2

Chamada Capacitor

Executa Camera.getPhoto({...}) com parâmetros configurados.

3

Hardware & OS

Android solicita permissão, abre app de câmera e grava foto em arquivo temporário.

4

Render na WebView

Retorna image.webPath que é injetado na tag <img>.

Matriz de Decisão: Uri vs. Base64 vs. DataUrl

Modo (CameraResultType) O que retorna? Performance Uso Recomendado
CameraResultType.Uri URL local segura (capacitor://... ou http://...) Alta (Ultrarrápido) Exibição direta na interface da WebView (melhor opção geral).
CameraResultType.Base64 String de texto codificada gigante contendo os bytes da imagem. Média/Baixa (Consome RAM) Envio direto para APIs de banco de dados (JSON payloads) ou Supabase.
CameraResultType.DataUrl Base64 pré-formatado com prefixo MIME (data:image/jpeg;base64,...) Média Testes rápidos e renderização inline sem conversão prévia.

2. Guia Passo a Passo (Passo a Passo em Sala)

Siga estes 4 passos simples com sua turma para configurar o projeto completo do zero.

01

Instalação e Sincronização de Dependências

No terminal integrado do VS Code (na raiz do projeto React + Capacitor), rode os seguintes comandos:

# 1. Instala o plugin oficial da câmera

npm install @capacitor/camera

# 2. Copia o plugin compilado para a pasta nativa do Android

npx cap sync android

Importante: Sempre execute 'npx cap sync' após instalar qualquer novo plugin Capacitor!

02

Configuração do AndroidManifest.xml (Permissões)

Abra o arquivo localizado no diretório nativo do Android: android/app/src/main/AndroidManifest.xml e adicione as permissões de acesso ao hardware:

<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- Permissões para liberar uso da Câmera e Galeria -->
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

  <application ...>
    ...
  </application>
</manifest>
03

Criação do Componente React e Função Async

No seu componente React (ex: CameraView.jsx), importe a biblioteca e utilize um estado para armazenar o link da imagem:

import { useState } from 'react';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

export default function CameraView() {
  const [fotoUri, setFotoUri] = useState(null);

  const capturarFoto = async () => {
    try {
      const image = await Camera.getPhoto({
        quality: 90,
        allowEditing: false,
        resultType: CameraResultType.Uri,
        source: CameraSource.Prompt // Abre modal para escolher Câmera ou Fotos
      });

      // Atualiza o estado do React com o caminho seguro webPath
      setFotoUri(image.webPath);
    } catch (error) {
      console.warn("Captura cancelada pelo usuário ou falha:", error);
    }
  };
  // ... JSX de renderização ...
}
04

Testando no Smartphone Físico

Conecte seu celular Android via cabo USB, ative o modo Depuração USB nas opções de desenvolvedor e execute o deploy nativo:

$ npx cap run android

O Android Studio ou a CLI do Capacitor compilará o APK e instalará diretamente no dispositivo conectado.

3. Código Exemplo Completo (React + Capacitor)

Copie o arquivo integral abaixo para utilizar na demonstração em tempo real com os alunos.

src/components/NativeCameraModule.jsx
JSX / React 18
import React, { useState } from 'react';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

/**
 * Componente Demonstrativo de Aula - Integração Câmera Nativa
 * Disciplina: Programação para Dispositivos Móveis II
 */
export default function NativeCameraModule() {
  const [fotoUri, setFotoUri] = useState(null);
  const [carregando, setCarregando] = useState(false);
  const [mensagemStatus, setMensagemStatus] = useState('');

  // Função principal para invocar a câmera física ou galeria
  const capturarFotoOuGaleria = async () => {
    setCarregando(true);
    setMensagemStatus('');

    try {
      // 1. Invocação da API Nativa do Capacitor
      const image = await Camera.getPhoto({
        quality: 90,
        allowEditing: true, // Permite recortar foto nativamente
        resultType: CameraResultType.Uri, // Retorna caminho de arquivo seguro
        source: CameraSource.Prompt, // Prompt: Pergunta se quer Câmera ou Fotos
        promptLabelHeader: 'Selecione a Origem',
        promptLabelPhoto: 'Escolher da Galeria',
        promptLabelPicture: 'Tirar Foto com Câmera'
      });

      // 2. image.webPath é a URI pronta para ser exibida no atributo src do React
      if (image && image.webPath) {
        setFotoUri(image.webPath);
        setMensagemStatus('Foto capturada com sucesso!');
      }
    } catch (error) {
      console.warn('Operação cancelada ou indisponível no navegador:', error);
      setMensagemStatus('Captura cancelada ou não suportada neste ambiente.');
    } finally {
      setCarregando(false);
    }
  };

  // Limpar a foto capturada
  const descartarFoto = () => {
    setFotoUri(null);
    setMensagemStatus('Foto descartada.');
  };

  return (
    <div className="max-w-md mx-auto my-8 p-6 bg-slate-50 border border-slate-200 rounded-2xl shadow-lg font-sans">
      <div className="text-center mb-6">
        <span className="text-xs font-bold uppercase tracking-wider text-red-600 bg-red-100 px-3 py-1 rounded-full">
          Capacitor Hardware API
        </span>
        <h2 className="text-2xl font-black text-slate-900 mt-2">Captura Nativa</h2>
        <p className="text-xs text-slate-500 mt-1">Integração de Câmera e Galeria com React</p>
      </div>

      {/* Área de Exibição da Imagem (Preview) */}
      <div className="relative aspect-square w-full bg-slate-200 border-2 border-dashed border-slate-300 rounded-xl overflow-hidden flex items-center justify-center mb-6">
        {fotoUri ? (
          <img 
            src={fotoUri} 
            alt="Captura do Usuário" 
            className="w-full h-full object-cover rounded-xl transition duration-300"
          />
        ) : (
          <div className="text-center p-6 text-slate-400">
            <svg className="w-16 h-16 mx-auto mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
            </svg>
            <p className="text-xs font-semibold">Nenhuma imagem capturada ainda</p>
          </div>
        )}
      </div>

      {/* Status feedback */}
      {mensagemStatus && (
        <p className="text-xs text-center font-medium text-slate-600 mb-4 bg-white p-2 rounded-lg border border-slate-200">
          {mensagemStatus}
        </p>
      )}

      {/* Botões de Ação */}
      <div className="space-y-3">
        <button
          onClick={capturarFotoOuGaleria}
          disabled={carregando}
          className="w-full py-3.5 px-4 bg-red-600 hover:bg-red-700 active:bg-red-800 text-white font-bold text-sm rounded-xl shadow-md transition flex items-center justify-center space-x-2 disabled:opacity-50"
        >
          <span>{carregando ? 'Abrindo Câmera...' : '📷 Abrir Câmera / Galeria'}</span>
        </button>

        {fotoUri && (
          <button
            onClick={descartarFoto}
            className="w-full py-2.5 px-4 bg-slate-200 hover:bg-slate-300 text-slate-800 font-semibold text-xs rounded-xl transition"
          >
            Descartar Foto
          </button>
        )}
      </div>
    </div>
  );
}

4. Playground / Simulador Interativo

Teste as configurações da API do Capacitor direto no navegador antes de enviar o código ao celular físico.

Parâmetros de Camera.getPhoto()

90%

Quanto menor a qualidade, menor o tamanho do arquivo retornado.

Habilita a ferramenta de corte e edição nativa do Android.

REACT APP SIMULATION Preview na WebView
Aguardando disparo
Status: Inativo
// Payload JSON retornado pelo Capacitor:
{\n  "status": "Aguardando ação..."\n}
32 Alunos Atividade Prática Avaliativa

5. Exercícios Individuais Práticos

Cada aluno possui seu tema exclusivo de aplicativo para implementar com código fonte próprio.

Progresso Geral da Turma 0 de 32 Concluídos (0%)
Código copiado com sucesso!