I use ChangeNotifierProvider of a model, which has a "number" property and a method to set a random value to it. There are 2 routes. In the first one, there is a variable which listens to this property using context.select or context.watch (does not matter) and then used in a Text widget. And there is also a button that calls a method of the model, which sets random value. Initially, if no screens have changed, when this button is pressed, the widget is rebuilt as expected. The problem occurs when pushing to another route and then returning back to the first one. If the button is pressed again, the widget is rebuilt twice, because provider the for some reason is also changed twice. And the more times you switch between screens, the more times the widget is rebuilt when you press the button for setting a random value. Below you can see a screenshot from Dart DevTools with logs. Here I gave a small example, just to illustrate my problem, but in another project, where instead of a simple text widget there is a table with data, the decrease in performance after each screen change becomes more noticeable, because the response to user actions, leading to change in the Model, becomes longer.
class MyModel extends ChangeNotifier {
int _number = 0;
int get number => _number;
void setRandomNumber() {
print('Set random number');
_number = Random().nextInt(1000);
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
routes: {
'/home': (context) => MyHomePage(),
'/second': (context) => SecondPage(),
},
initialRoute: '/home',
),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final number = context.select<MyModel, num>((model) => model.number);
print('REBUILD');
return Scaffold(
body: SafeArea(
child: Column(
children: [
Text(number.toString()),
TextButton(
child: Text('Go to Page 2'),
onPressed: () {
print('Go to Page 2');
Navigator.pushNamed(context, '/second');
},
),
TextButton(
child: Text('Set Random Number'),
onPressed: () => context.read<MyModel>().setRandomNumber(),
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: TextButton(
child: Text('Go to Home Page'),
onPressed: () {
print('Go to Home Page');
Navigator.pushNamed(context, '/home');
},
),
);
}
}
Console:
flutter: Set random number
flutter: REBUILT
flutter: Go to Page 2
flutter: Go to Home Page
flutter: Set random number
flutter: REBUILT
flutter: REBUILT
Logs in DevTools
It's because you push without popping. You have a stack of Screen 1, Screen 2, Screen 1. You press the button on Screen 1, it executes the set random number function and it notifies the listeners on both instances of screen 1.
It's generally a good practice not to have duplicate screens in your navigation stack at any time. Packages like auto_route enforce this somewhat, though you can also manage this without the use of a package. Just be diligent and be wary of pushing when popping can lead you to the same screen.
Related
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'),
);
}
}
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.
One of the main mecanism of Flutter Navigator 2.0 it the function onPopPage inside RouterDelegate > build > Navigator. However, I do not understand when route.didPop(result) returns false.
We can use the John Ryan's famous example to show my question. His demo code.
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
On all of my tests, using AppBar autogenerated back button, route.didPop(result) returns true.
The doc stays :
bool didPop(dynamic result)
package:flutter/src/widgets/navigator.dart
A request was made to pop this route. If the route can handle it internally (e.g. because it has its own stack of internal state) then return false, otherwise return true (by returning the value of calling super.didPop). Returning false will prevent the default behavior of [NavigatorState.pop].
When this function returns true, the navigator removes this route from the history but does not yet call [dispose]. Instead, it is the route's responsibility to call [NavigatorState.finalizeRoute], which will in turn call [dispose] on the route. This sequence lets the route perform an exit animation (or some other visual effect) after being popped but prior to being disposed.
This method should call [didComplete] to resolve the [popped] future (and this is all that the default implementation does); routes should not wait for their exit animation to complete before doing so.
See [popped], [didComplete], and [currentResult] for a discussion of the result argument.
But was does "If the route can handle it internally (e.g. because it has its own stack of internal state) then return false" mean ? The route has its own stack of internal state ? How to produce this result ?
Thank you, stay safe
After some research to fully understand the Navigator 2.0, I think this might be the answer to the question:
route.didPop(result) will return false, when the Route, which are asked to pop, keeps local history entries and they have to be removed before popping the complete Route.
So what are local history entries (the stack of internal states)?
Local history entries are a way to implement local navigation within a page. You can do so using the method addLocalHistoryEntry. To understand this better, take a look at the official Flutter Docs sample:
The following example is an app with 2 pages: HomePage and SecondPage.
The HomePage can navigate to the SecondPage. The SecondPage uses a
LocalHistoryEntry to implement local navigation within that page.
Pressing 'show rectangle' displays a red rectangle and adds a local
history entry. At that point, pressing the '< back' button pops the
latest route, which is the local history entry, and the red rectangle
disappears. Pressing the '< back' button a second time once again pops
the latest route, which is the SecondPage, itself. Therefore, the
second press navigates back to the HomePage.
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (BuildContext context) => HomePage(),
'/second_page': (BuildContext context) => SecondPage(),
},
);
}
}
class HomePage extends StatefulWidget {
HomePage();
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('HomePage'),
// Press this button to open the SecondPage.
ElevatedButton(
child: Text('Second Page >'),
onPressed: () {
Navigator.pushNamed(context, '/second_page');
},
),
],
),
),
);
}
}
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
bool _showRectangle = false;
void _navigateLocallyToShowRectangle() async {
// This local history entry essentially represents the display of the red
// rectangle. When this local history entry is removed, we hide the red
// rectangle.
setState(() => _showRectangle = true);
ModalRoute.of(context).addLocalHistoryEntry(
LocalHistoryEntry(
onRemove: () {
// Hide the red rectangle.
setState(() => _showRectangle = false);
}
)
);
}
#override
Widget build(BuildContext context) {
final localNavContent = _showRectangle
? Container(
width: 100.0,
height: 100.0,
color: Colors.red,
)
: ElevatedButton(
child: Text('Show Rectangle'),
onPressed: _navigateLocallyToShowRectangle,
);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
localNavContent,
ElevatedButton(
child: Text('< Back'),
onPressed: () {
// Pop a route. If this is pressed while the red rectangle is
// visible then it will will pop our local history entry, which
// will hide the red rectangle. Otherwise, the SecondPage will
// navigate back to the HomePage.
Navigator.of(context).pop();
},
),
],
),
),
);
}
}
To see the sample in the docs, click here.
I hope I answered the question in an understandable way.
I want my page to hit the API always whenever it enters a page.
For example, I am having 2 screens i.e FirstSCreen and SecondScreen.
In FirstScreen I am calling an API to fetch some data. So if the user navigates from FirstScreen to SecondScreen and then comes back to FirstScreen by pressing the back button It should hit the API in FirstScreen again.
I want to know is there any inbuilt function in flutter where I should call my methods so that it can work every time it enters the screen. I have tried using didUpdateWidget() but it is not working the way I want.
initState() is also called only once the widget is loaded ..
Please explain me
You can use async await for it. Let's say you have a button that change the route.
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);
ApiCall();
},
The ApiCall function will call only when user push the back on the second screen.
I have an example that might help. However, it might not cover all scenarios where your FirstScreen will always call a specific function whenever the page is displayed after coming from a different page/screen, or specifically resuming the app (coming from background -- not to be confused when coming from another screen or popping).
My example however, will always call a function when you come back from a specific screen, and you can re-implement it to other navigation functions to ensure that a specific function is always called when coming back to FirstScreen from other screens.
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
home_page.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: RaisedButton(
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SecondPage()))
.then((value) => _reload(value)),
child: Text('Navigate to Next Page'),
),
)
],
),
);
}
Future<void> _reload(var value) async {
print(
'Home Page resumed after popping/closing SecondPage with value {$value}. Do something.');
}
}
second_page.dart
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, 'Passed Value');
/// It's important that returned value (boolean) is false,
/// otherwise, it will pop the navigator stack twice;
/// since Navigator.pop is already called above ^
return false;
},
child: Scaffold(
appBar: AppBar(),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text('Second Page')),
],
),
),
);
}
}
#Rick's answer works like a charm even for Riverpod state management as I coul d not get the firstscreen to rebuild to show changes to the 'used' status of a coupon on the second screen where there is a button to use it.
So I did on the details page:
return WillPopScope(
onWillPop: () async {
//final dumbo = ref.read(userProvider.notifier).initializeUser();
Navigator.pop(context);
return false; //true;
},
Then on the coupons list page, each coupon card has:
child: Card(
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.0),
side: BorderSide(
color: coupons[index].isUsed
? Colors.grey
: Colors.blue),
),
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
//go to Details screen with Coupon instance
//https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html
Navigator.pushNamed(context, '/details',
arguments: coupons[index].couponId)
.then((_) => _reload());
/*Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailsScreen(
coupon: coupons[index],
),
),
);*/
},
where I used _ since there is no parameter.
Then the function in class CouponScreen is:
void _reload() {
setState(() {});
}
I believe setState(){} is what triggers the rebuild. I apologize for the formatting but it's very difficult to copy and paste Flutter code that is nested deeply.
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,