setState does not update GridView in flutter - flutter

I have read the other questions and while they do have similar titles to this one, they don't cover my issue. Please read below.
I'm using Android Studio. I have a simple stateful app with a GridView. Its children are in a list "geza". When I press the button a Text('Hello') is added to the list.
This is done inside the setState() method. The newly added widget will not appear in the grid.
If I do a hot-redeploy from Android studio, then after the redeploy the widget will show up. So it looks like the widget is added to the list, but the Grid is not updated.
What am I doing wrong?
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var geza = <Widget>[ Text('AAA') ];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Test'),
),
body: Center(
child: GridView.count(crossAxisCount: 2,
children: this.geza
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
this.geza.add(Text('Hello'));
});
},
child: Icon(Icons.add),
),
);
}
}

You could use spread operator (...) to solve this issue.
GridView.count(
crossAxisCount: 2,
children: [
...geza
],
),

I think Flutter treats list of widgets specially, as you can see in this similar case. This solution uses ListView.builder: in your case, you can use a GridView.builder, but you can also use strings instead of widgets inside your geza list to avoid any further problems:
class _MyHomePageState extends State<MyHomePage> {
// Use a list of strings
final List<String> geza = ['AAA'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Test'),
),
body: Center(
child: GridView.count(
crossAxisCount: 2,
// Convert to a list of widgets here
children: this.geza.map((v) => Text(v)).toList(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Add a string to the list
setState(() => this.geza.add('Hello'));
},
child: Icon(Icons.add),
),
);
}
}

Related

flutter: GlobalKey error when using Drawer

