在我的应用程序中,我拒绝使用 StatefullWidget 而是使用 Flutter_Bloc,因此如果我需要更改流中的状态,那么它将更像是 StreamBuilder。 我遇到了一个问题:我有一个使用 TextField 或 TextFormField 的小部件,对我来说使用什么并不重要,目前我首先拥有的是功能,而不是设计,因为截止日期正在紧迫(对于大学来说) )。我想在从后端应用程序接收数据的事件被触发后,初始化从 BlocProvider 获取的 Bloc 数据。如果你使用TextFormField,它有一个initialValue属性,如果你在那里写state.Title,那么我通过调试器看到下图:最初状态有我在Super中为块写的内容,事实上,一切都在其中是空的,但是过了一会儿(然后是毫秒数),它的状态在初始化事件之后更新:bloc..add(event),但是一个空值进入了initialValue并且没有重新绘制,我不知道如何最好地为我实现这种行为,以便该值在文本字段中初始化,并随后与状态更新一起更改。 在我的应用程序中,我做了一件我不喜欢的事情 - 我设置了一个变量,并通过 TextEditingController 在 StreamBuilder 内更改了文本字段内的文本,当我单击保存按钮时,我发送此值,但我希望它能够与状态交互并每次更新。我附上了我遇到问题的代码示例。
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'));
},
);
}
}
部分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);
}
我接受建设性的批评,以及改进我的代码的好建议),但最重要的是我想看到解决这个问题的选项我希望在初始化小部件后,我的 Bloc 将接收所有必要的数据,如果字段对应的数据存在,则将它们显示在我的字段中,以便随着 Bloc 状态的变化进一步更正或更改它们,并发送将数据更改到我的后端,以便我的数据已在数据库中更新。
PlanStageDetail.dart
class PlanStageDetailWidget extends StatelessWidget {
const PlanStageDetailWidget({super.key, required this.planId, required this.title, required this.id});
final num planId;
final num id;
final String title;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => PlanStageDetailBloc()..add(PlanStageDetailInitialEvent(planId, id)),
child: BlocBuilder<PlanStageDetailBloc, PlanStageDetailInitialState>(
builder: (context, state) {
final bloc = context.read<PlanStageDetailBloc>();
TextEditingController titleController = TextEditingController(text: state.title);
titleController.value = titleController.value.copyWith(
text: bloc.state.title,
selection: TextSelection.fromPosition(TextPosition(offset: bloc.state.title.length)));
TextEditingController dataController = TextEditingController(text: state.data);
dataController.value = dataController.value.copyWith(
text: bloc.state.data,
selection: TextSelection.fromPosition(TextPosition(offset: bloc.state.data.length)));
return RefreshIndicator(
onRefresh: () async => bloc.add(PlanStageDetailInitialEvent(planId, id)),
child: Scaffold(
appBar: AppBar(
backgroundColor: ConstantColors.mainMenuBtn,
title: Row(children: [
Padding(
padding: const EdgeInsets.only(right: 15),
child: IconButton(
onPressed: () => context
.read<HomeAuthNavCubit>()
.changePageRoute(PlanStagesList(planId: planId), 'Планы', 4),
icon: const Icon(Icons.arrow_back_ios_rounded)),
),
Text(title)
]),
),
body: Center(
child: SingleChildScrollView(
child: IntrinsicHeight(
child: Column(
children: <Widget>[
const Flexible(
child: FractionallySizedBox(
widthFactor: 0.9,
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: _PlanDropdownButton(),
))),
Flexible(
child: FractionallySizedBox(
widthFactor: 0.9,
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: TextFormField(
controller: titleController,
obscureText: false,
keyboardType: TextInputType.multiline,
onChanged: (text) {
bloc.add(ChangeTitle(text));
},
decoration: TextFormFieldStyle.textFieldStyle(
labelTextStr: 'Наименование *', hintTextStr: 'Введите наименование')))))
TextFormField
的公共状态并更新它以响应 Bloc 事件来实现所需的行为。我们可以使用
BlocListener
而不是
BlocBuilder
来更新它以响应 Bloc 事件。一步一步:
InitializeApi
事件来触发一些异步工作。
/// Base class for [ApiBloc] events.
abstract class ApiEvent {
const ApiEvent();
}
/// This event is used to trigger API initialization.
class InitializeApi extends ApiEvent {
const InitializeApi();
}
TextFormField
会做出回应。这包括未初始化状态
ApiPendingInitialization
(不是必需的,只是为了清楚起见而包含),以及异步操作完成时的
ApiInitialized
状态。
/// Base class for [ApiBloc] states.
abstract class ApiState {
const ApiState();
}
/// Intermediary state for unitialized [ApiBloc] instances.
class ApiPendingInitialization extends ApiState {
const ApiPendingInitialization();
}
/// State indicating that the [ApiBloc] is ready/initialized.
class ApiInitialized extends ApiState {
ApiInitialized(this.title);
final String title;
}
ApiBloc
,是为我们之前的课程配置的。它默认为
ApiPendingInitialization
,直到
InitializeApi
事件添加到 Bloc。
class ApiBloc extends Bloc<ApiEvent, ApiState> {
ApiBloc() : super(const ApiPendingInitialization()) {
on<InitializeApi>(_initializeApi);
}
/// Initialize the API. Simulate asynchronous work.
Future<void> _initializeApi(
InitializeApi event,
Emitter<ApiState> emit,
) async {
await Future.delayed(const Duration(seconds: 1));
emit(ApiInitialized("API Response Title"));
}
}
Widget
将其与
TextFormField
连接起来。这将简称为
ApiConsumer
。
class ApiConsumer extends StatelessWidget {
const ApiConsumer({super.key});
@override
Widget build(BuildContext context) {
/// This key will be used to access the TextFormField state.
///
/// The TextFormField state is of type FormFieldState<String>, so that is
/// the generic type of this GlobalKey.
final textFormFieldKey = GlobalKey<FormFieldState<String>>();
final textFormField = BlocListener<ApiBloc, ApiState>(
listener: (context, state) {
/// If the state is not ApiInitialized, then there is no data. Return.
if (state is! ApiInitialized) return;
/// The state must be ApiInitialized here, and ApiInitialized has data:
/// the title property.
textFormFieldKey.currentState?.didChange(state.title);
},
child: TextFormField(
key: textFormFieldKey, // Make sure to pass the key.
obscureText: false,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
label: Text('Title*'),
hintText: 'Enter title',
),
),
);
return MaterialApp(home: Scaffold(body: SafeArea(child: textFormField)));
}
}
runApp(
BlocProvider(
create: (_) => ApiBloc()..add(const InitializeApi()),
child: const ApiConsumer(),
),
);
当您运行此程序时,您会看到TextFormField
内部模拟的异步工作完成后,
_initializeApi
将更新为API响应值“API Response Title”。
注:
通过使用BlocListener
小部件,
ApiConsumer
小部件本身永远不会重建。然而,
TextFormField
确实会重建,因为它是一个
StatefulWidget
并且它的
didChange
方法在内部调用
setState
。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class BlocTextFormField<TBloc extends StateStreamable<TState>, TState> extends StatefulWidget {
const BlocTextFormField({
required this.selector,
this.initialValue,
this.onChanged,
this.decoration,
this.keyboardType,
this.inputFormatters,
super.key,
});
final String? initialValue;
final void Function(String)? onChanged;
final InputDecoration? decoration;
final String? Function(TState value) selector;
final TextInputType? keyboardType;
final List<TextInputFormatter>? inputFormatters;
@override
State<BlocTextFormField<TBloc, TState>> createState() => _BlocTextFormFieldState<TBloc, TState>();
}
class _BlocTextFormFieldState<TBloc extends StateStreamable<TState>, TState>
extends State<BlocTextFormField<TBloc, TState>> {
_BlocTextFormFieldState();
late TextEditingController _controller;
@override
Widget build(BuildContext context) {
return BlocListener<TBloc, TState>(
listener: (context, state) {
String? text = widget.selector(state);
if (text != null && text != _controller.text) {
_controller.text = text;
_controller.selection = TextSelection.collapsed(offset: (text).length);
}
},
child: TextFormField(
controller: _controller,
decoration: widget.decoration,
onTapOutside: (e) => FocusManager.instance.primaryFocus?.unfocus(),
keyboardType: widget.keyboardType,
inputFormatters: widget.inputFormatters,
),
);
}
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.initialValue);
_controller.addListener(_changed);
}
@override
void dispose() {
_controller.removeListener(_changed);
super.dispose();
}
void _changed() {
widget.onChanged?.call(_controller.text);
}
}
用途:
BlocTextFormField<OrderAddressDetailBloc, OrderAddressDetailState>(
selector: (state) => state.entranceNumber?.toString(),
initialValue: state.entranceNumber?.toString(),
onChanged: (entranceNumber) => context
.read<OrderAddressDetailBloc>()
.add(OrderAddressDetailEvent.entranceNumberChanged(entranceNumber)),
decoration: InputDecoration(
labelText: context.appTexts.order_entrance_number_title,
),
keyboardType: TextInputType.number,
inputFormatters: [LengthLimitingTextInputFormatter(3), FilteringTextInputFormatter.digitsOnly],
),