Why does `const` modifier avoid StreamBuilder rebuild in Flutter? - 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()
-- ...

Related

Generic filter Flutter

Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...

How to make my tabbar synced with scroll when touched certain widget without package?

I have initialised my tab.
tabs = [
Tab(text: tr("detail")),
Tab(text: tr("store")),
Tab(text: tr("review")),
];
_tabController = TabController(
length: tabs.length,
vsync: this,
);
Then i have my widget wrap inside CustomScrollView
CustomScrollView(
controller: _scrollController,
slivers: [
SliverToBoxAdapter(child: Consumer<MyProvider>(
builder: (context, myProvider, child) {
return Column(
children: [
Widget1(),
Widget2(),
Widget3(),
Widget4(),
],
);
}))
]
I want to assign the tab index to certain widget, when scroll until the widget then my tab will switch too. I will need to manually assign an unique index to each widget and bind to my tab. When scrolling allow to scroll to widget not visible on screen also. How should i actually start to achieve that? thanks
Doing this without packages - simply copy the code you need from the package if you don't want dependency.
From what i understood you have 3 problems to solve
When scrolled to the bottom of page, switch the tab
There already is a great answer for the problem
Answer: https://stackoverflow.com/a/54539182/15106600
Do something when certain widget is displayed
There is the package for it to detect if widget is displayed ( remember it triggers when it starts being visible not the full height displayed)
Answer: https://pub.dev/packages/visibility_detector
Navigate to next tab
NavigatorController.animateTo()
Answer: https://api.flutter.dev/flutter/material/TabController-class.html

bloc eventController is closed unexpected

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.

Flutter Bloc , Bloc state , navigate?

what I’m facing now is after I implemented bloc following one of the tutorials, I'm stuck now in place where after I'm getting the response and the state is changed, I want to navigate to another widget
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(APP_TITLE),
),
body: buildBody(context));
}
}
BlocProvider<SignInBloc> buildBody(BuildContext context) {
return BlocProvider(
create: (_) => sl<SignInBloc>(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
if(state is Empty)
return MessageDisplay(message: 'Sign In please.',);
else if(state is Loaded)
return HomePage();
else
return MessageDisplay(message: 'Sign In please.',);
}
),
SignInControls(),
],
),
),
),
);
}
in state of loaded I want to navigate to another widget.
so how to achieve that, and what is the best way for it?
You can't use the navigator or change the state while the widget is being built (your case).
There're two ways
1. The old fashioned way
WidgetsBinding.instance.addPostFrameCallback((_){
// Your code goes here
});
2. Since you already implemented the BLOC library you have a more elegant way to achieve this by using BlocListener. you can learn more about it in the documentation
Hope i helped!
Navigation can be used like Inherited widgets:
Navigator nav = Navigator.of(this.context);
then you can use somthing like:
nav.push(MaterialPageRoute(builder: (context) => YourSecondPage()))
in flutter, you can't just move to some page directly. you should use a route.
I think the cleanest way to use named routes. this is an example:
// here you put a class of names to use later in all of your project.
class RouteNames{
static String homepage = "/";
static String otherPage= "/otherpage";
}
// in your main file , MyApp class
var routes = {
RouteNames.homepage: (context)=> new MyHomePage(),
RouteNames.otherPage: (context)=> new MyOtherPage()
};
// then use routes variable in your MaterialApp constructor
// and later on in your project you can use this syntax:
Navigator.of(context).pushNamed(RouteNames.otherPage);
I think this way is clean and it's centralized, it's good if you want to send arguments to routes.
To learn more about navigation: navigation official documentation is pretty good
A note about the Bloc builder & listener:
Since BlocBuilder is going to be called lots of times. it should only contain widgets and widgets only. if you put navigation code inside it, this code would be called multiple times.
As Ayham Orfali said You definitely should use BlocListener for that. Inside it you can listen to changes in state. here is an example
// some code
children: <Widget>[
BlocListener(
bloc: BlocProvider.of<SignInBloc>(context),
listener: (context, state) {
if(state is Loaded){
Navigator.of(context).pushNamed("some other page");
}
// else do nothing!
},
child:// just bloc builder which contains widgets only. ,
SignInControls(),
]
// some other code

Is that possible to cache widget to resue?

I am using getbody() method to get app body and the getbody() return a Widget If i change the variable ct count the the getbody() will return different widget and each widget is stroied in Listqueue<MyPage()> if i set variable ct value 1 it return widget from my list at position 1 and if i set variable ct 2 then it will return corresponding widget .
But the problem is in each widget i am doing an api call but when i reuse that widget it is again calling the API call and why this is calling after first time nd how to stop calling api when i am reusing the widget
ListQueue<Page> page1, page2, page3, page4;
_AppFramePageState() {
page1 = ListQueue();
page1.add(Page(widget: AppHomePage(), title: "Home Page"));
page2 = ListQueue();
page2.add(
Page(widget: TemplesListing(), title: "Temple listing"));
page3 = ListQueue();
page3.add(Page(widget: AppCommunities(), title: "Communities"));
page4 = ListQueue();
page4.add(Page(widget: AppVideos(), title: "Media"));
}
If user click on cart button from toolbar then i will add more value to page1 list and if user click on back button then i will remove last item from page1 list
Widget getBody() {
//This code is solved my 50% issue but renaming 50% is there. How to solve this issue?
return IndexedStack(
index: _selectedIndex,
children: <Widget>[
page1.last.widget,
page2.last.widget,
page3.last.widget,
page4.last.widget
],);<br>
//This is the code i used first time
switch (_selectedIndex) {
case 0:
return page1.last.widget;
case 1:
return page2.last.widget;
case 2:
return page3.last.widget;
case 3:
return page4.last.widget;
}
}
class Page{
Widget widget;
String title;
Page({this.title,this.widget});
}
Note: All widgets are StatefulWidget
In general, it's not possible to reuse the widgets.
You can manually reuse the cache widget by comparing old and new state of the widgets. Which is very lengthy and i don't think you should follow it.
There are many State management architecture patterns like Provider, BLOC, MOBX etc. to manage your app in a great way. They are used to improve your app performance and decrease widget re-renders, manage data flow across the whole app etc.
One more thing you can do to make your Stateful widgets more impactive by using the const keyword whenever possible.
like for following widget ,
Column(
children: <Widget>[
// Widget 1
Center(child: Text(dynamic_value),),
// Widget 2
Container(
child: const Center(
child: const Padding(
padding: const EdgeInsets.all(10),
child: Text("Hello"),
),
),
),
],
)
In above example for Widget 1, you can't use "const keyword" as it depends on "dynamic_value".
While for Widget 2, you can use the "const keyword" which will be useful if your build method gets called again then "Center", "Padding" & "Text" widgets will be not called again as they are declared as constant widgets.