Deferred Navigation — o que é, princípios e implementação

Autor: IT Sectr Publicado: 2026-06-10 Tempo de leitura: 9 min

Deferred Navigation é um padrão de navegação adiada onde a transição para a próxima tela ocorre após a conclusão de uma operação assíncrona, em vez de diretamente no momento da ação do usuário. De acordo com Android Developers (2024), a navegação adiada ajuda a evitar condições de corrida entre navegação e carregamento de dados, e simplifica o tratamento de transições de notificações push e Deeplinks. A diferença chave é que a rota é calculada depois que todos os dados necessários estão disponíveis.

Pontos Principais

  • Deferred Navigation é navegação adiada onde a transição ocorre após operações assíncronas
  • Navegação direta trata a transição instantaneamente, a adiada espera pelos dados
  • Cenários típicos: autenticação, Deeplink, notificações push, carregamento de configuração
  • No Android é implementada via Navigation Component com callbacks e StateFlow
  • No iOS usa-se Combine ou async/await com um coordenador de navegação

O que é Deferred Navigation

Deferred Navigation é um padrão arquitetural onde a decisão de navegação é adiada até que todos os dados necessários estejam disponíveis. Ao contrário de uma transição direta onde o usuário pressiona um botão e imediatamente chega a uma nova tela, a navegação adiada separa o evento de gatilho e a transição real colocando uma operação assíncrona entre eles.

Arquiteturalmente, Deferred Navigation é construída sobre mudança de estado: o pressionar de um botão inicia um processo assíncrono, e uma assinatura do seu resultado desencadeia a navegação. Isto é especialmente importante em aplicações com arquitetura MVVM ou MVI, onde a ViewModel gerencia o estado e a View (Activity, Fragment, SwiftUI View) assina as mudanças e reage com uma transição. Esta abordagem elimina a dependência direta entre a UI e a lógica de navegação.

De acordo com o Google I/O 2023, a navegação adiada é recomendada para todos os cenários onde a navegação depende do resultado de uma requisição de rede, verificação de autenticação, carregamento de configuração ou permissões. O padrão também é obrigatório ao lidar com Deeplinks, onde a aplicação deve primeiro iniciar, carregar a tela raiz e só então navegar para a rota de destino.

Quando usar navegação adiada

Deferred Navigation é usada em cenários onde a navegação direta leva a um estado de tela incorreto ou erros de carregamento. Vamos examinar quatro casos principais onde a navegação adiada é necessária.

Autorização e autenticação

Se um usuário toca em conteúdo protegido, a aplicação deve primeiro verificar o token de acesso. A navegação direta para a tela de conteúdo resultará em uma tela vazia ou erro 401 se o token expirou. A navegação adiada verifica o token, e apenas em caso de sucesso — navega para a tela de destino. Em caso de falha — redireciona para a tela de login.

Manipulação de Deeplinks

Quando uma aplicação é aberta através de um link externo, primeiro deve carregar a tela raiz, restaurar o estado de navegação e só então realizar a transição Deeplink. A navegação direta para a tela de destino sem contexto raiz levará a anomalias: uma pilha de navegação vazia ou uma pilha de retorno quebrada.

Notificações push com conteúdo

Ao tocar em uma notificação push, a aplicação pode estar em um de três estados: fechada, em segundo plano ou ativa. Deferred Navigation determina o estado da aplicação, carrega o conteúdo necessário e só então exibe a tela de destino. O iOS permite lidar com este cenário através de UNNotificationContentExtension.

Feature flag dinâmico

Se a funcionalidade de uma tela é controlada por um feature flag do servidor, a navegação adiada permite primeiro solicitar a configuração e só então mostrar a tela. Se a funcionalidade estiver desabilitada — o usuário vê conteúdo alternativo ou um placeholder em vez de uma tela vazia.

CenárioNavegação diretaDeferred Navigation
AutorizaçãoTela vazia com token expiradoRedirecionamento para login
DeeplinkPilha de retorno quebradaPilha de navegação correta
PushCarregamento sem contextoDados prontos antes da transição
Feature flagExibir funcionalidade indisponívelPlaceholder ou alternativa

