I’m trying to build a Flutter app and I’m stuck on trying to dynamically update the quantity numbers of items in a list. I am reading locally from a json file which I then save to list in MyAppState. The list is then display to the user in gridview. Each item in the list has a plus and minus button which I can get to add/remove item from the users "collection" of items using onPressed(). The issue I’m currently facing is each item sums up the count of all items instead of displaying each items quantity independently. I’m hoping that I can get this app to show each value correctly. Any help would be greatly appreciated. Thank you!
Adding 6 of the first card
Adding 1 of the last card
This is the main error I’m receiving:
"Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<MyAppState?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<MyAppState?>
The widget which was currently being built when the offending call was made was:
MediaQuery)".
Here is my code:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Stash Box',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
var favorites = <WordPair>[];
var collection = <String>[];
void collectionAdd(String card){
collection.add(card);
notifyListeners();
}
int count(String card){
int count = 0;
for(var item in collection){
if(collection.contains(card)){
count++;
}
}
//notifyListeners();
return count;
}
void collectionRemove(String card){
if(collection.contains(card)){
collection.remove(card);
}
//collection.add(card);
notifyListeners();
}
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
class SpiritsCollectionPage extends StatefulWidget {
const SpiritsCollectionPage({super.key});
@override
__SpiritsCollectionState createState() => __SpiritsCollectionState();
}
class __SpiritsCollectionState extends State<SpiritsCollectionPage> {
List _cards = [];
Future<void> readJson() async {
final String response = await rootBundle.loadString('assets/spirits2.json');
final data = await json.decode(response);
//int len = data.length;
//_cards[len];
setState(() {
_cards = data['cards'];
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await readJson();
});
}
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
// Navigate back to the previous screen by popping the current route
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (_) => CollectionsPage(),
));
},
),
centerTitle: true,
title: const Text(
"High Spirits Collection",
),
),
body: GridView.builder(
itemCount: _cards.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 150,
child: FullScreenWidget(
disposeLevel: DisposeLevel.Medium,
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(2), // Image border
child: SizedBox.fromSize(
size: Size.square(135), // Image radius
child: Image.asset(_cards[index]["photo"]),
),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.red)),
child: const Text(
'-1',
style: TextStyle(
color: Colors.white,
//decoration: TextDecoration.underline,
decorationColor: Color.fromARGB(255, 0, 0, 0),
decorationStyle: TextDecorationStyle.wavy,
),
),
onPressed: () {
appState.collectionRemove(_cards[index].toString());
},
),
SizedBox(
width: 50,
child: Center(child: Text('${appState.count(_cards[index].toString())}'))),
TextButton(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.blue)),
child: const Text(
'+1',
style: TextStyle(
color: Color.fromARGB(255, 255, 255, 255),
decoration: TextDecoration.underline,
decorationColor: Colors.blue,
decorationStyle: TextDecorationStyle.wavy,
),
),
onPressed: () {
appState.collectionAdd(_cards[index].toString());
},
),
],
),
),
],
),
),
);
},
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:2,
mainAxisSpacing: 16,
),
//padding: EdgeInsets.all(16),
scrollDirection: Axis.vertical,
// onTap:
),
);
}
}
2
Answers
try using
FutureBuilder
to load the readJson instead of the InitState.Remove:
Wrap your widget with this FutureBuilder:
Update:
Try calling the SetState outside the async method.
To resolve this issue, you should avoid calling setState() or markNeedsBuild() during the build process. Instead, use the initState() method or other appropriate lifecycle methods to initialize and update your widgets.
1-Check your build() method for any direct or indirect calls to setState() or markNeedsBuild().
2-If you find any, move them to an appropriate lifecycle method like initState(), didUpdateWidget(), or didChangeDependencies().
3-Make sure you’re not creating an infinite loop of rebuilds by updating the state in response to a rebuild.
By following these steps and avoiding unnecessary calls to setState() or markNeedsBuild() during the build process, you should be able to resolve this issue