Analytics & Privacidade
Segment e Amplitude com portões de consentimento e alinhamento ATT no Expo SDK 57. As análises de produto pertencem ao programa de observabilidade, mas seguem regras de privacidade que os reportadores de crash não seguem.
Busque em todas as páginas da documentação
Segment e Amplitude com portões de consentimento e alinhamento ATT no Expo SDK 57. As análises de produto pertencem ao programa de observabilidade, mas seguem regras de privacidade que os reportadores de crash não seguem.
npm install @segment/analytics-react-native @segment/sovran-react-native
# ou Amplitude diretamente:
npm install @amplitude/analytics-react-native
npx expo install expo-tracking-transparencyFerramentas: Estes exemplos visam Expo SDK 57 (
expo~57.0.4), React Native 0.86.0 e React 19.2.3.
Cartão de receita de referência rápida - pronto para copiar e colar.
1. Estado de consentimento
// src/privacy/consent.ts
import AsyncStorage from "@react-native-async-storage/async-storage";
export type ConsentState = {
analytics: boolean;
crashReporting: boolean; // frequentemente separado - pode ser ativado por padrão para estabilidade
marketing: boolean;
decidedAt: string | null;
};
const KEY = "consent_v1";
const defaultState: ConsentState = {
analytics: false,
crashReporting: true,
marketing: false,
decidedAt: null,
};
export async function getConsent(): Promise<ConsentState> {
const raw = await AsyncStorage.getItem(KEY);
return raw ? JSON.parse(raw) : defaultState;
}
export async function setConsent(patch: Partial<ConsentState>) {
const next = { ...(await getConsent()), ...patch, decidedAt: new Date().toISOString() };
await AsyncStorage.setItem(KEY, JSON.stringify(next));
return next;
}2. Prompt ATT (iOS)
// src/privacy/att.ts
import { Platform } from "react-native";
import * as TrackingTransparency from "expo-tracking-transparency";
export async function requestAttIfNeeded() {
if (Platform.OS !== "ios") return "unavailable";
const { status: existing } = await TrackingTransparency.getTrackingPermissionsAsync();
if (existing === "granted" || existing === "denied") return existing;
const { status } = await TrackingTransparency.requestTrackingPermissionsAsync();
return status;
}3. Fachada de analytics com portão
// src/analytics/index.ts
import { getConsent } from "@/privacy/consent";
type Props = Record<string, string | number | boolean | null>;
let segmentClient: { track: (e: string, p?: Props) => void; identify: (id: string, t?: Props) => void; reset: () => void } | null = null;
export async function initAnalytics() {
const consent = await getConsent();
if (!consent.analytics) return;
const { createClient } = await import("@segment/analytics-react-native");
segmentClient = createClient({
writeKey: process.env.EXPO_PUBLIC_SEGMENT_WRITE_KEY!,
trackAppLifecycleEvents: true,
});
}
export async function track(event: string, properties?: Props) {
const consent = await getConsent();
if (!consent.analytics || !segmentClient) return;
segmentClient.track(event, sanitize(properties));
}
export async function identify(userId: string, traits?: Props) {
const consent = await getConsent();
if (!consent.analytics || !segmentClient) return;
segmentClient.identify(userId, sanitize(traits));
}
export async function resetAnalytics() {
segmentClient?.reset();
}
function sanitize(props?: Props): Props | undefined {
if (!props) return props;
const blocked = ["email", "phone", "name", "address"];
return Object.fromEntries(
Object.entries(props).filter(([k]) => !blocked.includes(k.toLowerCase()))
);
}4. Portão de UI de consentimento no layout raiz
// src/privacy/ConsentGate.tsx
import { useEffect, useState } from "react";
import { Button, Text, View } from "react-native";
import { getConsent, setConsent } from "./consent";
import { requestAttIfNeeded } from "./att";
import { initAnalytics } from "@/analytics";
export function ConsentGate({ children }: { children: React.ReactNode }) {
const [ready, setReady] = useState(false);
const [showBanner, setShowBanner] = useState(false);
useEffect(() => {
(async () => {
const consent = await getConsent();
setShowBanner(!consent.decidedAt);
if (consent.analytics) await initAnalytics();
setReady(true);
})();
}, []);
if (!ready) return null;
if (showBanner) {
return (
<View style={{ padding: 24, gap: 12 }}>
<Text>Usamos analytics para melhorar o aplicativo. Você pode alterar isso nas Configurações.</Text>
<Button
title="Aceitar analytics"
onPress={async () => {
await requestAttIfNeeded();
await setConsent({ analytics: true });
await initAnalytics();
setShowBanner(false);
}}
/>
<Button
title="Recusar"
onPress={async () => {
await setConsent({ analytics: false });
setShowBanner(false);
}}
/>
</View>
);
}
return <>{children}</>;
}Quando usar isso:
Segment → Amplitude com fontes específicas do ambiente e reset no logout.
// app.config.ts - declarar NSUserTrackingUsageDescription para iOS
export default {
expo: {
ios: {
infoPlist: {
NSUserTrackingUsageDescription:
"Este identificador nos ajuda a medir o desempenho do aplicativo e melhorar recursos.",
},
},
},
};// app/_layout.tsx
import { Stack } from "expo-router";
import { ConsentGate } from "@/privacy/ConsentGate";
export default function RootLayout() {
return (
<ConsentGate>
<Stack />
</ConsentGate>
);
}// src/features/auth/logout.ts
import { resetAnalytics } from "@/analytics";
import * as SecureStore from "expo-secure-store";
export async function logout() {
await SecureStore.deleteItemAsync("refresh_token");
await resetAnalytics();
// limpar estado do React, invalidar queries...
}// Esquema de evento estável - versionar propriedades cuidadosamente
await track("checkout_completed", {
order_id: order.id,
item_count: order.items.length,
currency: "USD",
value_cents: order.totalCents,
});| Abordagem | Prós | Contras |
|---|---|---|
| Roteador Segment | Um SDK, muitos destinos, filtragem do lado do servidor | Custo extra, outro fornecedor |
| Amplitude direto | UI rica de analytics de produto, experimentação | Cada nova ferramenta precisa de outro SDK |
| Ambos | Segment → Amplitude comum em empresas | Duas configurações para manter sincronizadas |
// Amplitude direto (quando Segment não é necessário)
import { init, track, identify, reset } from "@amplitude/analytics-react-native";
export async function initAmplitude() {
await init(process.env.EXPO_PUBLIC_AMPLITUDE_API_KEY!, undefined, {
trackingOptions: { ipAddress: false },
});
}account_id B2B - não para PIIrelease e otaUpdateId como super-propriedades para correlação de release - Noções Básicas de OTA UpdatesRelacionado: Noções Básicas de Observabilidade - não confundir
track()comcaptureException()
checkout_completed, não completeCheckoutschema_version se necessáriouser_id opaco da autenticação, nunca emailscreen_view duplicado tanto do roteador quanto do useEffect da telacold_start_ms) pertencem aqui ou em Monitoramento de Desempenho - escolher um proprietáriodecidedAt seja definido.Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026