Pular para o conteúdo
← Voltar para o blog
Analytics React Native

Como validar eventos no Firebase DebugView em um APK release React Native

9 min de leitura

Escrito por: Jonathan Reis em

Um contador zerado no DebugView não prova que um APK release React Native está sem analytics. Este é o caminho de validação que separa estado local do Firebase e ingestão no backend do GA4.

Atualizado:

Imagem de prévia OpenGraph deste artigo. Como validar eventos no Firebase DebugView em um APK release React Native

Abra Páginas e telas no GA4 de um app React Native e encontre zero visualizações, com quase tudo em (not set). Não é bug do Google — na maioria dos casos o app simplesmente não manda screen_view.

Foi o que vimos no Daily Sudoku: Offline Puzzle depois dos testes internos de julho de 2026. O GA4 já recebia uns quatrocentos eventos em poucos dias. game_started, shop_viewed, home_play_pressed apareciam no topo. O relatório de navegação, não.

Eventos customizados contam o que aconteceu. Não dizem em qual tela o jogador estava quando apertou Play, abriu a Loja ou desistiu no meio da partida.

Na Fase 1 daquele mês estendemos @apps/analytics, ligamos screen_view ao React Navigation, sincronizamos user properties, configuramos o GA4 Admin e validamos num APK de produção. O ponto chato foi o device físico: DebugView vazio, logcat salvando o dia.

O que o GA4 já mostrava

O projeto Firebase sunstoneapps-daily-sudoku alimenta a propriedade GA4 sunstoneapps-daily-sudoku-app. A telemetria de gameplay estava viva:

  • shop_viewed, app_opened, game_started, home_play_pressed, difficulty_selected, game_finished, entre outros.

Isso serve para funis montados a partir de toques explícitos e hooks de ciclo de vida. Não substitui relatório por tela. O relatório Páginas e telas do GA4 espera screen_view (ou o rastreamento automático de telas que o Firebase pode fazer quando configurado). Sem isso, perguntas como “o usuário abre a Loja antes ou depois da primeira partida?” exigem inferência frágil pela ordem dos eventos.

Tínhamos parâmetros nos eventos (difficultyId, result, source) que só viram dimensões fáceis depois de registrados no GA4 Admin. Nomes de parâmetro no stream bruto não são a mesma coisa que dimensões prontas para relatório.

Objetivos da Fase 1

Escopo da Fase 1: visibilidade, não telemetria nova de gameplay:

  1. screen_view automático em cada mudança de rota do React Navigation.
  2. setUserProperty para um conjunto pequeno de campos de segmentação sem PII.
  3. Dimensões customizadas no GA4 para parâmetros de evento e user properties que já enviamos.
  4. Eventos principais marcados para as métricas que importam ao negócio.
  5. Validação num APK parecido com produção, não só em providers mock.

Funis de ads, abandono e hábito Daily ficam para depois. A Fase 1 só precisava tornar legível no GA4 o contrato de eventos que o app já mandava. (Configurar mensagens de privacidade do AdMob UMP foi uma preocupação separada que veio depois.)

Estendendo o @apps/analytics

O monorepo já roteava eventos de produto pelo @apps/analytics, com provider Firebase no nativo e no-op na web. Adicionamos dois métodos na superfície pública:

  • logScreenView({ screenName, screenClass? })
  • setUserProperty({ name, value })

Ambos respeitam o mesmo gate analytics.enabled do trackEvent. No nativo chamam @react-native-firebase/analytics. Na web fazem noop para SSR e builds Expo web não quebrarem.

O runtime Firebase fica em firebaseRuntime.ts para Jest e stubs web continuarem finos. Testes unitários mockam o provider e verificam o dispatch com analytics habilitado.

Regra que mantivemos: não logar PII. User properties são estado de produto (theme_mode, locale, max_mistakes_setting), não e-mail nem identificadores de conta.

Ligando o React Navigation

O Sudoku usa um NavigationContainer em AppNavigator.tsx. O padrão:

  1. Manter ref do container de navegação.
  2. Em onReady e onStateChange, ler o nome da rota ativa.
  3. Se o nome mudou desde a última emissão, chamar logScreenView.

O helper getActiveRouteName percorre navegadores aninhados para a rota folha (Game, Shop, WinFlow, etc.). O hook useAnalyticsNavigationTracking deduplica a rota anterior para não spammar analytics em eventos de estado repetidos.

Telas mapeadas no MVP: Home, Profile, Favorites, Shop, Settings, PreGameAd, Game, WinFlow, WinStats. screen_class espelha o nome da rota, alinhado ao builtin firebase_screen_class quando os relatórios hidratarem.

SyncAnalyticsUserProperties é um componente pequeno perto da raiz do app. Observa versão, locale, tema, limite de erros, tutorial concluído e entitlement sem anúncios, chamando setUserProperty quando os valores mudam. Tutorial concluído atualiza ao fim do welcome, não só no cold start.

O que entrou no GA4 Admin

Com o código pronto, configuramos a propriedade (passos manuais no Admin; parte da UI usa overlays e comboboxes difíceis de automatizar):

Dimensões de evento (8): difficultyId, result, source (rótulo “Game start source”), productId, step, stepId, scoreBucket, hasActiveGame.

Dimensões de usuário (6): app_version, locale, theme_mode, max_mistakes_setting, tutorial_completed, has_noads_entitlement.