Deferred Navigation vs navegação direta

A navegação direta é uma abordagem tradicional onde a transição é realizada imediatamente em resposta a um evento. O usuário pressiona um botão, e o roteador de UI muda a tela imediatamente. Esta abordagem é simples e previsível, mas limitada em cenários que exigem dados do servidor ou verificação de condições.

Deferred Navigation adiciona uma camada na forma de estado assíncrono. Um evento do usuário inicia uma operação, e uma assinatura do resultado controla a navegação. Isto aumenta a complexidade do código, mas oferece flexibilidade: o mesmo gatilho pode levar a diferentes telas dependendo dos dados carregados.

A escolha entre as duas abordagens depende dos requisitos: se exibir uma tela não requer dados assíncronos — use navegação direta. Se a tela depende do resultado de uma requisição, autorização ou condições externas — a navegação adiada é necessária. Uma abordagem híbrida, onde algumas transições são diretas e outras adiadas, é a prática mais comum em aplicações industriais.

Implementação no Android: Navigation Component e ViewModel

Android Jetpack fornece mecanismos para implementar Deferred Navigation no nível da arquitetura. A ideia principal é que a ViewModel gerencia o estado, enquanto a Activity ou Fragment assina as mudanças e desencadeia a navegação através do NavController.

Deferred Navigation com StateFlow

StateFlow em corrotinas Kotlin é a ferramenta ideal para navegação adiada. A ViewModel atualiza um StateFlow com um evento de navegação, e a Activity o observa e realiza a transição. Uma vez que o evento é processado, o StateFlow é limpo, evitando navegação repetida.

kotlin
class MainViewModel : ViewModel() {
    private val _navigation = MutableSharedFlow<NavigationEvent>()
    val navigation: SharedFlow<NavigationEvent> = _navigation

    fun onDeepLinkReceived(link: String) {
        viewModelScope.launch {
            val data = resolveDeepLink(link)
            _navigation.emit(NavigationEvent.GoToScreen(data))
        }
    }
}

Deferred Navigation com NavController

Na Activity, uma assinatura de navegação aciona o NavController com a rota da ViewModel. Para evitar navegação repetida ao rotacionar a tela, é usado um wrapper NavigationEventWrapper que processa o evento apenas uma vez. Jetpack Navigation 2.7+ suporta Safe Args para passagem de argumentos type-safe.

kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val vm: MainViewModel by viewModels()
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            vm.navigation.collect { event ->
                when (event) {
                    is NavigationEvent.GoToScreen ->
                        findNavController(R.id.nav_host)
                            .navigate(event.route)
                }
            }
        }
    }
}

Implementação no iOS: Coordinator Pattern e Combine

iOS não tem um Navigation Component embutido similar ao Android Jetpack, então os desenvolvedores implementam Deferred Navigation através do Coordinator Pattern em combinação com Combine ou async/await. O Coordinator gerencia a pilha de telas e toma decisões de navegação com base nos dados carregados.

Coordinator com Combine

Coordinator é um objeto que gerencia a navegação entre ViewControllers. Em combinação com Combine, a ViewModel publica eventos através de PassthroughSubject, e o Coordinator os assina e realiza a transição. Esta abordagem separa completamente a UI da lógica de navegação e segue as recomendações da Apple para arquitetura de aplicações.

swift
final class AppCoordinator {
    private var cancellables = Set<AnyCancellable>()

    func start(viewModel: MainViewModel) {
        viewModel.$navigationDestination
            .compactMap { $0 }
            .sink { [weak self] destination in
                self?.navigateTo(destination)
            }
            .store(in: &cancellables)
    }
}

Deferred Navigation com async/await

Swift 5.5 introduziu concorrência estruturada, que permite implementar navegação adiada através de async/await sem Combine. A ViewModel fornece uma função async que retorna uma rota após carregar os dados. O Coordinator chama esta função em uma Task e realiza a transição com base na rota recebida.

swift
class AuthViewModel: ObservableObject {
    func resolveDeeplink(_ url: URL) async -> AppRoute? {
        guard let token = await AuthService.shared.getValidToken() else { return .login }
        return await DeeplinkRouter.resolve(url, token: token)
    }
}

