bloc eventController is closed unexpected - flutter

i´ve an app that should support responsive design.
In the mobile view I am using the scaffold drawer for the main menu:
return Scaffold(
appBar: AppBar(
title: title,
),
drawer: MainMenuDrawer(),
body: body,
);
In the desktop view the menu should always be visible, so i don´t use the drawer propery. Instead of that i´ve added the same menu as widget into a row:
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true ,
title: title
),
body: Row(
children: [
MainMenuDrawer(),
Expanded(child: body),
],
),
);
The menu has a logout button which sends an event to my authentication bloc and pops all pages from the navigation stack, to get to the first page (login page).
authenticationBloc.add(AuthenticationEvent.loggedOut());
while (Navigator.canPop(context)) {
Navigator.pop(context);
}
The authentication bloc is as singleton:
#singleton
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState>
As long as i am using the mobile view, I can logout from everywhere and it´s works as expected.
But when I switch to the desktop view the logout button doesn´t work anymore.
In the debug mode, I can see, that the eventController of the authBloc is already closed, so no event is added:
void add(Event event) {
if (_eventController.isClosed) return; <- isClosed is true
Does anyone know why this happened in the desktop view?

i´ve found the problen In the onTap method of my listitem.
To close the drawer i´ve called the Navigator.pop(context).
But this will cause the previous page to be closed when the menu is used as drawer.

Related

Why does `const` modifier avoid StreamBuilder rebuild in Flutter?

In my app's homepage I call a ScoreListPageBody that is rebuild when I change the Locale from another page (SettingsPage).
To force rebuild it, it contains a StreamBuilder that is triggered when the Locale changes and the users goes back to the HomePage (using Navigator.push(....).then(() => triggerStream())).
It works very well when I do this in my HomePage :
return Scaffold(
appBar: MenuAppBar(),
body: PageView(
controller: this._pageController,
children: [
ScoreListPageBody(),
FavoritesListPageBody(),
],
)
);
But this prevents the rebuild of ScoreListPageBody's StreamBuilder :
return Scaffold(
appBar: MenuAppBar(),
body: PageView(
controller: this._pageController,
children: const [
ScoreListPageBody(),
FavoritesListPageBody(),
],
)
);
I do not understand why, as const modifier should not impact the StreamBuilder's behavior, right ?
So, it works without const, but is it an issue to report to Flutter team ?
The const modifier impact the StreamBuilder because this is inside ScoreListPageBody() and you put the modifier in the PageView which is one step above the tree.
- PageView
-- ScoreList()
-- ...

How can i make shared appBar and bottomNavigation across flutter screens

i am new to flutter and i would like to have a shared appBar and bottomNavigationBar
the way i am currently using is implementing them in spearte files and importing them in each screen but i need to make one screen contains the appbar and the bottomNavigationBar and the content of the body renders diffrent views according to the routes and i can't make it at main page because the main page renders welcome screen
in reactJS i was importing the header and footer and make my routes between them how can i make somthing similar in flutter
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: navBar(),
body: SingleChildScrollView(reverse: true, child: ScanPassport()),
bottomNavigationBar: bottomNavigation(),
currentIndex: _selectedIndex,
onTap: _onItemTapped,
showSelectedLabels: false,
showUnselectedLabels: false,
type: BottomNavigationBarType.fixed,
),
);
}
}
A couple of options:
Use MaterialApp() router. There's a tutorial here showing how to refactor your Scaffold() so you don't have duplicate code for appBar, bottomNavigationBar, etc. This is the official Flutter method for view routing.
If you need to swap out your Scaffold() body but can't change your other Scaffold() elements, you can create a widget for the body that takes a "page" value and change what it returns from build() based on the "page" value passed. The widget holding your Scaffold can be stateful and your bottom navigation bar can setState() the page number.
In your context, You can just have a single scaffold homepage screen after logged in success or after welcome screen (depends up app). Once you navigate to homepage, You will have appBar and bottom navigation bar at top and bottom respectively. The remaining portion at center will be your body content of the selected tab. Once you switched the tap on bottom navigation, you should change the content rather than using routes for switching the body content. The current page status will be preserved on homepage.

