Flutter MaterialPageRoute as fullscreenDialog appears underneath BottomNavigationBar - flutter

There's a similar unanswered question here: FullscreenDialog screen not covering bottomnavigationbar
but I want to provide more context, to see if that helps find a solution.
We'll start at the top with my main.dart, I am building a MaterialApp that builds a custom DynamicApp. Here's the important stuff:
Widget build(BuildContext context) {
var _rootScreenSwitcher = RootScreenSwitcher(key: switcherKey);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (context, child) {
return DynamicApp(
navigator: locator<NavigationService>().navigatorKey,
child: child,
switcher: _rootScreenSwitcher,
);
},
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case SettingsNavigator.routeName:
return MaterialPageRoute(
builder: (context) => SettingsNavigator(),
fullscreenDialog: true);
default:
return MaterialPageRoute(builder: (context) => SettingsNavigator());
}
},
home: _rootScreenSwitcher,
);
}
My DynamicApp sets up the root Scaffold like so:
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawer(
selectedIndex: _selectedIndex,
drawerItems: widget.drawerItems,
headerView: Container(
child: Text('Drawer Header'),
decoration: BoxDecoration(color: Colors.blue),
),
onNavigationItemSelect: (index) {
onTapped(index);
return true; // true means that drawer must close and false is Vice versa
},
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (index) {
onTapped(index);
},
currentIndex: _selectedIndex,
items: bottomNavBarItems,
showUnselectedLabels: false,
),
body: widget.child,
);
}
The child of the DynamicApp is a widget called RootScreenSwitcher which is an IndexedStack and controls the switching of screens from my BottomNavigationBar and also when items are selected in the Drawer. Here's the RootScreenSwitcher:
class RootScreenSwitcherState extends State<RootScreenSwitcher> {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
set currentIndex(index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _currentIndex,
children: menuItems.map<Widget>((MenuItem item) {
return item.widget;
}).toList(),
),
),
);
}
void switchScreen(int index) {
setState(() {
_currentIndex = index;
});
}
}
Each of the main section of the app has its own Navigator and root screen. This all works fine, and I'm happy with the overall navigation structure. Each root screen has its own AppBar but the global Drawer and BottomNavigationBar are handled in the DynamicApp so I don't have to keep setting them in the other Scaffold screens.
So, then it came to start to introduce other sections of the app that are not serviced by the bottom tab bar, and can be presented from the Drawer or from other action buttons. Each of these new sections would have to be modal fullscreenDialog screens so they slide up from the bottom, but have their own navigation and scaffold.
My issue is that when I navigate to my SettingsNavigator screen it slides up from behind the BottomNavigationBar, and not on top of everything. Here's the onGenerateRoute method of the MaterialApp:
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case SettingsNavigator.routeName:
return MaterialPageRoute(
builder: (context) => SettingsNavigator(),
fullscreenDialog: true);
default:
return MaterialPageRoute(builder: (context) => SettingsNavigator());
}
}
I'm new to Flutter and don't quite understand how routing works with contexts, so I am wondering whether the context of the screen that calls the navigateTo method of the Navigator becomes the main build context, and is therefore not on top of the widget tree?
gif here: https://www.dropbox.com/s/nwgzo0q28cqk61p/FlutteRModalProb.gif?dl=0
Here's the tree structure that shows that the Scaffold for the Settings screen has been placed inside the DynamicApp Scaffold. The modal needs to sit above DynamicApp.
Can anyone shed some light on this?
UPDATE: I have tried creating and sharing a unique ScaffoldState key for the tab bar screens, and then the Settings page has a different key. It made no difference. I wonder now if it is the BuildContext having the same parent.
UPDATE UPDATE:
I had a breakthrough last night which has made me realise that it just isn't going to be possible to use embedded Scaffolds in the way I have them at the moment. The problem is that i have a root scaffold called DynamicApp which persists my Drawer and BottomNavigationBar, but loading in other Scaffold pages into the body means the modals are slotting into that body and behind the BottomNavigationBar. To solve the problem you have to subclass BottomNavigationBar and reference it in every Scaffold; which means encapsulating all of the business logic so it uses ChangeNotifier to change state when the nav is interacted with. Basically, Flutter forces a separation of concerns on your architecture, which I guess is a good thing. I'll compose a better answer when I've done all the extra work.

