I tried out the flutter_bloc package but I run into this error:

The following assertion was thrown building BlocBuilder<MyCubit, MyState>(dirty, dependencies: [_InheritedProviderScope<MyCubit?>], state: _BlocBuilderBaseState<MyCubit, MyState>#8c21a):
setState() or markNeedsBuild() called during build.

This Form 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: Form-[LabeledGlobalKey<FormState>#24535]
  state: FormState#05273
The widget which was currently being built when the offending call was made was: BlocBuilder<MyCubit, MyState>
  dirty
  dependencies: [_InheritedProviderScope<MyCubit?>]
  state: _BlocBuilderBaseState<MyCubit, MyState>#8c21a
The relevant error-causing widget was: 
  BlocBuilder<MyCubit, MyState> BlocBuilder:file:///E:/Projekte/untitled9/lib/main.dart:47:18

Here is my code:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'my_cubit.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider<MyCubit>(
      create: (context) => MyCubit(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Form(
        key: formKey,
        child: Center(
          child: BlocBuilder<MyCubit, MyState>(
            builder: (context, state) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  TextFormField(
                    initialValue: state.text,
                    onChanged: (value) {
                      if (!formKey.currentState!.validate()) {
                        return;
                      }

                      final newData = state.copyWith(text: value);
                      BlocProvider.of<MyCubit>(context).changeData(newData);
                    },
                    validator: (v) => v!.trim().isNotEmpty ? null : 'error',
                  ),
                  ElevatedButton(
                    onPressed: formKey.currentState == null || !formKey.currentState!.validate() ? null : () { // this causes the error
                      print('hi');
                    },
                    child: const Text('Press'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

I know that the error is caused the by the conditional onPressed property of my ElevatedButton. If I replace the line onPressed: formKey.currentState == null || !formKey.currentState!.validate() ? null : () { with the simple onPressed: () { everything works fine. But I want to disable or enable the button based on the current user input and if it is valid.

What is the default why to fix this? Should the BLoC handle the validation?


Solution 1: Younss AIT MOU

When you have the ...called during build error it usually means that your widget is not yet ready to consume its state. In this case, and if you really want to mingle with the state in the view without migrating it to the BLoC, you need to wrap your code in addPostFrameCallback

WidgetsBinding.instance.addPostFrameCallback(
   (_) => {
   // your onPressed logic
   },
);

doing so will insure that your widget has at least once before consuming its state.

Below is an example:

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final formKey = GlobalKey<FormState>();
  void Function()? myOnPress;
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      myOnPress =
          formKey.currentState == null || !formKey.currentState!.validate()
              ? null
              : () {
                  print('hi');
                };
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Form(
        key: formKey,
        child: Center(
          child: BlocBuilder<MyCubit, MyState>(
            builder: (context, state) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  TextFormField(
                    initialValue: state.text,
                    onChanged: (value) {
                      if (!formKey.currentState!.validate()) {
                        return;
                      }

                      final newData = state.copyWith(text: value);
                      BlocProvider.of<MyCubit>(context).changeData(newData);
                    },
                    validator: (v) => v!.trim().isNotEmpty ? null : 'error',
                  ),
                  ElevatedButton(
                    onPressed: myOnPress,
                    child: const Text('Press'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}


Solution 2: Yeasin Sheikh

The issue is happening from formKey.currentState!.validate(), this is trying to validate on build time.

onPressed: formKey.currentState == null ||
    !formKey.currentState!.validate()

Try to

class _MyHomePageState extends State<MyHomePage> {
  final formKey = GlobalKey<FormState>();
  bool isValid = false;

  late final TextEditingController controller = TextEditingController()
    ..addListener(() {
      isValid =
          formKey.currentState != null && formKey.currentState!.validate();
      setState(() {});
    });

and use

ElevatedButton(
  onPressed: isValid
      ? () {
          // this causes the error
          print('hi');
        }
      : null,
  child: const Text('Press'),
),