Android

rememberDecoratedNavEntries Faz rememberSaveable Funcionar em Vários NavBackStacks

O texto explica como o Navigation 3 preserva o estado de tela em aplicativos com múltiplas abas, usando a função rememberDecoratedNavEntries para associar explicitamente o estado UI ao histórico de navegação correspondente. Essa abordagem permite que cada aba tenha seu próprio histórico e estado independente, garantindo que as alterações feitas em uma aba não afetem as outras.

Compartilhar
Medium

rememberDecoratedNavEntries Faz rememberSaveable Funcionar em Vários NavBackStacks

Como o Navigation 3 preserva o estado da tela em pilhas de navegação independentes

Pressione enter ou clique para visualizar a imagem em tamanho completo
Imagem criada por IA

Quando um aplicativo tem várias abas, cada aba geralmente precisa de sua própria história de navegação.

Por exemplo:

Home
└─ HomeBackStack

Search
└─ SearchBackStack

Profile
└─ ProfileBackStack

Cada aba tem um NavBackStack independente.

O problema é que as telas dentro dessas pilhas também podem conter um estado de IU local:

@Composable
fun HomeScreen() {
var query by rememberSaveable {
mutableStateOf("")
}

HomeContent(
query = query,
onQueryChange = { query = it }
)
}

O que acontece com o query quando o usuário muda para outra aba?

O Problema: Pilhas de Navegação Múltiplas

O Problema: Pilhas de Navegação Múltiplas

var currentTab by rememberSerializable {
mutableStateOf<TabRoot>(Home)
}

val homeBackStack = rememberNavBackStack(Home)
val searchBackStack = rememberNavBackStack(Search)
val profileBackStack = rememberNavBackStack(Profile)

Cada aba tem sua própria história de navegação:

HomeBackStack
Home → Form

SearchBackStack
Search → Result → Detail

ProfileBackStack
Profile

A história de navegação é independente.

No entanto, também precisamos preservar o estado da IU dentro de cada tela.

rememberSaveable É Local à Composição

Considere esta tela:

@Composable
fun TelaInicial() {
var consulta by rememberSaveable {
mutableStateOf("")
}

TextField(
value = consulta,
onValueChange = { consulta = it }
)
}

A tela possui o estado:

TelaInicial
└─ rememberSaveable
└─ consulta

Se cada aba tem sua própria história de navegação, o estado deve estar associado à entrada de navegação correspondente.

HomeBackStack

Entrada de Navegação Home

TelaInicial

rememberSaveable

rememberDecoratedNavEntries Faz a Relação Ser Explícita

Ao invés de passar o back stack diretamente para NavDisplay, podemos criar explicitamente as entradas:

val homeEntries = rememberDecoratedNavEntries(
backStack = homeBackStack,
entryDecorators = decorators,
entryProvider = entryProvider {
entry<Home> {
TelaInicial()
}

entry<Form> {
FormScreen()
}
}
)

Então:

NavDisplay(
entries = homeEntries
)

A sequência agora é explícita:

HomeBackStack

rememberDecoratedNavEntries

Entrada de Navegação Home

TelaInicial

O mesmo padrão pode ser usado para cada aba.

val searchEntries = rememberDecoratedNavEntries(
backStack = searchBackStack,
entryDecorators = decorators,
entryProvider = entryProvider {
entry<Search> {
SearchScreen()
}

entry<Result> {
ResultScreen()
}

entry<Detail> { key ->
DetailScreen(key.id)
}
}
)

Agora temos:

HomeBackStack

HomeEntries


SearchBackStack

SearchEntries


ProfileBackStack

ProfileEntries

A Parte Importante: NavEntryDecorator

O nome rememberDecoratedNavEntries contém uma palavra importante:

Decorado

The entries are not simply created.

They are created with decorators.

For rememberSaveable, the relevant decorator is:

val decorators = listOf(
    rememberSaveableStateHolderNavEntryDecorator(),
)

Each decorator adds entry-scoped behavior.

This gives each navigation entry a state-saving scope.

The Screen Does Not Need Special Code

The screen does not need to know about NavBackStack.

