Tarefas em Segundo Plano no React Native com expo-background-task

Aprenda a implementar tarefas em segundo plano no React Native usando expo-background-task do Expo SDK 53. Guia prático com exemplos de código, configuração por plataforma e soluções para erros comuns.

Por que Tarefas em Segundo Plano São Essenciais em Apps Modernos

Vamos ser sinceros: em 2026, ninguém tem paciência pra abrir um app e esperar os dados carregarem do zero. Os usuários esperam que tudo esteja atualizado, sincronizado e pronto pra uso. E sabe o que torna isso possível? Tarefas em segundo plano — basicamente, pedaços de código que rodam enquanto o app não está na tela, sem nenhuma interface visível.

O Expo SDK 53 trouxe o expo-background-task, uma API moderna que veio pra substituir o antigo expo-background-fetch. Ela usa as APIs nativas mais recentes de cada plataforma: o WorkManager no Android e o BGTaskScheduler no iOS.

Neste guia, você vai aprender a implementar tarefas em segundo plano de forma confiável, respeitando as restrições (cada vez mais rigorosas) dos sistemas operacionais atuais.

O que é o expo-background-task

O expo-background-task é um módulo do Expo que fornece uma API pra executar tarefas diferíveis em segundo plano, otimizando o consumo de bateria. Diferente das abordagens antigas que dependiam de APIs depreciadas, esse módulo foi construído sobre fundações estáveis e modernas — o que, na prática, significa menos dor de cabeça com atualizações do sistema operacional.

Principais Características

  • WorkManager (Android): Permite especificar um intervalo mínimo de execução com garantia de entrega, mesmo após reinicializações do dispositivo
  • BGTaskScheduler (iOS): Usa o sistema inteligente de agendamento da Apple, que considera bateria, rede disponível e padrões de uso do usuário
  • Arquitetura de worker único: Múltiplas tarefas JavaScript rodam sequencialmente por um único worker nativo, respeitando os limites das plataformas
  • Conectividade de rede garantida: As tarefas são configuradas pra exigir conexão de rede antes da execução
  • Compatibilidade com CNG: Configurações nativas são aplicadas automaticamente via prebuild

Migração do expo-background-fetch

Se você já usa o expo-background-fetch, pode ficar tranquilo — a migração é bem simples. A API do expo-background-task é quase idêntica, com ajustes mínimos nos parâmetros de registerTaskAsync.

Um detalhe importante: o expo-background-fetch foi oficialmente depreciado e não vai receber mais atualizações a partir do Expo SDK 54. Então vale migrar logo.

As principais diferenças são:

  • Substituir importações de expo-background-fetch por expo-background-task
  • Usar BackgroundTaskResult ao invés de BackgroundFetchResult nos retornos das tarefas
  • O parâmetro minimumInterval agora é especificado em minutos (antes era em segundos — cuidado pra não confundir!)
  • Remover opções específicas do Android como stopOnTerminate e startOnBoot, que não são mais necessárias

Instalação e Configuração

Instalando os pacotes necessários

O expo-background-task trabalha em conjunto com o expo-task-manager pra definir e gerenciar tarefas. Instale os dois:

npx expo install expo-background-task expo-task-manager

Configuração no iOS (CNG/Prebuild)

Se você usa Continuous Native Generation (CNG) com npx expo prebuild, as configurações no Info.plist são aplicadas automaticamente. Nenhuma configuração manual necessária — só alegria.

Configuração Manual no iOS

Pra projetos que não usam CNG, você vai precisar adicionar manualmente as seguintes entradas ao Info.plist:

<key>UIBackgroundModes</key>
<array>
  <string>processing</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>com.expo.modules.backgroundtask.processing</string>
</array>

Essas entradas habilitam a capacidade de processamento em segundo plano e registram o identificador de tarefa permitido pelo sistema.

Configuração no Android