Eventos principais: game_finished, daily_complete, shop_purchase_completed (além de first_open padrão do Firebase).

Resumo dos relatórios: hub com usuários ativos, contagem de eventos, retenção e breakdown. Páginas e telas só popula depois que devices rodam build com screen_view — espere 24–48 h de atraso após o release.

Pulamos evento derivado game_won no Admin. Filtrar game_finished com result = win basta; a UI “sem código” do GA4 assume acionador por nome de tela e é fácil errar.

Follow-up manual opcional: salvar exploração de funil app_opened → home_play_pressed → game_started → game_finished pelo modelo Exploração de funil na Biblioteca.

Validando num APK de produção

Build debug é péssimo substituto do comportamento de loja. Usamos:

pnpm sudoku:apk:prd
adb shell setprop debug.firebase.analytics.app com.sunstoneapps.dailysudoku

Instalar o APK release, cold start, navegar Home → Loja → Jogo.

Quando o contador do DebugView mostra zero

A interface do DebugView pode induzir uma conclusão errada. Numa validação release, o contador visível continuava em zero dispositivos de depuração enquanto um Redmi Note 7 rodava bundle de produção com debug.firebase.analytics.app configurado. Era tentador atribuir o problema ao aparelho ou ao fabricante. Não era uma conclusão sustentada pelos dados.

O logcat continua útil para confirmar o estado local do Firebase:

adb logcat -s FA-SVC | rg "screen_view|Setting user property|Faster debug"

Ele confirmou o modo rápido de debug, o bundle de produção e chamadas locais de screen_view. Não prova, sozinho, que um evento chegou à propriedade GA4 esperada.

A confirmação decisiva veio da resposta realtime carregada pela própria página do DebugView. Com DevTools aberto, recarregue a página e inspecione a requisição bem-sucedida de dados em tempo real. O payload continha eventos customizados com debug_event = 1, parâmetros e ambiente de produção:

app_bootstrap_completed
environment: production
debug_event: 1

shop_opened
debug_event: 1

O nome do endpoint e o formato da resposta são detalhes internos do GA4. Não automatize contra uma URL fixa. O procedimento durável é inspecionar a resposta realtime gerada pela página já aberta. Se ela contém o evento e os parâmetros, o backend recebeu os dados mesmo quando o contador ou seletor renderizam o aparelho errado.

Primeiro confirme o bundle release e o modo Firebase local. Depois confira a resposta do DebugView. Só quando ambos falharem vale investigar rede, identidade do projeto Firebase, consentimento ou Google Play services.

Nomeando eventos de produto sem esconder a ação em parâmetros

Mantivemos screen_view, evento padrão do Firebase: Home, Shop e Game são dimensões de tela, não ações distintas. Nomes como screen_view_home fragmentariam o relatório padrão Páginas e telas.

Para ações de domínio, substituímos nomes amplos e o envelope agregado ad_event por nomes que mostram entidade e transição de negócio:

  • app_bootstrap_completed, home_play_pressed e game_difficulty_selected.
  • daily_challenge_opened, daily_challenge_completed, daily_streak_increased e daily_streak_reset.
  • shop_opened e shop_purchases_restored.
  • ad_banner_requested, ad_pre_game_blocked e ad_rewarded_completed.

As propriedades continuam distinguindo variantes. game_abandoned carrega gameType (daily ou standard) e source (back_to_home); eventos de ads levam placementId, provider, elegibilidade e shown quando o adapter conhece o resultado. O nome identifica a transição; as propriedades explicam o contexto.

Checklist operacional no repositório

O checklist vivo está em docs/app-sudoku/SUDOKU_ANALYTICS_OBSERVABILIDADE_CHECKLIST.md. Fases:

  • Fase 1: visibilidade de jornada (screen_view, dimensões, eventos principais) — em grande parte concluída.
  • Fase 2: instrumentação de ads e abandono em @apps/ads e bridges de gameplay.
  • Fase 3: hábito Daily e eventos de settings.
  • Fase 4: export BigQuery quando volume real justificar SQL.

O contrato tipado de eventos fica em apps/sudoku/src/app/analytics/events.ts e na seção Telemetria do SUDOKU_PLANO.md. Ao adicionar eventos, atualize tipos, docs e dimensões GA4 na mesma mudança.

Três erros que quase nos custaram tempo

Registrar dimensão no Admin só quando o relatório já está vazio atrasa uma semana. Parâmetro no stream não vira filtro sozinho — agora criamos dimensão de evento e de usuário no mesmo PR do código.

Tratar o contador do DebugView como a ferramenta inteira foi um erro. Logcat provou a configuração local; a resposta realtime provou a ingestão no backend. Um não substitui o outro.

screen_view vale mais que o décimo evento customizado. O GA4 ficou legível antes de qualquer telemetria nova de gameplay que ainda não segmentávamos.

Analytics continua fora do devModeEnabled e fora do caminho crítico do jogo — fire-and-forget, warning em falha, zero modal para o jogador.

Depois disso

Agora a Fase 2 cobre decisões e resultados de ads que o adapter realmente observa. Callbacks nativos de load, impressão e fechamento continuam como trabalho separado, porque o adapter ainda não os expõe de forma confiável.

Se Páginas e telas está vazio e você já tem Firebase no React Native, comece por screen_view e umas poucas user properties. O resto espera.