Jaime Albornoz Soto
Jaime Albornoz Soto

Reputation: 23

How can I rebuild a widget after the StreamBuilder gets built?

I'm trying to get the totalValue in the title of the appBar. The value of totalValue comes from the Streambuilder and it changes everytime I press the FilterChips.

Code:

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:calc_tag/services/get_items_for_month.dart';
import 'package:flutter/scheduler.dart';

class FilterChipExample extends StatefulWidget {
  final int year;
  final int month;
  FilterChipExample({required this.year, required this.month});
  @override
  _FilterChipExampleState createState() => _FilterChipExampleState();
}

class _FilterChipExampleState extends State<FilterChipExample> {
  final List<String> categories = [
    'Costanera Norte',
    'Vespucio Norte',
    'Vespucio Sur',
    'Autopista Central',
  ];
  num totalValue = 0;
  String titulo = '';

  List<String> selectedCategories = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$totalValue'),
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(8.0),
            margin: const EdgeInsets.all(8.0),
            child: Wrap(
              children: categories.map((category) {
                return FilterChip(
                    selectedColor: Colors.grey,
                    showCheckmark: false,
                    visualDensity:
                        const VisualDensity(vertical: -4, horizontal: -4),
                    selected: selectedCategories.contains(category),
                    label: Container(
                      margin: const EdgeInsets.all(0.0),
                      child: Text(category),
                    ),
                    onSelected: (selected) {
                      setState(() {
                        if (selected) {
                          selectedCategories.add(category);
                        } else {
                          selectedCategories.remove(category);
                        }
                      });
                    });
              }).toList(),
            ),
          ),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: getItemsForMonth(widget.year, widget.month),
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Error: ${snapshot.error}'));
                }
                if (!snapshot.hasData) {
                  return const Center(child: CircularProgressIndicator());
                }

                final documents = snapshot.data!.docs;
                final filteredDocuments = documents.where((element) {
                  return selectedCategories.contains(element['contract']);
                }).toList();

                // setState(() {
                totalValue = filteredDocuments.fold(
                    0.0, (num sum, doc) => sum + (doc['cost'] ?? 0.0));
                  
                // });

                return ListView.builder(
                  itemCount: filteredDocuments.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(
                          '${filteredDocuments[index]['contract']} ${(filteredDocuments[index]['created'] as Timestamp).toDate()}'),
                      subtitle: Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Text(
                              '\$ ${filteredDocuments[index]['cost'].toString()}'),
                          const SizedBox(
                            width: 30,
                          ),
                          Text(
                            ' ${filteredDocuments[index]['category'].toString()} ',
                            style: const TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 10),
                          ),
                        ],
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

I tried using setState(){} inside the StreamBuilder but I get this error:

FlutterError (setState() or markNeedsBuild() called during build. This FilterChipExample 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: FilterChipExample The widget which was currently being built when the offending call was made was: StreamBuilder<QuerySnapshot<Object?>>)

Upvotes: 0

Views: 46

Answers (2)

Zhentao
Zhentao

Reputation: 801

when you encountering errors like

setState() or markNeedsBuild() called during build

try to use WidgetsBinding.instance.addPostFrameCallback to delay updating your widgets

    WidgetsBinding.instance.addPostFrameCallback((_) {
      setState(() {
      totalValue = filteredDocuments.fold(
          0.0, (num sum, doc) => sum + (doc['cost'] ?? 0.0));
      });
    });

Upvotes: 1

Franklin Diaz
Franklin Diaz

Reputation: 725

The easiest solution for this case is to up the StreamBuilder so that the appBar is inside it.

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var i = 1;
  StreamController<int> s = StreamController<int>();
  var title = "Title";

  @override
  void initState() {
    super.initState();
    () async {
      while (true) {
        await Future.delayed(const Duration(seconds: 2), () {
          s.add(i++);
        });
      }
    }();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: StreamBuilder<int>(
          stream: s.stream,
          builder: (context, snapshot) {
            return Scaffold(
              appBar: AppBar(
                title: Text('Stream value: ${snapshot.data}'),
              ),
              body: Center(
                child: Text('Stream value: ${snapshot.data}'),
              ),
            );
          }),
    );
  }
}

Upvotes: 1

Related Questions