No Android, nenhuma configuração extra é necessária. O módulo cuida de tudo usando o WorkManager por baixo dos panos.

Implementação Passo a Passo

Passo 1: Definir a Tarefa no Escopo Global

Esse é um ponto que pega muita gente de surpresa: a definição da tarefa precisa estar no escopo global do seu bundle JavaScript — fora de qualquer componente React. Por quê? Porque quando o sistema inicia seu app em segundo plano, o JavaScript é carregado, a tarefa roda e o processo morre. Nenhuma view é montada.

import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';

const TAREFA_SYNC = 'tarefa-sincronizacao-dados';

// Definição DEVE estar no escopo global
TaskManager.defineTask(TAREFA_SYNC, async () => {
  try {
    const agora = Date.now();
    console.log(
      `Tarefa em segundo plano executada em: ${new Date(agora).toISOString()}`
    );

    // Sua lógica de sincronização aqui
    const response = await fetch('https://api.seuservidor.com/sync', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ultimaSync: agora }),
    });

    if (!response.ok) {
      return BackgroundTask.BackgroundTaskResult.Failed;
    }

    return BackgroundTask.BackgroundTaskResult.Success;
  } catch (error) {
    console.error('Erro na tarefa em segundo plano:', error);
    return BackgroundTask.BackgroundTaskResult.Failed;
  }
});

Passo 2: Registrar a Tarefa

Com a tarefa definida, agora você precisa registrá-la pra que o sistema saiba quando executá-la. Essa parte pode ficar dentro de um componente React normalmente:

async function registrarTarefaBackground() {
  return BackgroundTask.registerTaskAsync(TAREFA_SYNC, {
    minimumInterval: 15, // intervalo mínimo em minutos
  });
}

async function cancelarTarefaBackground() {
  return BackgroundTask.unregisterTaskAsync(TAREFA_SYNC);
}

O minimumInterval define o intervalo mínimo entre execuções. O valor mínimo aceito é 15 minutos, e o padrão é 12 horas. Mas aqui vai um ponto crucial: esse é um limite inferior. O sistema pode decidir executar sua tarefa bem depois, dependendo das condições do dispositivo.

Passo 3: Criar o Componente de Controle

Aqui vai um exemplo completo de componente que permite ao usuário ativar e desativar a tarefa em segundo plano. Eu particularmente gosto de ter esse tipo de controle visível no app — dá mais transparência pro usuário:

import { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';

const TAREFA_SYNC = 'tarefa-sincronizacao-dados';

export default function TelaBackgroundTask() {
  const [registrada, setRegistrada] = useState(false);
  const [status, setStatus] = useState(null);

  useEffect(() => {
    atualizarStatus();
  }, []);

  const atualizarStatus = async () => {
    const statusAtual = await BackgroundTask.getStatusAsync();
    setStatus(statusAtual);

    const estaRegistrada = await TaskManager.isTaskRegisteredAsync(TAREFA_SYNC);
    setRegistrada(estaRegistrada);
  };

  const alternarTarefa = async () => {
    if (!registrada) {
      await registrarTarefaBackground();
      Alert.alert('Sucesso', 'Tarefa em segundo plano ativada!');
    } else {
      await cancelarTarefaBackground();
      Alert.alert('Sucesso', 'Tarefa em segundo plano desativada.');
    }
    await atualizarStatus();
  };

  return (
    <View style={styles.container}>
      <Text style={styles.titulo}>Tarefas em Segundo Plano</Text>
      <Text style={styles.info}>
        Status: {status === BackgroundTask.BackgroundTaskStatus.Available
          ? 'Disponível'
          : 'Restrito'}
      </Text>
      <Text style={styles.info}>
        Tarefa: {registrada ? 'Ativa' : 'Inativa'}
      </Text>
      <Button
        disabled={status === BackgroundTask.BackgroundTaskStatus.Restricted}
        title={registrada ? 'Desativar Tarefa' : 'Ativar Tarefa'}
        onPress={alternarTarefa}
      />
      <Button
        title='Verificar Status'
        onPress={atualizarStatus}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
  titulo: { fontSize: 22, fontWeight: 'bold', marginBottom: 20 },
  info: { fontSize: 16, marginBottom: 10 },
});

Referência Completa da API

Pra quem gosta de ter tudo documentado (eu sou dessa turma), aqui vai a referência completa do módulo expo-background-task.

Funções

  • getStatusAsync() — Retorna o status de disponibilidade da API. Retorna BackgroundTaskStatus.Available ou BackgroundTaskStatus.Restricted
  • registerTaskAsync(taskName, options?) — Registra uma tarefa em segundo plano. A tarefa precisa ter sido previamente definida com TaskManager.defineTask
  • unregisterTaskAsync(taskName) — Cancela o registro de uma tarefa, impedindo futuras execuções
  • triggerTaskWorkerForTestingAsync() — Dispara manualmente todas as tarefas registradas pra fins de teste. Funciona apenas no modo de desenvolvimento

Enums

  • BackgroundTaskResult.Success (valor: 1) — Tarefa concluída com sucesso
  • BackgroundTaskResult.Failed (valor: 2) — A tarefa encontrou um erro
  • BackgroundTaskStatus.Restricted (valor: 1) — Tarefas em segundo plano indisponíveis
  • BackgroundTaskStatus.Available (valor: 2) — Tarefas em segundo plano disponíveis

Event Listener (apenas iOS)

O addExpirationListener(listener) permite registrar um callback chamado quando o sistema decide encerrar as tarefas em segundo plano. Use pra liberar recursos ou salvar o estado atual. O task runner é reagendado automaticamente após a expiração.

import * as BackgroundTask from 'expo-background-task';

const subscription = BackgroundTask.addExpirationListener(() => {
  console.log('Sistema solicitou encerramento da tarefa. Salvando estado...');
  // Lógica de cleanup aqui
});

// Para remover o listener depois:
subscription.remove();

Comportamento por Plataforma

Aqui é onde as coisas ficam interessantes — e um pouco frustrantes, honestamente. Android e iOS têm comportamentos bem diferentes quando o assunto é tarefas em segundo plano.

Android (WorkManager)

No Android, o WorkManager é mais previsível e "amigável" pro desenvolvedor:

  • Intervalo mínimo de 15 minutos entre execuções
  • Tarefas sobrevivem a reinicializações do dispositivo
  • Funciona tanto em emuladores quanto em dispositivos físicos
  • A tarefa roda quando o intervalo mínimo é atingido e as condições de rede são satisfeitas

iOS (BGTaskScheduler)

No iOS, o sistema tem muito mais controle sobre quando suas tarefas rodam:

  • O sistema decide o melhor momento com base em bateria, rede e hábitos do usuário
  • Tarefas podem ser adiadas significativamente — às vezes pro período noturno
  • A API de Background Tasks não funciona em simuladores iOS — só em dispositivos físicos
  • Se o usuário desativar "Atualização em segundo plano" nas configurações, nada vai rodar
  • Tarefas podem ser interrompidas a qualquer momento — use o addExpirationListener pra lidar com isso

Testando Tarefas em Segundo Plano

Testar tarefas em segundo plano é, pra ser bem honesto, um dos maiores desafios desse tipo de implementação. Você depende do agendamento do sistema operacional, que pode levar horas. Felizmente, o expo-background-task oferece uma saída pra isso.

Usando triggerTaskWorkerForTestingAsync

A ideia é simples: adicione um botão de debug que dispara as tarefas manualmente. Eu sempre faço isso nos meus projetos e recomendo fortemente:

import * as BackgroundTask from 'expo-background-task';
import { Button } from 'react-native';

function BotaoDebug() {
  const dispararTarefa = async () => {
    try {
      const resultado = await BackgroundTask.triggerTaskWorkerForTestingAsync();
      console.log('Tarefa disparada com sucesso:', resultado);
    } catch (error) {
      console.error('Erro ao disparar tarefa:', error);
    }
  };

  // Exibir apenas em modo de desenvolvimento
  if (!__DEV__) return null;

  return (
    <Button
      title='[DEV] Disparar Tarefa em Background'
      onPress={dispararTarefa}
    />
  );
}

Depuração no Android

No Android, dá pra usar o adb pra inspecionar tarefas do WorkManager:

adb shell dumpsys jobscheduler | grep -i expo

Depuração no iOS

No iOS, infelizmente não existe ferramenta similar ao adb. Use o triggerTaskWorkerForTestingAsync mesmo. Se aparecer este erro no console do Xcode:

No task request with identifier com.expo.modules.backgroundtask.processing has been scheduled

Significa que você precisa rodar npx expo prebuild pra aplicar as configurações de segundo plano ao projeto nativo.

Casos de Uso Práticos

Sincronização de Dados Offline

Esse é provavelmente o caso de uso mais comum — e com razão. Sincronizar dados armazenados localmente com o servidor quando o app tá em segundo plano é extremamente útil pra apps que precisam funcionar offline:

import AsyncStorage from '@react-native-async-storage/async-storage';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';

const TAREFA_SYNC_OFFLINE = 'sync-dados-offline';

TaskManager.defineTask(TAREFA_SYNC_OFFLINE, async () => {
  try {
    // Buscar dados pendentes no armazenamento local
    const dadosPendentes = await AsyncStorage.getItem('pendingSync');

    if (!dadosPendentes) {
      return BackgroundTask.BackgroundTaskResult.Success;
    }

    const itens = JSON.parse(dadosPendentes);

    // Enviar dados ao servidor
    const response = await fetch('https://api.seuservidor.com/sync', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ itens }),
    });

    if (response.ok) {
      // Limpar dados pendentes após sincronização
      await AsyncStorage.removeItem('pendingSync');
      return BackgroundTask.BackgroundTaskResult.Success;
    }

    return BackgroundTask.BackgroundTaskResult.Failed;
  } catch (error) {
    console.error('Erro na sincronização:', error);
    return BackgroundTask.BackgroundTaskResult.Failed;
  }
});

