Flutter TextField\TextFormField 与 flutter_bloc 初始化值

问题描述 投票:0回答:3
我英文不太好,所以希望有翻译) P.S 我是 flutter 和 dart 世界的新手,我是一名 .net 和 Angular 开发人员。

在我的应用程序中,我拒绝使用 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 状态的变化进一步更正或更改它们,并发送将数据更改到我的后端,以便我的数据已在数据库中更新。

flutter dart textfield flutter-bloc flutter-state
3个回答
1
投票
我一直在寻找问题的解决方案,并与 ChatGPT 进行了交谈,这就是我发现的内容。这对我来说似乎是一个很好的解决方案,但我还没有在 Android 以外的系统上测试过它。不过,它在 Android 上运行得很好。


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: 'Введите наименование')))))
    

0
投票
可以通过直接访问

TextFormField

 的公共状态并更新它以响应 Bloc 事件来实现所需的行为。我们可以使用 
BlocListener
 而不是 
BlocBuilder
 来更新它以响应 Bloc 事件。

一步一步:

    定义 Bloc 事件。在这种情况下,我将定义一个
  1. 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(); }

    我们需要集团声明我们的
  1. 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; }

    一个简单的
  1. 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")); } }

    现在 Bloc 样板已完成,我们将创建
  1. 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


0
投票
我根据这个问题制作了这个小部件

TextField 和 BLoC - 值未更新或键盘已关闭

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], ),
    
© www.soinside.com 2019 - 2024. All rights reserved.