TL;DR: Criei uma implementação completa de bridge nativa para integrar o SDK da Unico em aplicações React Native, incluindo captura de selfies e documentos. Este artigo documenta todo o processo, os desafios enfrentados e as soluções encontradas.
🎯 O Problema que Me Motivou
Durante o desenvolvimento de uma aplicação React Native que precisava de verificação biométrica, me deparei com um desafio frustrante: como integrar o SDK da Unico com React Native?
Os Obstáculos Encontrados:
- ❌ Ausência de documentação específica para React Native
- ❌ Falta de exemplos práticos de implementação
- ❌ Complexidade da bridge nativa em duas plataformas
- ❌ Configurações obscuras que consumiram dias
Após muito pesquisar e não encontrar recursos adequados, decidi criar uma solução completa e compartilhar com a comunidade para que outros desenvolvedores não passem pelo mesmo sufoco.
🏗️ A Arquitetura da Solução
Desenvolvi uma arquitetura robusta que combina Clean Architecture no React Native com bridges nativas otimizadas para iOS e Android.
Visão Geral da Estrutura:
📦 Projeto
├── 🤖 Android (Kotlin)
│ ├── UnicoSdkModule (Bridge principal)
│ ├── UnicoConfig (Credenciais)
│ ├── UnicoTheme (Personalização)
│ └── UnicoSdkPackage (Registro)
├── 🍎 iOS (Swift)
│ ├── UnicoSdkModule (Bridge principal)
│ ├── UnicoConfig (Credenciais)
│ ├── UnicoTheme (Personalização)
│ └── Bridge Header (Interoperabilidade)
└── ⚛️ React Native (TypeScript)
├── Domain (Regras de negócio)
├── Data (Repositórios)
├── Infrastructure (Serviços)
└── Presentation (UI e Hooks)
🤖 Implementação Android: Do Zero ao Funcionamento
1. Configuração do Gradle - A Base de Tudo
O primeiro desafio foi configurar corretamente o sistema de build do Android. O SDK da Unico não está no Maven Central, então precisei adicionar o repositório específico:
// android/build.gradle (projeto)
allprojects {
repositories {
google()
mavenCentral()
// 🎯 CRÍTICO: Repositório do SDK da Unico
maven { url "https://maven-sdk.unico.run/sdk-mobile" }
}
}
E no android/app/build.gradle
, a dependência e configuração das variáveis de ambiente:
dependencies {
implementation "io.unico:capture:5.33.0"
// outras dependências...
}
android {
defaultConfig {
// 🔑 Injeção das variáveis do .env
buildConfigField "String", "UNICO_BUNDLE_ID", "\"${project.env.get("UNICO_BUNDLE_ID") ?: ""}\""
buildConfigField "String", "UNICO_HOST_KEY", "\"${project.env.get("UNICO_HOST_KEY") ?: ""}\""
}
}
2. A Bridge Principal - UnicoSdkModule.kt
Esta é a peça central que conecta JavaScript ao SDK nativo. Implementei usando handlers especializados para melhor organização:
class UnicoSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
@ReactMethod
fun captureSelfie(promise: Promise) {
runOnMainThread {
try {
// Validações de permissão e activity
val activity = activity ?: run {
promise.reject("ERROR", "Activity not found")
return@runOnMainThread
}
if (!hasPermissions()) {
promise.reject("PERMISSION_DENIED", "Permissão de câmera necessária")
return@runOnMainThread
}
// Configuração do SDK
val config = UnicoConfig()
val theme = UnicoTheme()
AcessoBio(activity, callback)
.setAutoCapture(true)
.setSmartFrame(true)
.setEnvironment(Environment.UAT)
.setTheme(theme)
.build()
.prepareCamera(config, selfieCallback)
} catch (e: Exception) {
promise.reject("ERROR", "Erro geral: ${e.message}")
}
}
}
}
3. Gerenciamento de Credenciais
Criei uma classe específica para gerenciar as credenciais de forma segura:
class UnicoConfig : AcessoBioConfigDataSource {
override fun getBundleIdentifier(): String {
return BuildConfig.UNICO_BUNDLE_ID ?: ""
}
override fun getHostKey(): String {
return BuildConfig.UNICO_HOST_KEY ?: ""
}
}
As credenciais fluem do arquivo .env
→ react-native-config
→ BuildConfig
→ UnicoConfig
.
🍎 Implementação iOS: Swift + Objective-C
1. Configuração do CocoaPods
No iOS, o gerenciamento de dependências é feito via CocoaPods:
# Podfile
target 'MeuApp' do
# SDK da Unico
pod 'unicocheck-ios'
pod 'react-native-config', :path => '../node_modules/react-native-config'
# Setup de permissões - apenas Camera
setup_permissions(['Camera'])
end
2. Bridge Header - Conectando Worlds
Um dos maiores desafios no iOS foi configurar corretamente o bridge header para conectar Objective-C com Swift:
// MeuApp-Bridging-Header.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTViewManager.h>
#import <React/RCTUtils.h>
3. Arquitetura com Handlers Especializados
Implementei uma arquitetura modular no iOS usando handlers especializados:
@objc(UnicoSdk)
class UnicoSdkModule: RCTEventEmitter {
@objc
func captureSelfie(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
DispatchQueue.main.async {
guard let rootViewController = RCTPresentedViewController() else {
reject("ERROR", "Não foi possível obter o view controller", nil)
return
}
let captureHandler = SelfieCaptureHandler(
resolve: resolve,
reject: reject,
eventEmitter: self
)
captureHandler.startCapture(from: rootViewController)
}
}
}
// Handler especializado para selfie
class SelfieCaptureHandler: NSObject {
private let resolve: RCTPromiseResolveBlock
private let reject: RCTPromiseRejectBlock
private weak var eventEmitter: RCTEventEmitter?
func startCapture(from viewController: UIViewController) {
let manager = AcessoBioManager(viewController: viewController)
manager?.setTheme(UnicoTheme())
manager?.setEnvironment(.UAT)
manager?.setSmartFrame(true)
manager?.setAutoCapture(true)
manager?.build().prepareSelfieCamera(self, config: UnicoConfig())
}
}
4. Configuração de Variáveis no Info.plist
No iOS, as variáveis do .env
são injetadas automaticamente no Info.plist
:
<!-- Info.plist -->
<key>UNICO_SDK_KEY</key>
<string>$(UNICO_SDK_KEY)</string>
<key>UNICO_BUNDLE_IDENTIFIER</key>
<string>$(UNICO_BUNDLE_IDENTIFIER)</string>
⚛️ React Native: Clean Architecture
1. Estrutura de Camadas
Implementei uma arquitetura limpa e escalável:
// Domain Layer - Regras de negócio
export enum DocumentType {
CNH_FRENTE = 'CNH_FRENTE',
CNH_VERSO = 'CNH_VERSO',
RG_FRENTE = 'RG_FRENTE',
RG_VERSO = 'RG_VERSO',
CPF = 'CPF',
}
export type CapturedItem = {
id: string;
type: ActionType;
documentType?: DocumentType;
result: UnicoResult;
timestamp: string;
status: DocumentStatus;
};
// Use Case - Lógica de captura
export const executeCaptureSelfie = async (
unicoRepository: UnicoRepository
): Promise<CapturedItem> => {
const result = await unicoRepository.captureSelfie();
if (!result.success || !result.data) {
throw new Error(result.error || 'Erro na captura da selfie');
}
return {
id: `selfie_${Date.now()}`,
type: ActionType.SELFIE,
result: result.data,
timestamp: new Date().toISOString(),
status: DocumentStatus.CAPTURED,
};
};
2. Infrastructure Layer - Conexão com Nativo
// UnicoSdkService.ts - Ponte com módulos nativos
import { NativeEventEmitter, NativeModules } from 'react-native';
const { UnicoSdk: UnicoSdkModule } = NativeModules;
export const testConnection = (): Promise<string> => {
return UnicoSdkModule.testConnection();
};
export const captureSelfie = (): Promise<UnicoResult> => {
return UnicoSdkModule.captureSelfie();
};
export const captureDocument = (
documentType: DocumentType
): Promise<UnicoResult> => {
return UnicoSdkModule.captureDocument(documentType);
};
3. Presentation Layer - Hooks Especializados
// useCapture.ts - Hook para capturas
export const useCapture = () => {
const [isLoading, setIsLoading] = useState(false);
const { requestPermissionOrRedirect } = usePermissions();
const captureSelfie = useCallback(async (): Promise<CapturedItem | null> => {
try {
const granted = await requestPermissionOrRedirect();
if (!granted) return null;
setIsLoading(true);
const newItem = await useCases.captureSelfie.execute();
Alert.alert('✅ Sucesso', 'Selfie capturada!');
return newItem;
} catch (error: unknown) {
Alert.alert('Erro', (error as Error).message);
return null;
} finally {
setIsLoading(false);
}
}, [requestPermissionOrRedirect, useCases.captureSelfie]);
return { isLoading, captureSelfie };
};
🎨 Funcionalidades Avançadas
1. Fluxo Guiado Inteligente
Implementei um sistema de fluxo guiado que orienta o usuário através dos passos necessários:
const DEFAULT_STEPS: FlowStep[] = [
{
id: 'selfie',
title: 'Capturar Selfie',
description: 'Primeiro, vamos tirar uma selfie sua',
icon: '🤳',
},
{
id: 'cnh_frente',
title: 'CNH Frente',
description: 'Agora capture a frente da sua CNH',
icon: '🪪',
},
{
id: 'cnh_verso',
title: 'CNH Verso',
description: 'Por último, capture o verso da CNH',
icon: '🔄',
},
{
id: 'submit',
title: 'Enviar',
description: 'Pronto! Vamos enviar tudo para verificação',
icon: '📤',
},
];
2. Sistema de Eventos Nativos
Para melhor UX, implementei um sistema de eventos que permite ao React Native reagir a ações do usuário no SDK nativo:
// JavaScript - Escutando eventos nativos
import { NativeEventEmitter } from 'react-native';
const eventEmitter = new NativeEventEmitter(UnicoSdk);
eventEmitter.addListener('onErrorAcessoBio', (event) => {
console.error('Erro do SDK:', event.code, event.description);
});
eventEmitter.addListener('onUserClosedCameraManually', () => {
console.log('Usuário fechou a câmera');
});
3. Personalização Visual Completa
Criei classes de tema que permitem personalizar completamente a interface do SDK:
// Android
class UnicoTheme : IAcessoBioTheme {
override fun getColorBackground(): Any = "#F1F0F8"
override fun getColorBoxMessage(): Any = "#FFFFFF"
override fun getColorTextMessage(): Any = "#322E50"
// ... 14 cores personalizáveis
}
// iOS
class UnicoTheme: AcessoBioThemeDelegate {
func getColorBackground() -> Any! {
return UIColor(red: 0.94, green: 0.94, blue: 0.97, alpha: 1.0)
}
// ... mesmas cores, implementação iOS
}
🚨 Principais Desafios e Soluções
1. Gerenciamento de Permissões
Desafio: Diferentes APIs de permissão em cada plataforma.
Solução: Abstração unificada com verificação prévia:
export const ensureCameraPermission = async (): Promise<boolean> => {
try {
const permissions = await checkPermissions();
return permissions.camera;
} catch (error) {
console.error('Erro ao verificar permissões:', error);
return false;
}
};
2. Thread Safety
Desafio: SDK requer UI thread, React Native executa em background thread.
Solução: Garantir execução na main thread:
// Android
private fun runOnMainThread(action: () -> Unit) {
if (Looper.myLooper() == Looper.getMainLooper()) {
action()
} else {
Handler(Looper.getMainLooper()).post(action)
}
}
// iOS
@objc
func captureSelfie(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
DispatchQueue.main.async {
// Operações de UI aqui
}
}
3. Configuração de Credenciais
Desafio: Credenciais precisam estar disponíveis em runtime sem hardcode.
Solução: Fluxo .env
→ react-native-config
→ plataforma nativa:
# .env
UNICO_BUNDLE_ID=com.empresa.app
UNICO_HOST_KEY=chave_secreta
4. Debugging Complexo
Desafio: Debuggar código que atravessa 3 camadas (JS → Bridge → SDK).
Solução: Logging detalhado em cada camada:
Log.d(TAG, "✅ AcessoBio.prepareCamera() chamado com sucesso")
Log.e(TAG, "❌ Exception geral em captureSelfie", e)
📊 Resultados Alcançados
Funcionalidades Implementadas:
- ✅ Captura de selfie com biometria facial
- ✅ Captura de documentos (CNH, RG, CPF) com frames guiados
- ✅ Fluxo guiado completo de verificação
- ✅ Gerenciamento de permissões nativo
- ✅ Personalização visual completa
- ✅ Eventos nativos para JavaScript
- ✅ Arquitetura escalável e testável
- ✅ Documentação completa com guias passo-a-passo
Métricas de Qualidade:
- 🎯 100% funcional em dispositivos reais
- 📱 Suporte completo iOS e Android
- 🧪 Arquitetura testável com separação de responsabilidades
- 📚 Documentação completa com troubleshooting
- 🔧 Configuração simplificada via arquivo
.env
🎓 Lições Aprendidas
1. Documentação é Crucial
A falta de documentação específica para React Native custou semanas de desenvolvimento. Por isso, criei uma documentação detalhada para evitar que outros passem pelo mesmo problema.
2. Arquitetura Limpa Compensa
Investir tempo em arquitetura limpa desde o início facilitou enormemente a manutenção e extensão do código.
3. Thread Safety é Crítico
Problemas de thread causaram crashes esporádicos. Garantir execução na main thread é fundamental.
4. Testes em Dispositivos Reais
Simuladores não são suficientes para testar funcionalidades de câmera e biometria.
5. Comunidade é Poderosa
Compartilhar conhecimento gera valor exponencial para toda a comunidade.
🎯 Conclusão
Desenvolver uma bridge nativa para o SDK da Unico foi um desafio técnico complexo, mas extremamente gratificante. O resultado é uma solução robusta, bem documentada e pronta para uso em produção.
O mais importante: esta implementação agora está disponível para toda a comunidade React Native, evitando que outros desenvolvedores enfrentem os mesmos obstáculos que encontrei.
Principais Benefícios para a Comunidade:
- ⏰ Economia de Tempo: Semanas de desenvolvimento economizadas
- 📚 Conhecimento Compartilhado: Documentação completa e detalhada
- 🏗️ Arquitetura Sólida: Base para implementações futuras
- 🔧 Troubleshooting: Soluções para problemas reais
- 🤝 Open Source: Disponível para todos
📞 Como Contribuir
Este projeto é open source e feito para a comunidade. Se você:
- ✨ Tem sugestões de melhorias
- 🐛 Encontrou bugs ou problemas
- 📝 Quer melhorar a documentação
- 🌍 Pode traduzir para outros idiomas
- 💡 Tem ideias para novas funcionalidades
Sua contribuição é muito bem-vinda!
Links Úteis:
- 📂 Repositório: GitHub - SDK Unico Bridge
- 📚 Documentação Completa: Guias detalhados para iOS e Android
- 🐛 Issues: Reporte problemas ou sugira melhorias
- 💬 Discussões: Participe da comunidade
Se este artigo te ajudou, considere:
- ⭐ Dar uma estrela no repositório
- 📤 Compartilhar com outros desenvolvedores
- 💬 Deixar feedback nos comentários
- 🤝 Contribuir com melhorias
Top comments (1)
Nice posting! Looking forward to collaborating with you