flutter hot reload other pages

I am beginner of flutter.
Text (printA ())
and
PrintA () {print ("A"); retuen "A";}
are executed on page A of Flutter, and "A" is displayed in the console after hot reloading. Then navigate to page B and print "B" to the console as well. At this time, if I hot reload, not only "B" but also "A" will be displayed on the console. If I repeat the screen navigation, the number of A and B displayed on the console will continue to increase with one hot reload. What is the reason for this?
class PageA extends StatelessWidget {
const WordPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
appBar: AppBar(
title: Text("PageA"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(printA()),
],
),
),
);
}
}
printA() {
print("A");
return "A";
}
Also, if I just make a screen navigate in the emulator without hot reloading, only "A" on page A and only "B" on page B will be displayed on the console as usual. and I use m1 mac.
A widget that manages a set of child widgets with a stack discipline.
Many apps have a navigator near the top of their widget hierarchy in
order to display their logical history using an Overlay with the most
recently visited pages visually on top of the older pages. Using this
pattern lets the navigator visually transition from one page to
another by moving the widgets around in the overlay. Similarly, the
navigator can be used to show a dialog by positioning the dialog
widget above the current page.
This is from the doc https://api.flutter.dev/flutter/widgets/Navigator-class.html your new page 'B' is just above the previous page 'A' which was never destroyed. And hot reload is causing the build method of page 'A' run again and you get 'A' printed.

flutter - android back button does not call onWillPop of WillPopScope