Verificação de Atualizações com expo-updates

Outro caso de uso bem interessante: verificar se existem atualizações OTA disponíveis pro app enquanto ele tá em segundo plano. Quando o usuário abrir o app da próxima vez, a versão mais recente já vai estar lá:

import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
import * as Updates from 'expo-updates';

const TAREFA_VERIFICAR_UPDATES = 'verificar-atualizacoes';

TaskManager.defineTask(TAREFA_VERIFICAR_UPDATES, async () => {
  try {
    const update = await Updates.checkForUpdateAsync();

    if (update.isAvailable) {
      await Updates.fetchUpdateAsync();
      // A atualização será aplicada na próxima abertura do app
    }

    return BackgroundTask.BackgroundTaskResult.Success;
  } catch (error) {
    console.error('Erro ao verificar atualizações:', error);
    return BackgroundTask.BackgroundTaskResult.Failed;
  }
});

Boas Práticas e Otimizações para 2026

O iOS 18+ e o Android 15 trouxeram lógicas bem agressivas de otimização de bateria. Se você não tomar cuidado, o sistema pode marcar seu app como "consumidor excessivo de energia" e restringir as tarefas permanentemente. Já vi isso acontecer em produção e não é nada divertido.

Mantenha as Tarefas Leves

  • Faça apenas o trabalho mínimo necessário em cada execução
  • Use endpoints de API que retornem apenas deltas (mudanças incrementais)
  • Evite processamento pesado como manipulação de imagens ou cálculos intensivos
  • Termine a tarefa o mais rápido possível — quanto mais curta, melhor