After many hours tearing my hair out trying to pass ScaffoldState keys around, and Navigators the answer was to build the BottomNavigationBar into every Scaffold. But to do this I've had to change how my architecture works ... for the better! I now have a BottomNavigationBar and RootScreenSwitcher that listens for updates from an AppState ChangeNotifier and rebuilds itself when the page index changes. So, I only have to change state in one place for the app to adapt automatically. This is the AppState class:
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier {
int _pageIndex = 0;
int get pageIndex {
return _pageIndex;
}
set setpageIndex(int index) {
_pageIndex = index;
notifyListeners();
}
}
and this is my custom BottomNavigationBar called AppBottomNavigationBar:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AppBottomNavigationBar extends StatefulWidget {
AppBottomNavigationBar({Key key}) : super(key: key);
#override
_AppBottomNavigationBarState createState() => _AppBottomNavigationBarState();
}
class _AppBottomNavigationBarState extends State<AppBottomNavigationBar> {
#override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(
context,
);
int currentIndex = state.pageIndex;
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: currentIndex ?? 0,
items: bottomNavBarItems,
showUnselectedLabels: false,
onTap: (int index) {
setState(() {
state.setpageIndex = index;
});
},
);
}
}
So, now in my other Scaffold pages I only need to include this line to make sure the BottomNavigationBar is in the Scaffold`:
bottomNavigationBar: AppBottomNavigationBar(),
Which means absolute minimal boilerplate.
I changed the name of the DynamicApp class to AppRootScaffold and this now contains a Scaffold, Drawer, and then set the body of the Scaffold as the RootScreenSwitcher:
class RootScreenSwitcher extends StatelessWidget {
RootScreenSwitcher({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(
context,
);
int currentIndex = state.pageIndex;
return SafeArea(
top: false,
child: IndexedStack(
index: currentIndex ?? 0,
children: menuItems.map<Widget>((MenuItem item) {
return item.widget;
}).toList(),
),
);
}
}
I still have a lot to do to streamline this architecture, but it is definitely the better way to go.
UPDATE:
Can you spot the problem with the new Scaffold architecture?
This is still not great. Ironically, I need the BottomNavigationBar back in the root Scaffold for this to work as expected. But then the modals wont appear over the top of the bar again.

Related

Flutter change a "shared" widget as the route changes, like the navBar of facebook.com

I don't know if I used correct terms in the title. I meant share by being displayed in diffrent pages with the same state, so that even if I push a new page, the “shared” widget will stay the same.
I'm trying to share the same widget across several pages, like the navigation bar of facebook.com.
As I know, Navigator widget allows to build up a seperate route. I've attempted to use the widget here, and it works quite well.
...
Scaffold(
body: Stack(
children: [
Navigator(
key: navigatorKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) => MainPage());
},
// observers: <RouteObserver<ModalRoute<void>>>[ routeObserver ],
),
Positioned(
bottom: 0,
child: BottomBarWithRecord(),
)
],
));
...
To summarize the situation, there used to be only one root Navigator (I guess it's provided in MaterialApp, but anyway), and I added another Navigator in the route under a Stack (which always display BottomBarWithRecord).
This code works perfect as I expected, that BottomBarWithRecord stays the same even if I open a new page in that new Navigator. I can also open a new page without BottomBarWithRecord by pushing the page in the root Navigator: Navigator.of(context, rootNavigator: true).push(smthsmth)
However, I couldn't find a way to change BottomBarWithRecord() as the route changes, like the appbar of facebook.com.
What I've tried
Subscribe to route using navigator key
As I know, to define a navigator key, I have to write final navigatorKey = GlobalObjectKey<NavigatorState>(context);. This doesn't seem to have addListener thing, so I couldn't find a solution here
Subscribe to route using navigator observer
It was quite complicated. Normally, a super complicated solutions works quite well, but it didn't. By putting with RouteAware after class ClassName, I could use some functions like void didPush() {} didPop() didPushNext to subscribe to the route. However, it was not actually "subscribing" to the route change; it was just checking if user opened this page / opened a new page from this page / ... , which would be complicated to deal with in my situation.
React.js?
When I learned a bit of js with React, I remember that this was done quite easily; I just had to put something like
...
const [appBarIndex, setAppBarIndex] = useState(0);
//0 --> highlight Home icon, 1 --> highlight Chats icon, 2 --> highlight nothing
...
window.addEventListener("locationChange", () => {
//location is the thing like "/post/postID/..."
if (window.location == "/chats") {
setAppBarIndex(1);
} else if (window.location == "/") {
setAppBarIndex(0);
} else {
setAppBarIndex(2);
}
})
Obviously I cannot use React in flutter, so I was finding for a similar easy way to do it on flutter.
How can I make the shared BottomBarWithRecord widget change as the route changes?
Oh man it's already 2AM ahhhh
Thx for reading this till here, and I gotta go sleep rn
If I've mad e any typo, just ignore them
You can define a root widget from which you'll control what screen should be displayed and position the screen and the BottomBar accordingly. So instead of having a Navigator() and BottomBar() inside your Stack, you'll have YourScreen() and BottomBar().
Scaffold(
body: SafeArea(
child: Stack(
children: [
Align(
alignment: Alignment.topCenter,
child: _buildScreen(screenIndex),
),
Align(
alignment: Alignment.bottomCenter,
child: BottomBar(
screenIndex,
onChange: (newIndex) {
setState(() {
screenIndex = newIndex;
});
},
),
),
],
),
),
)
BotttomBar will use the screenIndex passed to it to do what you had in mind and highlight the selected item.
_buildScreen will display the corresponding screen based on screenIndex and you pass the onChange to your BottomBar so that it can update the screen if another item was selected. You won't be using Navigator.of(context).push() in this case unless you want to route to a screen without the BottomBar. Otherwise the onChange passed to BottomBar will be responsible for updating the index and building the new screen.
This is how you could go about it if you wanted to implement it yourself. This package can do what you want as well. Here is a simple example:
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
final PersistentTabController _controller = PersistentTabController(initialIndex: 0);
#override
Widget build(BuildContext context) {
return PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
);
}
List<Widget> _buildScreens() {
return [
const FirstScreen(),
const SecondScreen(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
icon: const Icon(Icons.home),
title: ('First Screen'),
),
PersistentBottomNavBarItem(
icon: const Icon(Icons.edit),
title: ('Second Screen'),
),
];
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Center(
child: Text('First Screen'),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Center(
child: Text('Second Screen'),
);
}
}

Update Scaffold-Properties in nested CupertinoPageScaffold in Flutter

How can I update some properties of the roots scaffold in a child widget(page).
Here is a snippet from my root scaffold.
CupertinoPageScaffold(
resizeToAvoidBottomInset:
state.resizeToAvoidBottomInsets, //update this here
child: CupertinoTabScaffold(
controller: _tabController,
tabBar: CupertinoTabBar(
onTap: onTap,
items: widget.items,
),
tabBuilder: (BuildContext context, index) {
return StatusBarPadding(child: _tabs[index]);
}),
),
The docs say, I should add a listener to avoid a nested scaffold (e.g. to update resizeToAvoidBottomInset).
However, this does only work for one page per tab. When I nest tabs, I can't access them directly anymore.
I tried two solutions which I will explain in the follow (+problems):
Solution 1: Provider
I used a Provider to keep track of a global Navbar State:
class NavbarState extends ChangeNotifier {
bool _resizeBottom;
NavbarState(this._resizeBottom);
get resizeBottom => _resizeBottom;
void setResizeBottom(bool state) {
_resizeBottom = state;
notifyListeners();
}
}
Then In my Pages I set the state in the initState-Method with BlocProvider.of<NavbarState>(context).setResizeBottom(val) (respective for dispose).
This has 2 problems:
Calling notifyListeners triggers a setState in the consumer and you can't call setState in the initState method.
I have to declare this in every initState and dispose method.
Solution 2: Bloc
Once again I have a global state, but it does not have to inherit from `ChangeNotifier`. I track the state with a `NavbarBloc`-class.
Then I can add an event in the onGenerateRoute method. This is more handy then the provider approach, because there is just one place where I manage this state.
However, there is still a big problem:
When I navigate back, the onGenerateRoute Method does not get called and hence the state is not getting updated.
What the easiest solution would be
At least from an app-developer perspective it would be nice if I could just ask for the the current widget which is sitting in the active navigator.
Example of a Navbar
Here is an illustration of 3 navigators for the given cupertinotabscaffold.
The middle "stack" is active and the topmost widget is seen on the screen. Thus, currently the resize param should be false. On navigating between the stacks (tapping navigation icon), the resize parameter should adjust. Furthermore, on navigating in between a single stack (push, pop) should also adjust the resize param (E.g. on a pop the param should be set to true).
I couldn't find anything like that. Thus I need your help.
For setting the state how about a setter as a callback to onTap?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var values = [
[true, false],
[false, true],
];
int stackIndex = 0;
bool _resizeToAvoidBottomInsets = true;
set _resizeToAvoidButtomInsets(bool value) => setState(() {
print("set _resizeToAvoidBottomInsets = $value");
_resizeToAvoidBottomInsets = value;
});
void handleTap(int i) {
print("tapped: $i");
_resizeToAvoidBottomInsets = values[stackIndex][i];
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
resizeToAvoidBottomInset: _resizeToAvoidBottomInsets,
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.ac_unit)),
BottomNavigationBarItem(icon: Icon(Icons.wb_sunny)),
],
onTap: handleTap,
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Page 1 of tab $index'),
),
child: Center(
child: CupertinoButton(
child: Text(
'resizeToAvoidBottomInsets: $_resizeToAvoidBottomInsets',
),
onPressed: () {
// set state and increment stack index before push
stackIndex++;
_resizeToAvoidButtomInsets = values[stackIndex][0];
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Page 2 of tab $index'),
),
child: Center(
child: CupertinoButton(
child: Text(
'resizeToAvoidBottomInsets: $_resizeToAvoidBottomInsets',
),
onPressed: () {
// set state and decrement stack index before pop
stackIndex--;
_resizeToAvoidButtomInsets =
values[stackIndex][0];
Navigator.of(context).pop();
}),
),
);
},
),
);
},
),
),
);
},
);
},
),
);
}
}
So I implemented a solution for the given problem. I don't think this is very smooth, but at least one can abstract the scaffold-properties from each single page.
I use a bloc for that as already mentioned in the question-solution-try-2.
The bloc emits a state MyScaffoldState which contains a ScaffoldProperties attribute. For this toy example I implemented it as follows:
class ScaffoldProperties {
bool resizeBottom;
String title;
ScaffoldProperties({this.resizeBottom = false, required this.title});
ScaffoldProperties copyWith({ScaffoldProperties? state}) {
return ScaffoldProperties(
resizeBottom: state?.resizeBottom ?? resizeBottom,
title: state?.title ?? title,
);
}
}
You could add any scaffold-property to this class.
The main work happens in my MultiTabScaffold class. This class takes the parameters
List<Widget> pages;
List<BottomNavigationBarItem> items;
Route<dynamic>? Function(RouteSettings)? onGenerateRoute;
List<NavigatorObserver> navigatorObservers
And most importantly
Function<ScaffoldProperties> getScaffoldPropertiesFromRouteName(String? route);
The class wraps the necessary Scaffolds around the pages. Additionally, it uses a BlocBuilder and thus can use the ScaffoldProperties of the current state.
The getScaffoldPropertiesFromRouteName Method takes a route and returns a ScaffoldProperties-Instance. Thus all title, resizeProperties,... have to be collected here.
To update the current state (or emit events to the bloc), I had to modify a few points.
onGenerateRoute: After generating the route (pushing to navigator), the ScaffoldProperties of the current page have to be emitted (with usage of the getScaffoldPropertiesFromRouteName Function)
WillPopScope: Also after popping the page, I emit an event with the properties of the page below.
Initial Value: Initially, when you open the app, neither onGenerateRoute nor willpop will be called. Thus, you have to have a different handling for the initial route '/' of every Navigator.
I have a toy example fully implemented.
This feels like a lot of work for such a simple use case, but I couldn't find a simpler way to do this. If there is one, please let me know.

Flutter Remove Bottom Navigation Bar on Navigatore.push

I'm working on a project with flutter, I have a bottom navigation bar , this bar allow me to move on different scenes . I would like to remove my bottom navigation bar when i navigate to a detail page, there is any method? I'm trying all the possible solution , but i can't solve the problem, maybe i've implemented wrong my navigation bar !
Some Code:
My HomePage where i implemented the bottom navigation bar and where i call all the others pages:
class homeScreen extends StatefulWidget {
#override
_homeScreenState createState() => _homeScreenState();
}
class _homeScreenState extends State<homeScreen> {
TextEditingController _linkController;
int _currentIndex = 0;
final _pageOptions = [
meetingScreen(),
dashboardScreen(),
notificationScreen(),
profileScreen(),
];
#override
void initState() {
// TODO: implement initState
super.initState();
_linkController = new TextEditingController();
}
#override
Widget build(BuildContext context) {
ScreenUtil.instance = ScreenUtil(width: 1125, height: 2436)..init(context);
return MaterialApp(
title: myUi.myTitle,
theme: myUi.myTheme ,
debugShowCheckedModeBanner: false,
home:Scaffold(
bottomNavigationBar: _buildBottomNavigationBar(),
body: _pageOptions[_currentIndex] ,
) ,
);
}
Widget _buildBottomNavigationBar() {
return BottomNavigationBar(
backgroundColor: Colors.white,
selectedItemColor: Theme.of(context).primaryColor,
//fixedColor: gvar.secondaryColor[gvar.colorIndex],
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.explore,size: ScreenUtil().setWidth(80),),
title: new Text("Esplora"),
),
BottomNavigationBarItem(
icon:Icon(Icons.dashboard,size: ScreenUtil().setWidth(80),),
title: new Text("Attività"),
),
BottomNavigationBarItem(
icon:Icon(Icons.notifications,size: ScreenUtil().setWidth(80),),
title: new Text("Notifiche"),
),
BottomNavigationBarItem(
icon:Icon(Icons.person,size: ScreenUtil().setWidth(80),),
title: new Text("Profilo"),
),
],
);
}
}
This is the code i use into the dashboardScreen to push to my detail page:
onTap: () {
//method: navigate to Meeting
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => new meetingScreen()),
);
},
In the new meetingScreen i have some ui , but i cant remove my old bottom navigation bar, i tried implementing a new navigation bar but it isn't displayed.
A partial working solution was to do like so:
Navigator.of(context, rootNavigator: true).pushReplacement(MaterialPageRoute(builder: (context) => new meetingScreen()));
But now I can't do Navigator.pop with the cool animation , because the new page is the root!
Thanks guys for the support !!
To navigate to a different screen without the bottom Navigation bar, you'd need to call Navigator.push() from _homeScreenState. For you to be able to do this, you need to create a NavigatorKey in _homeScreenState and set it in MaterialApp().
final _mainNavigatorKey = GlobalKey<NavigatorState>();
...
MaterialApp(
navigatorKey: _mainNavigatorKey,
...
)
You can then pass the NavigatorKey to your pages. To use _mainNavigatorKey, fetch its current state to be able to invoke push().
navigatorKey.currentState!.push(...)
Here's a sample that I have. Though instead of a bottom Navigation bar, I'm using a Navigation Drawer. The same method applies in this sample.

Flutter persistent bottom navigation bar and page view replacement approach

I have a Navbar state full widget that tracks current page and returns a widget with a bottom navbar and dynamic body based of current page which is stored as a state
class _PullingoNavbarState extends State<PullingoNavbar> {
static int _page = 1;
final _screens = {0: PullingoMap(), 1: Dashboard(), 2: Dashboard()};
#override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_page],
bottomNavigationBar: CurvedNavigationBar(
animationDuration: Duration(milliseconds: 200),
backgroundColor: Colors.blueAccent,
index: _page,
items: <Widget>[
PullingoIcon(icon: Icons.favorite),
PullingoIcon(icon: Icons.chrome_reader_mode),
PullingoIcon(icon: Icons.person),
],
onTap: (index) {
setState(() {
_page = index;
});
},
),
);
}
}
and the root widget as follows:
class RoutesWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(
title: 'PULLINGO',
theme: pullingoTheme,
routes: {
"/": (_) => PullingoNavbar(),
},
);
}
pre creating instances of _screens in a map doesn't feel like a good approach to me. this probably will create states of those screens regardless of user visits them or not. There are few suggestions given here. does the above method look okay or should I completely avoid this approach.
You can use PageView and AutomaticKeepAliveClientMixin to persist your widgets when navigating. With this approach a widget is only created when a user navigates to it by bottom navigation bar. I have recently written an article about how to use it, might be useful.
https://cantaspinar.com/persistent-bottom-navigation-bar-in-flutter/

Flutter : handling multiple navigation screen in CupertinoTabScaffold

Hello I am new to Flutter and I am trying to implement a bottom tab bar with multiple navigation screen for each tab.
Here is my initial set up
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return CupertinoApp(home: HomeScreen(),
routes: {
Screen1.id: (context) => Screen1(),
Screen2.id: (context) => Screen1(),
DetailScreen3.id: (context) => DetailScreen3(),
DetailScreen4.id: (context) => DetailScreen4(),
});
}
}
Here is my HomeScreen
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.book_solid),
title: Text('Articles'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.eye_solid),
title: Text('Views'),
),
],
),
tabBuilder: (context, index) {
if (index == 0) {
return Screen1();
} else {
return Screen2();
}
},
);
}
}
Here is my screen1
class Screen1 extends StatelessWidget {
static const String id = 'screen1';
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, DetailScreen3.id);
},
child: Center(
child: Text('Screen 1',),
),
),
);
}
}
and here is my screen3
class DetailScreen3 extends StatelessWidget {
static const String id = 'screen3';
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Center(
child: Text('terzo schermo',),
),
);
}
}
The tabBar work ok and I am able to swap between the 2 tabs but I am not able to navigate from screen 1 to screen 3. When I tap on screen1 Center widget, the screen start to navigate but half way it stops and then the screen become all black...
Here is the error
There are multiple heroes that share the same tag within a subtree.
Within each subtree for which heroes are to be animated (i.e. a
PageRoute subtree), each Hero must have a unique non-null tag. In this
case, multiple heroes had the following tag: Default Hero tag for
Cupertino navigation bars with navigator NavigatorState#05492(tickers:
tracking 2 tickers)
I understand the problem is related to the hero tag of the navigation bar which must have a unique identifier. How should I fix this problem? should I assign an heroTag to all navigation bar???
Many thanks in advance for the help
I resolved by setting the following properties for each CupertinoNavigationBar
heroTag: 'screen1', // a different string for each navigationBar
transitionBetweenRoutes: false,
As an iOS developer, I tried flutter for the first time, this thing caused a black screen after jumping the page, and also troubled me for two days
heroTag: 'screen1', // a different string for each navigationBar
transitionBetweenRoutes: false,