I`m new to flutter.
After login, the screen goes dark and the following error occurs when moving to the my HomePage. How can I solve this problem?
The following assertion was thrown while finalizing the widget tree:
Multiple widgets used the same GlobalKey.
The key [LabeledGlobalKey#c6bc9] was used by multiple widgets. The parents of those widgets were:
HomePage(state: _HomePageState#8437e)
HomePage(state: _HomePageState#f49a2)
A GlobalKey can only be specified on one widget at a time in the widget tree.
This is my HomePage
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String uid = FirebaseAuth.instance.currentUser!.uid;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _willPopCallback,
child: Scaffold(
key: _scaffoldKey,
endDrawer: Drawer(
child: ListView(
children: [
...
],
),
),
appBar: AppBar(
automaticallyImplyLeading: false,
actions: [
IconButton(
onPressed: () {
showProfileDialog(context, uid: uid);
},
icon: const Icon(Icons.account_circle, size: 30)),
IconButton(
onPressed: () {
_scaffoldKey.currentState!.openEndDrawer();
},
icon: const Icon(
Icons.dehaze_sharp,
size: 30,
),
),
const SizedBox(width: 10)
],
),
body: Center(
child:
...
),
),
),
);
}
}
Future<bool> _willPopCallback() async {
return true;
}
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
you can access to key
Scaffold.of(context).openDrawer();
and this will work without create GlobalKey
It means that the key still holds the state of the old widget. Try resetting the global key in dispose of HomePageState class.

Correctly managing Flutter pages with bottom navigation, and using state to trigger rebuild with setState()

My app is now being broken down into multiple pages (separate dart files) with a bottom Navigation bar. The first page is a map, and every time I go to a different page, I return and the map widget rebuilds itself and re-initialises (markers, map.center and map.zoom, etc).
So, I introduced an IndexedStack() which has the desired effect of keeping the pages in their original state. However, when I make some changes I'd like to trigger a rebuild with setState((){}), however the error I'm then getting is
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: setState() called in constructor: _PageMapState#52149(lifecycle state: created, no widget, not mounted)
So this leads me to think that the way I'm creating and displaying these pages is perhaps wrong, as I'm trying to setState() on something that doesn't yet exist (or a different version of it)
I'd like to see if I'm using the correct methods to manage separate pages in the app state and navigation. My current setup with the IndexedStack is like so:
Main App
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'My app'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget _widgetOptionsPages(int index) {
switch (index) {
case 0:
return PageMap();
break;
case 1:
return PageSearch();
break;
case 2:
return PageDownloads();
break;
case 3:
return PageSettings();
break;
case 4:
return PageAbout();
break;
}
return PageMap();
}
#override
Widget build(BuildContext context) {
return
Scaffold(
// appBar: AppBar(
// title: const Text('Title'),
// ),
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => btn1(),
child: const Text('BTN 1'),
),
ElevatedButton(
onPressed: () => btn2(),
child: const Text('BTN 2'),
),
ElevatedButton(
onPressed: () => btn3(),
child: const Text('BTN 3'),
),
],
),
),
bottomNavigationBar: SalomonBottomBar(
duration: Duration(milliseconds: 300),
itemPadding: EdgeInsets.all(15),
margin: EdgeInsets.fromLTRB(20,10,20,10),
currentIndex: _currentIndex,
onTap: (i) => setState(() => _currentIndex = i),
items: [
/// Nav Item to Map
SalomonBottomBarItem(
icon: Icon(Icons.map_sharp),
title: Text("Map"),
selectedColor: Colors.purple,
),
/// Nav Item to About
SalomonBottomBarItem(
icon: Icon(Icons.help),
title: Text("About"),
selectedColor: Colors.deepOrangeAccent,
),
],
),
body: _widgetOptionsPages(_currentIndex),
);
}
About Page (Example About page fragment. Map is the first page, and similar)
class PageAbout extends StatelessWidget {
const PageAbout({Key? key}) : super(key: key);
#override
Widget build (BuildContext context){
return Scaffold(
appBar: AppBar(
title: const Text('About'),
), body: Center(child: Text("About")));
}
}
I started looking at the following methods in MainApp, but they kept forcing the map to rebuild and reposition back to the starting pos (mentioned earlier)
static final List<Widget> _pagesFragments = [
PageMap(),
...
PageAbout(),
];
and
body: IndexedStack(
index: _currentIndex,
children: _pagesFragments,
),
),
Many thanks

Widget not updating in flutter?

This method is not updating the widget
Widget displayAppropriateWidget() {
if (isParsedCorrectly) {
_widgetToDisplay = displaySecondHalf;
} else {
_widgetToDisplay = displayFirstHalf;
}
setState(() {});
return _widgetToDisplay;
}
Even when the bool in other file and this file updates widget doesn't update. I can only see the changes only after I hot reload
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const PreferredSize(
preferredSize: Size.fromHeight(100),
child: CustomAppBar("Sign-Up"),
),
body: Align(
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 100, bottom: 5),
child: Image.asset(
'assets/images/college.png',
width: 200,
fit: BoxFit.cover,
),
),
displayAppropriateWidget(),
],
),
),
);
}
}
You need to listen to the changes of that bool by injecting it into the Widget in some way. For example by:
Passing it into the widget as an argument - which you can do if the bool is declared in a Widget higher up in the widget tree. Then you can call setState in that parent Widget once you've updated the bool. But setState can only be used in StatefulWidget. More on using setState here: https://docs.flutter.dev/development/ui/interactive and here: https://api.flutter.dev/flutter/widgets/State/setState.html
Or by using some sort of state management. Here's a tutorial for that: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple
Also, your function displayAppropriateWidget calls setState, which you should not do from inside a Widgets build method - since that would mean rebuilding every time you build, causing infinite rebuilds.
Another tip is to try to avoid building Widgets from functions, as it's not as performant and can lead to some hard to identify bugs: https://www.youtube.com/watch?v=IOyq-eTRhvo
Please refer to below example code of updating value from another class
ValueListenableBuilder widget. It is an amazing widget. It builds the widget every time the valueListenable value changes. Its values remain synced with there listeners i.e. whenever the values change the ValueListenable listen to it. It updates the UI without using setState() or any other state management technique.
In Dart, a ValueNotifier is a special type of class that extends a ChangeNotifer . ... It can be an int , a String , a bool or your own data type. Using a ValueNotifier improves the performance of Flutter app as it can help to reduce the number times a widget gets rebuilt.
ValueListenableBuilder will listen for changes to a value notifier and automatically rebuild its children when the value changes.
ValueNotifer & ValueListenableBuilder can be used to hold value and update widget by notifying its listeners and reducing number of times widget tree getting rebuilt.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
final ValueNotifier<int> counter = ValueNotifier(0);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: FloatingActionButtonClass(),
);
}
}
class FloatingActionButtonClass extends StatelessWidget {
void _incrementCounter() {
counter.value++;
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
body: Center(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyHomePage()),
);
},
child: Text("Floating Action Button"),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({
Key? key,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Example"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
ValueListenableBuilder(
valueListenable: counter,
builder: (context, value, child) {
return Text(
counter.value.toString(),
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
);
}
}

flutter build only a single widget

I have two widgets in a column. One is Text and second is TextButton. What i want that if i click on button then the Text widget rebuild only not the whole page.
I am new to flutter how can i achieve this? If i convert this to a statful widget and call setState method then whole page will be rebuild. but i want to know any trick to do rebuild only a single widget out of whole page.
class Page3 extends StatelessWidget {
Color color = Colors.red;
changeColor() {
// do something to rebuild only 1st column Text not the whole page
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: color),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
Please refer to below code
ValueListenableBuilder widget. It is an amazing widget. It builds the widget every time the valueListenable value changes. Its values remain synced with there listeners i.e. whenever the values change the ValueListenable listen to it. It updates the UI without using setState() or any other state management technique.
In Dart, a ValueNotifier is a special type of class that extends a ChangeNotifer . ... It can be an int , a String , a bool or your own data type. Using a ValueNotifier improves the performance of Flutter app as it can help to reduce the number times a widget gets rebuilt.
ValueListenableBuilder will listen for changes to a value notifier and automatically rebuild its children when the value changes.
For more info refer to this link description
Solution 1
class Page3 extends StatelessWidget {
Color color = Colors.red;
final ValueNotifier<bool> updateColor = ValueNotifier(false);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
color = changedColor;
updateColor.value = !updateColor.value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<bool>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: color),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Solution 2
In ValueListenable we pass our created ValueNotifier variable whose changes will be notified and in builder we will return a widget that will be reflected every time when the value of ValueNotifier will be changed.
class Page3 extends StatelessWidget {
// Color color = Colors.red;
final ValueNotifier<Color> updateColor = ValueNotifier(Colors.red);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
updateColor.value = changedColor;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<Color>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: val),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Here's the code of what you need to do
class Demo extends StatefulWidget {
const Demo({Key? key}) : super(key: key);
#override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
var isTextChanged = false;
Void changeColor() {
setState(() {
isTextChanged = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: isTextChanged ? Colors.red : Colors.black),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
setStatefunction can not be called inside StatelessWidget widget. if you want to rebuild the widget tree, you have to convert it to StatefulWidget.
This is what you can do.
class Page3 extends StatefulWidget {
const Page3();
#override
_Page3State createState() => _Page3State();
}
class _Page3State extends State<Page3> {
Color color = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
Text(
'Title',
style: TextStyle(color: color),
),
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
]),
);
}
changeColor() {
setState(() {
color = Colors.green;
});
}
}
If you want to rebuild the Text widget without rebuilding the whole Page3 then you need to got for 'state management' solution.
Try below code hope its help to you. you must used StateFulWidget for that
Create one bool variable
bool isButtonPressed = true;
Your widgets:
Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Title',
style: isButtonPressed
? TextStyle(
color: Colors.black,
fontSize: 20,
)
: TextStyle(
color: Colors.green,
fontSize: 20,
),
),
),
TextButton(
child: new Text('Change color'),
onPressed: () {
setState(() {
isButtonPressed = !isButtonPressed;
});
},
),
],
),
Your Screen without button pressed:
Your Screen with button pressed:
You need to understand how setState works.
Lets assume you have a class named HomeScreen, within the homescreen you are overriding the build method to build your own widgets.
Widget build(BuildContext context){
return Column(
children:<Widget> [
FirstTextWidget();
SecondTextWidget();
ThirdTextWidget(),
])
}
when you call SetState function within that "homesceeen" class, the homescreen class itself calls the build method again and all of componenets you have returned within build function get re-rendered. Every text within homescreen class gets rerendered.
So whats the solution?
The Solution is separate your stateful widget with different class so that only those widgets gets rerendered when needed not whole. I will prefer you to use State Management plugin like Provider, bloc etc.

flutter: no refresh indicator when using RefreshIndicator

I added the RefreshIndicator to my page, but there is no indicator visible when pull to refresh. The code is below:
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: LocalGalleryTab(),
);
}
}
class LocalGalleryTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _LocalGalleryState();
}
}
class _LocalGalleryState extends State<LocalGalleryTab> {
#override
Widget build(BuildContext context) {
return new Container(child: new Center(
child: new RefreshIndicator(
child: Text("Local Gallery"),
onRefresh: _refreshLocalGallery,
),
));
}
Future<Null> _refreshLocalGallery() async{
print('refreshing stocks...');
}
}
How to use the RefreshIndicator? The flutter doc does not give much infomation.
By design, RefreshIndicator works with ListView.
But if you want to use RefreshIndicator with non-scrollable-widgets, you can wrap your widget into Stack with ListView:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[ListView(), YOUR_CHILD_WIDGET],
),
),
Refresh Indicator by default does not work on a short list so add the following to the ListView.builder
physics: const AlwaysScrollableScrollPhysics(),
You need to add scroll child inside RefreshIndicator
see example below
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: LocalGalleryTab(),
);
}
}
class LocalGalleryTab extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _LocalGalleryState();
}
}
class _LocalGalleryState extends State<LocalGalleryTab> {
#override
Widget build(BuildContext context) {
return new Container(child: new Center(
child: new RefreshIndicator(
child: ListView(
children: List.generate(50, (f) => Text("Item $f")),
),
onRefresh: _refreshLocalGallery,
),
));
}
Future<Null> _refreshLocalGallery() async{
print('refreshing stocks...');
}
}
RefreshIndicator(
onRefresh: _onRefresh,
child:SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ListView(
padding: EdgeInsets.all(8.0),
children: _listData.map((i) {
return ListTile(
title: Text("Item $i"),
);
}).toList(),
)
)
);
Add physics in SingleChildScrollView
You can use ListView with SinglchildScrollView widget also.
RefreshIndicator(
onRefresh: () {
//HERE YOUR FUNCTION TO CALL
},
child: Stack(
children: <Widget>[
ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: []
)
],
),
),
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I think the best approach by far is to use CustomScrollView with RefreshIndicator. See example below.
class RefreshDemo extends StatelessWidget {
const RefreshDemo({super.key});
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
// Your refresh logic goes here
await Future.delayed(Duration(seconds: 2));
return true;
},
child: CustomScrollView(
slivers: [
// Wrap your widgets with the SliverToBoxAdapter
SliverToBoxAdapter(
child: Container(
child: Column(
children: [
Text('Hello'),
Text('World'),
]
)
),
),
],
),
);
}
}
I got the solution of this after little change from one answer which was already there in current page!
Just need to change position of stack children (main widget first then listview).
updated answer which will work 100%:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[YOUR_CHILD_WIDGET, ListView()],
),
),
past answer from above answers:
RefreshIndicator(
onRefresh: () {},
child: Stack(
children: <Widget>[ListView(), YOUR_CHILD_WIDGET],
),
),
The most important is to implement in ListView:
physics: const AlwaysScrollableScrollPhysics(),
because otherwise items which are not covering whole screen are stuck and you cannot move/refresh them.
as mentioned above refresh indicator only works with Listview so use it else wrap Listview with Stack