1

I'm building my first app, and for state management I'm using ValueChangeNotifier and Provider with the state pattern. But when I start my app, I get the following error:

Exception has occurred. FlutterError (setState() or markNeedsBuild() called during build. This _InheritedProviderScope<EvaluationStore?> 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<EvaluationStore?> The widget which was currently being built when the offending call was made was: Builder)

I don't know how to show my problem without showing my project's classes, so I apologize if this gets too long.

I created a model class.

class EvaluationModel {
  final String token;
  final DateTime creation;
  final String technicians;
  final String customer;
  final String responsible;
  final String compressor;
  final int horimeter;
  final int oilType;
  final int oil;
  final int oilFilter;
  final int airFilter;
  final int separatorFilter;
  final int revitalize;
  final int revitalization;
  final String? technicalAdvice;
  final bool uploaded;
  // continues with the basic methods of a data class...
}

So I created a service class that is responsible for the EvaluationModel methods, where I created a method to fill my list with data coming from a MySQL database.

class EvaluationService {
  Future<List<EvaluationModel>> fetchEvaluations(
      String creationStart,
      String creationEnd,
      String technicians,
      String customer,
      String compressor) async {
    List<EvaluationModel> evaluations = <EvaluationModel>[];
    EvaluationModel evaluation;
        final MySqlConnection conn = await Database.getDbConnection();
    final Results result = await conn.query(
        await rootBundle.loadString('lib/assets/evaluation_select.sql'),
        [creationStart, creationEnd, technicians, customer, compressor]);
    await conn.close();
    for (var row in result) {
      evaluation = EvaluationModel(
          token: row['token'],
          creation: row['creation'],
          technicians: row['technicians'],
          customer: row['customer'],
          responsible: row['responsible'],
          compressor: row['compressor'],
          horimeter: row['horimeter'],
          oilType: row['oiltype'],
          oil: row['oil'],
          oilFilter: row['oilfilter'],
          airFilter: row['airfilter'],
          separatorFilter: row['separatorfilter'],
          revitalize: row['revitalize'],
          revitalization: row['revitalization'],
          technicalAdvice: row['technicalAdvice'],
          uploaded: true);
      evaluations.add(evaluation);
    }
    return evaluations;
  }
}

Then I created the EvaluationState and EvaluationStore class to manage the state of my page.

abstract class EvaluationState {}

class InitialEvaluationState extends EvaluationState {}

class LoadingEvaluationState extends EvaluationState {}

class SuccessEvaluationState extends EvaluationState {
  final List<EvaluationModel> evaluations;
  SuccessEvaluationState(this.evaluations);
}

class ErrorEvaluationState extends EvaluationState {
  final String message;
  ErrorEvaluationState(this.message);
}
class EvaluationStore extends ValueNotifier<EvaluationState> {
  final EvaluationService service;

  EvaluationStore(this.service) : super(InitialEvaluationState());

  Future fetchEvaluations(String creationStart, String creationEnd,
      String technicians, String customer, String compressor) async {
    value = LoadingEvaluationState();
    try {
      final evaluations = await service.fetchEvaluations(
          creationStart, creationEnd, technicians, customer, compressor);

      value = SuccessEvaluationState(evaluations);
    } catch (e) {
      value = ErrorEvaluationState(e.toString());
    }
  }
}

So, to work with the Provider I did it like this in the MyApp class.

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider(create: (_) => EvaluationService()),
        ChangeNotifierProvider(
            create: (context) => EvaluationStore(context.read()))
      ],
      child: MaterialApp(
        title: 'Avaliação',
        theme: ThemeData(
          primarySwatch: Colors.deepOrange,
        ),
        home: const EvaluationsPage(),
      ),
    );
  }

And finally, on the page I'm treating it like this:

class EvaluationsPage extends StatefulWidget {
  const EvaluationsPage({Key? key}) : super(key: key);

  @override
  State<EvaluationsPage> createState() => _EvaluationsPageState();
}

class _EvaluationsPageState extends State<EvaluationsPage> {
  @override
  void initState() {
    super.initState();
    context
        .read<EvaluationStore>()
        .fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
  }

  @override
  Widget build(BuildContext context) {
    final store = context.watch<EvaluationStore>();
    final state = store.value;
    Widget? child;

    if (state is LoadingEvaluationState) {
      child = const Center(child: CircularProgressIndicator());
    }
    if (state is ErrorEvaluationState) {
      child = Center(child: Text(state.message));
    }
    if (state is SuccessEvaluationState) {
      child = ListView.builder(
          itemCount: state.evaluations.length,
          itemBuilder: (context, index) {
            return ListTile(title: Text(state.evaluations[index].customer));
          });
    }

    return Scaffold(
      appBar: AppBar(title: const Text('Avaliações')),
      body: child ?? Container(),
    );
  }
}

Note: If I remove the line "value = LoadingEvaluationState();" from the Evaluation Store class, the app runs normally.

If anyone can help me, I can even make the project available.

I'm a beginner, I'm totally stuck, I don't know what to try.

Galdino
  • 11
  • 2

1 Answers1

0

the error occured because while execute the initState method, you call rebuild .

  • simple solution:
 @override
 void initState() {
 super.initState();
 WidgetsBinding.instance.addPostFrameCallback((_){
   context
        .read<EvaluationStore>()
        .fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
  }
});
   
pmatatias
  • 3,491
  • 3
  • 10
  • 30
  • It worked, thank you very much. So the method passed to addPostFrameCallback only runs after all widgets are completely built, right? – Galdino Jan 27 '23 at 11:39