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'),
),