Respeite os Limites do Sistema

  • Não tente contornar as restrições do SO — o sistema vai perceber e punir seu app
  • Defina intervalos razoáveis — nem sempre 15 minutos é necessário pra sua use case
  • Teste em dispositivos reais com bateria limitada
  • No iOS, considere que as tarefas podem rodar apenas uma ou duas vezes por dia

Gerencie Erros Adequadamente

  • Sempre retorne BackgroundTaskResult.Success ou BackgroundTaskResult.Failed
  • Use blocos try/catch — exceções não tratadas em tarefas de background podem causar comportamento imprevisível
  • Implemente retry com backoff exponencial pra falhas de rede
  • No iOS, use o addExpirationListener pra salvar progresso parcial quando o sistema encerrar a tarefa

Erros Comuns e Soluções

Tarefa não é executada

Problema: A tarefa foi registrada mas nunca roda.

Soluções:

  • Verifique se o defineTask está no escopo global (fora de componentes)
  • No iOS, execute npx expo prebuild pra aplicar as configurações no Info.plist
  • Confirme que o dispositivo tem conexão de rede ativa
  • No iOS, verifique se "Atualização em segundo plano" está habilitada em Ajustes > Geral

Erro "No task request with identifier"

Problema: O Xcode mostra esse erro ao disparar a tarefa.

Solução: Execute npx expo prebuild --clean pra regenerar os arquivos nativos com as configurações corretas. Isso resolve na grande maioria dos casos.

Tarefa funciona no Android mas não no iOS

Problema: Tudo funciona perfeitamente no Android mas a tarefa nunca é acionada no iOS.

Solução: Não entre em pânico — provavelmente você está testando no simulador. A API de Background Tasks do iOS simplesmente não funciona em simuladores. Teste em um dispositivo físico e use triggerTaskWorkerForTestingAsync pra validar a lógica.

Perguntas Frequentes (FAQ)

Qual a diferença entre expo-background-task e expo-background-fetch?

O expo-background-task é o substituto moderno do expo-background-fetch. Usa APIs nativas mais recentes — WorkManager no Android e BGTaskScheduler no iOS — oferecendo mais confiabilidade e melhor gerenciamento de energia. O expo-background-fetch foi oficialmente depreciado e não vai receber atualizações a partir do Expo SDK 54.

Posso executar tarefas quando o app está completamente fechado?

Depende da plataforma. No iOS, se o usuário forçar o encerramento (swipe up), as tarefas param e não voltam até o app ser aberto novamente. No Android, as tarefas gerenciadas pelo WorkManager podem sobreviver ao encerramento do app e até reinicializações do dispositivo (dependendo das configurações de economia de bateria do fabricante).

Qual é o intervalo mínimo entre execuções?

O intervalo mínimo configurável é de 15 minutos. No Android, o WorkManager respeita esse intervalo de forma mais previsível. No iOS, o BGTaskScheduler trata isso como uma sugestão e pode agendar a tarefa pra bem depois — às vezes executando só durante a madrugada quando o dispositivo tá carregando.

Preciso de um development build pra testar?

No iOS, sim — tarefas em segundo plano não funcionam no simulador e exigem um dispositivo físico com development build. No Android, dá pra testar em emuladores tranquilamente. Em ambas as plataformas, o triggerTaskWorkerForTestingAsync permite disparar tarefas manualmente durante o desenvolvimento sem esperar pelo agendamento do sistema.

Como o SO decide quando executar minha tarefa?

O sistema considera vários fatores: nível de bateria, disponibilidade de rede, padrões de uso do app, carga do sistema e se o dispositivo está na tomada. No iOS, a Apple usa machine learning pra prever quando o usuário vai abrir o app e executa tarefas estrategicamente antes desse momento. No Android, o WorkManager usa um sistema de constraints mais direto e previsível.

Sobre o Autor Editorial Team

Our team of expert writers and editors.