I do not know English well, so I will hope for a translator)
P.S I am new to the world of flutter and dart, I am a .net and angular developer.
In my application, I refuse to use StatefullWidget in favor of Flutter_Bloc, so if I need to change the state in the stream, then it will be more like a StreamBuilder.
I ran into a problem: I have a widget that uses TextField or TextFormField, it doesn’t matter to me what to use, at the moment I have functionality in the first place, not design, as the deadlines are burning (for the university). I want to initialize the data from the Bloc that gets there from the BlocProvider after the event that receives the data from my backend application is fired. If you use a TextFormField, it has an initialValue property, if you write state.Title there, then I see the following picture through the debugger: initially the state has what I wrote for the block in Super, and there, in fact, everything is null, but after literally a moment (some then the number of milliseconds) its state is updated after the initialization event: bloc..add(event), but an empty value got into initialValue and is not redrawn, I don’t know how best to make this behavior for me so that the value is initialized in the text field and subsequently changed together with state update.
In my application, I did a thing that I don’t like – I set up a variable and inside the StreamBuilder through the TextEditingController I change the text inside the text field, and when I click on the save button, I send this value, but I want it to interact with the state and update every time. I’m attaching an example of the code where I’m facing the problem.
ParentWidget
BlocProvider(
create: (context) => PlanStageDetailBloc()..add(PlanStageDetailInitialEvent(planId, id)),
child: BlocBuilder<PlanStageDetailBloc, PlanStageDetailInitialState>(
builder: (context, state) {
final bloc = context.read<PlanStageDetailBloc>();
return RefreshIndicator(
onRefresh: () async => bloc.add(PlanStageDetailInitialEvent(planId, id)),
child: Scaffold(
appBar: AppBar(
backgroundColor: ConstantColors.mainMenuBtn,
title: const Text('Test text'),
body: const PlanStageDetailScreen(),
ChildWidget
class PlanStageDetailScreen extends StatelessWidget {
const PlanStageDetailScreen({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Flexible(
child: FractionallySizedBox(
widthFactor: 0.9,
child: Padding(
padding: EdgeInsets.only(bottom: 10, left: 40),
child: _PlanDropdownButton(),
))),
const Flexible(
child: FractionallySizedBox(
widthFactor: 0.9,
child: Padding(padding: EdgeInsets.only(bottom: 20, left: 40), child: _TitleTextField())))
TitleTextFieldWidget
class _TitleTextField extends StatelessWidget {
const _TitleTextField();
@override
Widget build(BuildContext context) {
return BlocBuilder<PlanStageDetailBloc, PlanStageDetailInitialState>(
builder: (context, state) {
return TextFormField(
obscureText: false,
initialValue: state.title,
keyboardType: TextInputType.multiline,
onChanged: (text) => context.read<PlanStageDetailBloc>().add(ChangeTitle(text)),
onFieldSubmitted: (value) => context.read<PlanStageDetailBloc>().add(ChangeTitle(value)),
decoration:
TextFormFieldStyle.textFieldStyle(labelTextStr: 'Title*', hintTextStr: 'Enter title'));
},
);
}
}
Partial PlanStageDetailBloc.dart
class PlanStageDetailBloc extends Bloc<PlanStageDetailEvent, PlanStageDetailInitialState> {
PlanStageDetailBloc() : super(PlanStageDetailInitialState._(id: 0)) {
on<PlanStageDetailInitialEvent>(_onInit);
on<ChangeDate>(_onChangeDate);
on<ChangeTitle>(_onChangeTitle);
on<ChangeData>(_onChangeData);
on<ChangeStatus>(_onChangeStatus);
on<ChangePriority>(_onChangePriority);
on<ChangeParentPlan>(_onChangeParentPlan);
}
I accept constructive criticism, as well as good suggestions for improving my code), but most importantly I want to see options for solving this problem
I expect that after initializing the widget, my Bloc will receive all the necessary data and display them in my fields if the data corresponding to the field exists, in order to further correct or change them along with the Bloc state change, and send the changed data to my backend so that my data has been updated in the database.
2
Answers
The problem is that the TextFormField is trying to get its initial value from the Bloc state, but the Bloc state is not yet initialized when the TextFormField is created. To fix this, you can use the initState() method of the State class to initialize the Bloc state before the TextFormField is created.
Here is an example of how you can do this:
The desired behavior can be achieved by directly accessing the public state of the
TextFormField
and updating it in response to the Bloc event. We can update it in response to a Bloc event by using aBlocListener
instead of aBlocBuilder
.Step by step:
InitializeApi
event to trigger some asynchronous work.Code:
TextFormField
will respond to. This includes an uninitialized stateApiPendingInitialization
(not necessary, only included for clarity), and aApiInitialized
state for when the asynchronous operation is completed.Code:
ApiBloc
which is configured for our previous classes. It defaults toApiPendingInitialization
until theInitializeApi
event is added to the Bloc.Code:
Widget
to hook it up with theTextFormField
. This will simply be calledApiConsumer
.Code:
Code:
When you run this program, you will see that the
TextFormField
will update to the API response value "API Response Title" after the simulated asynchronous work inside_initializeApi
is complete.Note:
By using the
BlocListener
widget, theApiConsumer
widget itself never rebuilds. However, theTextFormField
does rebuild, because it is aStatefulWidget
and itsdidChange
method internally callssetState
.