// In Coordinator:
Task {
    if let route = await viewModel.resolveDeeplink(url) {
        navigateTo(route)
    }
}

Erros comuns e melhores práticas

Deferred Navigation simplifica o tratamento de cenários assíncronos, mas requer uma abordagem disciplinada ao gerenciamento de estado. Vamos examinar os principais erros que os desenvolvedores cometem ao implementar navegação adiada.

Erro: navegação antes da conclusão da inicialização

O erro mais comum é tentar realizar navegação adiada antes que a tela raiz esteja completamente inicializada e o NavController ou Coordinator esteja pronto para a transição. No Android isto leva a IllegalStateException, no iOS — a um estado de UI indefinido. A solução é garantir que o ciclo de vida do componente esteja no estado STARTED ou RESUMED antes de desencadear a navegação.

Erro: navegação repetida ao retornar a uma tela

Se o StateFlow ou Subject não limpar o evento após o processamento, ao retornar à tela anterior o usuário pode ser automaticamente redirecionado para a mesma tela novamente. Use SharedFlow com replay=0 no Android ou CurrentValueSubject com nil após o processamento no iOS, para que o evento de navegação dispare apenas uma vez.

Melhor prática: um único NavController ou Coordinator

Use um componente central para toda a navegação na aplicação. Quando cada Activity, Fragment ou ViewController tem seu próprio controlador de navegação, a navegação adiada entre diferentes partes da aplicação se torna caótica. Um único Coordinator simplifica a depuração e o teste de cenários de navegação.

Melhor prática: teste de cenários de navegação adiada

Deferred Navigation é mais difícil de testar do que a navegação direta porque operações assíncronas introduzem um fator tempo. Use TestDispatcher no Android (kotlinx-coroutines-test) e XCTestExpectation no iOS para simular carregamento de dados e verificar que a navegação segue a rota esperada. Simule serviços de autorização e deeplinks para teste isolado de cada cenário.

Perguntas Frequentes

Como Deferred Navigation difere de Deep Link?

Deferred Navigation é um padrão de transição adiada que pode ser aplicado em qualquer cenário assíncrono. Deep Link é um gatilho para navegação adiada, mas não o único. Autorização e feature flags também usam navegação adiada.

Pode-se combinar navegação adiada e direta?

Sim, a maioria das aplicações usa uma abordagem híbrida. Uma tela de lista de produtos (sem dependências assíncronas) pode usar navegação direta, enquanto uma tela de detalhes com carregamento de dados usa adiada. A separação é determinada pela arquitetura de cada tela específica.

Como evitar condições de corrida com navegação adiada?

Use SharedFlow sem replay no Android e combineLatest sem buffer no iOS. Cancele assinaturas anteriores em um novo gatilho. Isto garante que apenas o último evento de navegação seja processado.

Jetpack Compose suporta navegação adiada?

Sim, no Compose a navegação adiada é implementada através de assinatura do StateFlow da ViewModel e chamada de NavController.navigate em LaunchedEffect. O Google recomenda usar Navigation Compose com um modelo orientado a eventos para cenários adiados.

Como lidar com navegação adiada quando o aplicativo está minimizado?

Adie a navegação até que o aplicativo se torne ativo novamente. No Android use Lifecycle.State.STARTED para filtrar eventos. No iOS verifique UIApplication.State em blocos Combine ou async/await.

Resumo

  • Deferred Navigation é um padrão onde a transição ocorre após a conclusão de uma operação assíncrona
  • Cenários principais: autenticação, Deeplink, notificações push, feature flags
  • StateFlow/SharedFlow no Android e Combine/async-await no iOS são ferramentas de implementação
  • Deferred Navigation ao contrário da direta não causa condições de corrida durante carregamento assíncrono
  • Navigation Component no Android e Coordinator Pattern no iOS são as arquiteturas base
  • Um único NavController/Coordinator é obrigatório para navegação consistente
  • Teste de navegação adiada requer TestDispatcher e mocks para serviços assíncronos

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também