rememberDecoratedNavEntries Faz rememberSaveable Funcionar em Vários NavBackStacks
Como o Navigation 3 preserva o estado da tela em pilhas de navegação independentes
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
└─ ProfileBackStackCada 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
ProfileA 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
└─ consultaSe 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
↓
rememberSaveablerememberDecoratedNavEntries 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
↓
TelaInicialO 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
↓
ProfileEntriesA 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 ↓ rememberSaveableThis means that the screen can remain focused on UI.
Each Back Stack Has Its Own State Scope
Suppose both Home and Search contain a
rememberSaveablevalue: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
↓
SearchEntriesMas 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 PesquisaNavDisplay(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
NavBackStackparaNavEntryé 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
↓
NavDisplayIsso é 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 decoradosPor exemplo:
Home
├─ HomeBackStack
│ └─ Home → Form
│
└─ HomeEntries
├─ HomeEntry
└─ FormEntry
Search
├─ SearchBackStack
│ └─ Search → Result → Detail
│
└─ SearchEntries
├─ SearchEntry
├─ ResultEntry
└─ DetailEntryCada 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.
rememberDecoratedNavEntriesfornece 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
rememberSaveabledentro de cada tela pode ser escopo para o respectivoNavEntry.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
↓
rememberSaveableA 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.

