Flutter - Drawer: show existing page if exists, not a new instance

819 Views Asked by At

The app I am developing attempts to adopt, as much as possible, the most native experience for Android & iOS users.

To do so, the app runs a CupertinoApp and a MaterialApp.

On iOS, I use the CupertinoScaffold showing a CupertinoTabBar with BottomNavigationBarItems. It performs very well as expected:

  • pages are created only once, the first time I click on a tab;
  • pages are restored when returning to a previously displayed tab: in one of my tab, there is a list and its scroll position is always maintained, persisted.

on Android, the story is different: I am using the Drawer widget. When I click on an entry, I call Navigator#push to show the associated page. However, this will keep adding new instances of each entry's page in the stack.

I don't seem to be able to return to an existing page. At least, not how I can do it with iOS.

Looking at the Navigator I see functions that seem to achieve what I am looking for:

  • popUntil => will show my existing screen, but at the expanses of all the other ones that will therefore make a sacrifice for the sake of just one page. So much ado for nothing...
  • pushReplacement => will show a new instance of the target page and destroy the current one. Not desirable.

What am I missing ? How can I achieve that the CupertinoTabBar seems to be able to do?

1

There are 1 best solutions below

0
On

Well, I solved my issue by taking another direction.

Simply put I don't think there is a way, with the Navigator to achieve what I want.

The solution I find out was: Keep state of widgets with Bottom Navigation Bar in Flutter

This guide suggests to use a IndexedStack that displays only one child at the time while keeping its state.

So this is how I managed to make it work:

  1. Create your MaterialApp
  2. Set its home to be a custom stateful widget called MaterialHomePage
  3. This MaterialHomePage builds a Scaffold:
    • with an AppBar and its title (the title of the active page);
    • with a drawer and its arguments (more on that later)
    • and the body is that so-called wonderful IndexedStack:
      • set its children to be the list of pages / widgets, the same pages available in the drawer
      • set index to be the property _selectedPageIndex of your MaterialHomePage
    • and with a function that will be triggered when the user chooses a page to navigate to, in which case the usual call to setState occurs inside of which you may update the _selectedPageIndex property as well as _selectedPageIndexproperty`.
  4. The Drawer:
    • takes two parameters:
      • the current active page index (defaults to 0 at app launch)
      • a Function with the target page title & index
    • must list as many entries (usually ListTile) as pages you have declared in the IndexedStack displayed in MaterialHomePage.
    • upon clic on the entry, it calls the aforementioned function and passes the index of the clicked entry as well as the associated page title.

At the end, what happens:

  1. The user opens the drawer.
  2. The user chooses a new destination (let's say: entry #3).
  3. MaterialHomePage gets notified (via callback) of the user choice which triggers a setStage that will both:
    • update the current page thanks to IndexedStack with index = 3
    • and update its AppBar's title

and voilà there you go: each page gets retained, no loss of state and a smooth navigation.

NB: if the user uses the Android's back button feature, at this point it will close the app since the Navigator stack is empty. Maybe there is a way to listen for such an event if you desire to default to the first entry / page (displayed at launch) if the user was in a another page.

NB2: I also wonder if there is a way to animate page transitions.

PS: Let me know if the answers is a fine solution in which case I'll validate it. Or if you find a proper way to achieve it.