I would like to exit my application. I have implemented a WillPopScope, but it looks like the onWillPop function is not being called at all. I tried many things like swap WillPopScope with Scaffold, changing the return value of the function, but it just looks like it is not working as expected.
My code:
Future<bool> _willPopCallback() async {
exit(0);
// await showDialog or Show add banners or whatever
// then
return true; // return true if the route to be popped
}
return Scaffold(
appBar: MyAppBar(
leading: DrawerAction(),
title: Text(AppLocalizations.of(context).textCapitalized('home_page')),
onSearch: (searchTerms) => this.search(searchTerms, context),
),
body: new WillPopScope(
onWillPop: _willPopCallback, // Empty Function.
child: //my screen widgets
I am not sure if this is a bug I should report to flutter or I am doing something wrong. Happy to provide more code on request.
I have tried:
exit(0);
Navigator.of(context).pop();
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
Thanks in advance!
I managed to solve my problem and I think it is a very particular case, but it still might be helpful to someone.
TL;DR: Ensure that you dont have multiple Scaffolds in your widgets
I was using IndexedStack in my menu navigator, obviously wrapped with a Scaffold. The pages of the stack had Scaffold as well, and with this combination WillPopScope was not working neither in the navigator page neither in its stack pages. I solved by removing all the Scaffolds in the stack pages and having only one in the controller. In this way I managed to use WillPopScope correctly.
First of all do not ever use exit(0). It may be fine in Android environment, but apple won't allow the app on app store if it programmatically shuts down itself.
Here in the docs of onWillPop it clearly mentions that function should resolves to a boolean value.
Future<bool> _willPopCallback() async {
// await showDialog or Show add banners or whatever
// then
return Future.value(true);
}
This only works if your current page is the root of navigation stack.
Modify the code to return WillPopScope, and have Scaffold as a child.
return new WillPopScope(
onWillPop: _willPopCallback,
child: new Scaffold(
//then the rest of your code...
i know i am too late, but the problem still exists.
maybe i found the right solution.
make sure you are passing MaterialApp to the runApp method like this:
runApp(MaterialApp(home: MyFirstPage()));
this works for me for all my application's widgets. if you do not want to use it just wrap your widget in MaterialApp but do not forget that in every MaterialApp instance a new Navigator is created, so for me i just created one as above and in all my pages i just used scaffold and everything is ok.
I also stuck in the same problem but after a lot of searching, I found that this error is related to my parent container.
return WillPopScope(
onWillPop: () => _onWillPop(),
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
...
],
),
Another possible reason: my implementation of _onWillPop() was throwing an exception and the code inside _onWillPop() was ignored. The exception did not appear in the log.
I resolved it by using a TRY/CATCH inside _onWillPop(), and handling all code paths.
I have been battling this and initially thought it had something to do with the nested Scaffold widgets as the OP had mentioned in their answer above. I tested this though and still had the same problem. The answer for me was that my root Scaffold was a child of a Navigator. It worked as soon as I removed the Scaffold as a child of the Navigator. Thankfully I didn't need a Navigator at the root level anyway as I was using an IndexedStack which has multiple Navigator widgets in it.
This is a late answer but I hope can helps someone.
The #Gicminos answer was right. If you have nested scaffold willPopScope not worked.
I wanna add some info in case you need.
I have a Scaffold containing bottomNavBar. Every Item in bottomNav is a Navigator which children are Scaffold (you notice that in this moment there are scaffolds innested).
This is my MainScaffold containing the bottom bar:
...
_navigatorKeys = {
TabItem.tabOne: tabOneKey,
TabItem.tabTwo: GlobalKey<NavigatorState>(),
};
...
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
//check if there are pages in stack so it can pop;
var navigatorState =(_navigatorKeys.values.toList([_selectedIndex]as GlobalKey<NavigatorState>).currentState;
if ( navigatorState !=null) {
if (!await navigatorState
.maybePop()) return true;
}
return false;
},
child: Scaffold(
body: SafeArea(
child: IndexedStack(
index: _selectedIndex,
children: _pages,
),
),
resizeToAvoidBottomInset: true,
bottomNavigationBar: Container(...)
...
}
If you wrap with a WillPopScope widget also your children like in the code below:
#override
Widget build(BuildContext context) {
var t = AppLocalizations.of(context)!;
return WillPopScope(
onWillPop: () async {
debugPrint("test");
return true;
},
child: Scaffold(...)
}
both onWillPop will be called (in main scaffold and children scaffold).
In the example the first one will pop only if can (there are page in navigator stack), the second one will be called immediatly after the first one and it will call the debugPrint function before returned
I my case onWillPop didn't call because I had a custom AppBar and tried to call Navigator.pop(context) instead of Navigator.maybePop(context).

Removing the default back arrow on certain pages

From my understanding that a back arrow appears after navigating to any page by default. is there a way not to include the back arrow on a certain page of my choice ?
To hide the back button, set the automaticallyImplyLeading property of the Appbar as false
new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading: false,
)
)
Duplicate of https://stackoverflow.com/a/44978595/3013538
I believe the solutions are the following
You actually either:
Don't want to display that ugly back button ( :] ), and thus go for :
AppBar(...,automaticallyImplyLeading: false,...);
Don't want the user to go back - replacing current view - and thus go for:
Navigator.pushReplacementNamed(## your routename here ##);
Don't want the user to go back - replacing a certain view back in the stack - and thus use:
Navigator.pushNamedAndRemoveUntil(## your routename here ##, f(Route<dynamic>)→bool);
where f is a function returning truewhen meeting the last view you want to keep in the stack (right before the new one);
Don't want the user to go back - EVER - emptying completely the navigator stack with:
Navigator.pushNamedAndRemoveUntil(## your routename here ##, (_) => false);
Cheers
According to AppBar docs
If the leading widget is omitted, ... Otherwise, if the nearest Navigator has any previous routes, a BackButton is inserted instead.
So you can hide it in such way
new Scaffold(
appBar: new AppBar(
title: new Text("Without back button"),
leading: new Container(),
),
);
Set automaticallyImplyLeading to false in AppBar or assign empty container in leading
Using automaticallyImplyLeading
appBar: new AppBar(
title: new Text("Your Text Here"),
automaticallyImplyLeading: false,
),
Using empty Container
appBar: new AppBar(
title: new Text("Your Text Here"),
leading: new Container(),
),
In case you want to remove the back button in a FloatingSearchBar, use
automaticallyImplyBackButton: false