It does not need to receive a SaveableStateHolder.

It can continue to use normal Compose APIs:

@Composable
fun HomeScreen() {
    var query by rememberSaveable {
        mutableStateOf("")
    }

    TextField(
        value = query,
        onValueChange = { query = it }
    )
}

The state management is provided from outside the screen:

NavBackStack
      ↓
rememberDecoratedNavEntries
      ↓
NavEntryDecorator
      ↓
SaveableStateHolder
      ↓
HomeScreen
      ↓
rememberSaveable

This means that the screen can remain focused on UI.

Each Back Stack Has Its Own State Scope

Suppose both Home and Search contain a rememberSaveable value:

HomeBackStack
    └─ HomeEntry
        └─ rememberSaveable
            └─ query = "Compose"

SearchBackStack
    └─ SearchEntry
        └─ rememberSaveable
            └─ query = "Navigation"

The state is independent:

Home:
    query = "Compose"

Search:
    query = "Navigation"

Switching tabs changes the currently displayed entry collection:

HomeEntries

SearchEntries

Mas isso não requer que o estado de navegação da outra guia seja destruído.

HomeBackStack
└─ HomeEntry
└─ Estado da Home

SearchBackStack
└─ SearchEntry
└─ Estado de Pesquisa

NavDisplay(backStack) vs. NavDisplay(entries)

Para múltiplos back stacks independentes, NavDisplay(entries) fornece a cada pilha suas próprias entradas decoradas.

Isso permite que o estado UI salvo sobreviva às trocas de guia e seja restaurado após alterações de configuração ou morte do processo.

A API em alto nível é concisa:

NavDisplay(
backStack = backStack,
entryDecorators = decorators,
entryProvider = entryProvider {
...
}
)

A conversão de NavBackStack para NavEntry é oculta.

Com a API em baixo nível:

val entries = rememberDecoratedNavEntries(
backStack = backStack,
entryDecorators = decorators,
entryProvider = entryProvider {
...
}
)

NavDisplay(
entries = entries
)

a relação se torna explícita:

NavBackStack

rememberDecoratedNavEntries

Decorated NavEntry

NavDisplay

Isso é particularmente útil quando a aplicação tem múltiplos back stacks independentes de navegação.

Uma Guia, Uma Pilha de Navegação, Uma Coleção de Entradas

Um modelo mental simples é:

1 Guia
=
1 NavBackStack
=
1 rememberDecoratedNavEntries
=
1 coleção de objetos NavEntry decorados

Por exemplo:

Home
├─ HomeBackStack
│ └─ Home → Form

└─ HomeEntries
├─ HomeEntry
└─ FormEntry

Search
├─ SearchBackStack
│ └─ Search → Result → Detail

└─ SearchEntries
├─ SearchEntry
├─ ResultEntry
└─ DetailEntry

Cada guia possui ambos:

  • Sua história de navegação
  • Sua coleção de entradas decoradas

Isso torna a relação entre o estado da navegação e o estado UI mais fácil de entender.

Conclusão

Quando uma aplicação tem várias NavBackStacks independentes, o estado dentro de cada tela deve permanecer associado à entrada de navegação que possui essa tela.

rememberDecoratedNavEntries fornece uma maneira explícita de criar essas entradas decoradas:

val entries = rememberDecoratedNavEntries(
backStack = backStack,
entryDecorators = decorators,
entryProvider = entryProvider {
...
}
)

Então:

NavDisplay(
entries = entries
)

Ao adicionar:

rememberSaveableStateHolderNavEntryDecorator()

o estado rememberSaveable dentro de cada tela pode ser escopo para o respectivo NavEntry.

A própria tela não precisa de nenhum código especial.

Pode continuar a usar:

rememberSaveable { … }

A parte importante é a arquitetura ao seu redor:

Várias NavBackStack

rememberDecoratedNavEntries

Estado escopo para o NavEntry

rememberSaveable

A lição principal é:

rememberDecoratedNavEntries permite que cada NavBackStack independente produza sua própria coleção de entradas decoradas, permitindo que o estado rememberSaveable da tela permaneça escopo para a entrada de navegação que possui.

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa