Flutter Bloc不会更改TextFormField initialValue

a8jjtwal  于 2023-04-07  发布在  Flutter
关注(0)|答案(4)|浏览(120)

我正在使用Bloc库,并注意到在产生新状态后,我的TextFormField initialValue没有改变。
我的应用程序比这更复杂,但我做了一个最小的例子。还跟踪它在推送事件后的状态变化。
Bloc应该正确地重建整个小部件。我错过了什么吗?

import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;

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

enum Event { first }

class ExampleBloc extends Bloc<Event, int> {
  ExampleBloc() : super(0);
  @override
  Stream<int> mapEventToState(Event event) async* {
    yield state + 1;
  }
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => ExampleBloc(),
        child: Builder(
          builder: (contex) => SafeArea(
            child: BlocConsumer<ExampleBloc, int>(
                listener: (context, state) {},
                builder: (context, int state) {
                  developer.log(state.toString());
                  return Scaffold(
                    body: Form(
                      child: Column(
                        children: [
                          TextFormField(
                            autocorrect: false,
                            initialValue: state.toString(),
                          ),
                          RaisedButton(
                            child: Text('Press'),
                            onPressed: () {
                              context.bloc<ExampleBloc>().add(Event.first);
                            },
                          )
                        ],
                      ),
                    ),
                  );
                }),
          ),
        ),
      ),
    );
  }
}

pubspec.yaml

name: form
description: A new Flutter project.
version: 1.0.0+1
environment:
  sdk: ">=2.7.0 <3.0.0"
dependencies:
  flutter:
    sdk: flutter
  bloc: ^6.0.0
  flutter_bloc: ^6.0.0

编辑

正如@chunhunghan所指出的,添加一个UniqueKey解决了这个问题。我还应该提到我的情况。应用程序从两个TextFormFieldonChanged方法发出事件。这会导致窗体重置并删除键盘。自动对焦不起作用,因为有两个TextFormField wgich发出事件。

anauzrmj

anauzrmj1#

你可以复制粘贴运行完整的代码1和2下面
您可以将UniqueKey()提供给ScaffoldTextFormField以强制重新创建
有关详细信息,请参阅https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d
如果元素的键与相应的Widget的键不匹配,则返回。这会导致Flutter停用这些元素并删除对元素树中元素的引用
解决方案1:

return Scaffold(
        key: UniqueKey(),
        body: Form(

解决方案二:

TextFormField(
               key: UniqueKey(),

工作演示

完整代码1 ScaffoldUniqueKey

import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;

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

enum Event { first }

class ExampleBloc extends Bloc<Event, int> {
  ExampleBloc() : super(0);
  @override
  Stream<int> mapEventToState(Event event) async* {
    yield state + 1;
  }
}

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

  @override
  Widget build(BuildContext context) {
    print("build");
    return MaterialApp(
      home: BlocProvider(
        create: (_) => ExampleBloc(),
        child: Builder(
          builder: (contex) => SafeArea(
            child: BlocConsumer<ExampleBloc, int>(
                listener: (context, state) {},
                builder: (context, int state) {
                  print("state ${state.toString()}");
                  developer.log(state.toString());
                  return Scaffold(
                    key: UniqueKey(),
                    body: Form(
                      child: Column(
                        children: [
                          TextFormField(
                            autocorrect: false,
                            initialValue: state.toString(),
                          ),
                          RaisedButton(
                            child: Text('Press'),
                            onPressed: () {
                              context.bloc<ExampleBloc>().add(Event.first);
                            },
                          )
                        ],
                      ),
                    ),
                  );
                }),
          ),
        ),
      ),
    );
  }
}

完整代码2 TextFormFieldUniqueKey

import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;

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

enum Event { first }

class ExampleBloc extends Bloc<Event, int> {
  ExampleBloc() : super(0);
  @override
  Stream<int> mapEventToState(Event event) async* {
    yield state + 1;
  }
}

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

  @override
  Widget build(BuildContext context) {
    print("build");
    return MaterialApp(
      home: BlocProvider(
        create: (_) => ExampleBloc(),
        child: Builder(
          builder: (contex) => SafeArea(
            child: BlocConsumer<ExampleBloc, int>(
                listener: (context, state) {},
                builder: (context, int state) {
                  print("state ${state.toString()}");
                  developer.log(state.toString());
                  return Scaffold(
                    body: Form(
                      child: Column(
                        children: [
                          TextFormField(
                            key: UniqueKey(),
                            autocorrect: false,
                            initialValue: state.toString(),
                          ),
                          RaisedButton(
                            child: Text('Press'),
                            onPressed: () {
                              context.bloc<ExampleBloc>().add(Event.first);
                            },
                          )
                        ],
                      ),
                    ),
                  );
                }),
          ),
        ),
      ),
    );
  }
}
q3aa0525

q3aa05252#

您不应该仅仅因为要更新TextFormField的值而重建整个Form,请尝试使用TextEditingController并更新侦听器上的值。

TextEditingController _controller = TextEditingController();
BlocProvider(
    create: (_) => ExampleBloc(),
    child: Builder(
      builder: (contex) => SafeArea(
        child: BlocListener<ExampleBloc, int>(
            listener: (context, state) {
                _controller.text = state.toString();
            },
            child: Scaffold(
                body: Form(
                  child: Column(
                    children: [
                      TextFormField(
                        controller: _controller,
                        autocorrect: false,
                      ),
                      RaisedButton(
                        child: Text('Press'),
                        onPressed: () {
                          context.bloc<ExampleBloc>().add(Event.first);
                        },
                      )
                    ],
                  ),
                ),
              );
            }),
bvjxkvbb

bvjxkvbb3#

我也遇到了同样的问题。在添加Unique Key的同时,flutter一直在构建小部件,每次我的键盘都没有焦点。我解决这个问题的方法是在TextField的onChanged事件中添加一个去抖动。

class InputTextWidget extends StatelessWidget {
final Function(String) onChanged;
Timer _debounce;

void _onSearchChanged(String value) {
    if (_debounce?.isActive ?? false) _debounce.cancel();
    _debounce = Timer(const Duration(milliseconds: 2000), () {
      onChanged(value);
    });
  }

@override
  Widget build(BuildContext context) {
         return TextFormField(
          controller: TextEditingController(text: value)
            ..selection = TextSelection.fromPosition(
              TextPosition(offset: value.length),
            ),
          onChanged: _onSearchChanged,
          onEditingComplete: onEditingCompleted,
        );
       }
     }

希望如果这对某人有帮助,与形式,块和工作也更新了形式。
编辑:虽然添加了一个反跳帮助显示什么。我已经改变了代码更健壮。这里是变化。
InputTextWidget(已更改)

class InputTextWidget extends StatelessWidget {
final Function(String) onChanged;
final TextEditingController controller;

void _onSearchChanged(String value) {
    if (_debounce?.isActive ?? false) _debounce.cancel();
    _debounce = Timer(const Duration(milliseconds: 2000), () {
      onChanged(value);
    });
  }

@override
  Widget build(BuildContext context) {
         return TextFormField(
          controller: controller,
          onChanged: _onSearchChanged,
          onEditingComplete: onEditingCompleted,
        );
       }
     }

在我的演讲结束时

class _NameField extends StatelessWidget {
  const _NameField({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final TextEditingController _controller = TextEditingController();
    return BlocConsumer<SomeBloc,
        SomeState>(
      listenWhen: (previous, current) =>
          previous.name != current.name,
      listener: (context, state) {
        final TextSelection previousSelection = _controller.selection;
        _controller.text = state.name;
        _controller.selection = previousSelection;
      },
      buildWhen: (previous, current) =>
          previous.name != current.name,
      builder: (context, state) => FormFieldDecoration(
        title: "Name",
        child: InputTextWidget(
          hintText: "AWS Certification",
          textInputType: TextInputType.name,
          controller: _controller,
          onChanged: (value) => context
              .read< SomeBloc >()
              .add(SomeEvent(
                  value)),
        ),
      ),
    );
  }
}

这个编辑工作得很好。
最终编辑:
我在我的块状态下添加了一个key? key,并将此键传递给小部件。如果我需要重新绘制表单,我将事件中的键更改为UniqueKey。这是迄今为止我实现块和表单的最简单方法。如果您需要解释,请在这里评论,我稍后会添加它。

nszi6y05

nszi6y054#

正确的做法:
1.创建一个承载表单的有状态小部件。这是确保TextEditingControllers不会在rebuild =〉时被释放/新创建的必要条件。它确保状态被保留。
1.为每个TextFormField创建一个TextEditingController并传递它
1.加载数据后,仅设置TextEditingController的值一次。
1.使用TextFormField方法(onChanged、onFieldSubmitted)更新Bloc的状态,以保持TextEditingController和您的状态同步。
1.如果你有一些后端生成的值或调整,请确保更新,只有这样,TextEditingControllers与值。例如,再次触发初始Loading-Flow,或切换状态。
请记住,理解自己的BLoc逻辑/流程以及TextEditingController如何工作是绝对关键的。如果您不断更新TextEditingController,光标将跳转,因为设置文本将触发setState,从而导致新的构建调用。处理此问题的方法是在更改后设置选择,但如果您搞砸了BLoc,请注意。你将再次在跳跃的光标和键盘中结束。我的建议是退后一步,在一张纸上画出流程来获得概述。否则你将继续与框架作战。
本示例中的BLoC具有以下状态:

Loading |> Ready |> Saving |> Saved |> Ready

在“保存”之后,我用更新的记录将我的状态更新回就绪状态,并且BlocConsumer的侦听器将再次被触发,然后将更新TextEditingController。

TL:DR传入一个TextEditingController,只对真正需要重建的部分使用BlocBuilder,不要使用任何密钥破解。
实现示例:

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

// Ensure that it is a stateful widget so it keeps the controller on rebuild / window resizes / layout changes!
class BodyAddEditForm extends StatefulWidget {
  const BodyAddEditForm({
    Key? key,
  }) : super(key: key);

  @override
  State<BodyAddEditForm> createState() => _BodyAddEditFormState();
}

class _BodyAddEditFormState extends State<BodyAddEditForm> {
  final _formKey = GlobalKey<FormState>();

  // ensure that the controllers are only created once to avoid jumping cursor positions
  final _weightEditingController = TextEditingController();
  final _restingPulseEditingController = TextEditingController();
  // [other controllers]

  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<BodyAddEditBloc>(context);

    return Form(
      key: _formKey,
      child: Column(
        children: [
          BlocConsumer<BodyAddEditBloc, BodyAddEditState>(
            // ensure that the value is only set to the TextEditingController the first time the bloc is ready, after some internal loading logic is handled.
            listenWhen: (previous, current) => !previous.isReady && current.isReady,
            listener: (context, state) {
              // there is a chance that the bloc is faster than the current build can finish rendering the frame.
              // if we now would update the text field this would result in another build cycle which triggers an assertion.
              WidgetsBinding.instance.addPostFrameCallback((_) {
                // set the initial value of the text field after the data becomes available
                // ensure that we do not set the text field to 'null'
                weightEditingController.text = state.record.weight != null ? state.record.weight!.toString() : '';
              });
            },
            builder: (context, state) {
              return TextFormField(
                controller: _weightEditingController, // provide a controller per each TextFormField to ensure that the text field is updated.
                keyboardType: TextInputType.numberWithOptions(decimal: true),
                inputFormatters: [
                  FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*(\.|,)?[0-9]*')),
                ],
                onChanged: (value) {
                  // example to update value on change to correct user input
                  // this is not necessary if you only want to update the value 
                  // but it showcases how to handle that without resulting the virtual keyboard nor the cursor to jump around
                  value = value.replaceAll(',', '.');

                  if (value.isEmpty) {
                    bloc.add(BodyAddEditWeightChanged(weight: null));
                    return;
                  }

                  if (double.tryParse(value) == null) {
                    bloc.add(BodyAddEditWeightChanged(weight: null));
                    return;
                  }

                  bloc.add(BodyAddEditWeightChanged(weight: double.parse(value)));
                },
              );
            },
          ),
          // [more fields]
          BlocBuilder<BodyAddEditBloc, BodyAddEditState>(
            builder: (context, state) {
              return ElevatedButton(
                onPressed: () {
                  if (!_formKey.currentState!.validate()) {
                    return;
                  }

                  bloc.add(BodyAddEditSaveRequested());
                },
                child: Text("Save"),
              );
            },
          ),
        ],
      ),
    );
  }
}

游标设置示例,例如更新后:

_weightEditingController.selection = TextSelection.fromPosition(TextPosition(offset: _weightEditingController.text.length));

相关问题