Flutter Bloc 不会改变 TextFormField 的初始值

Posted

技术标签:

【中文标题】Flutter Bloc 不会改变 TextFormField 的初始值【英文标题】:Flutter Bloc does not change TextFormField initialValue 【发布时间】:2020-12-01 16:29:48 【问题描述】:

我正在使用 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 发出事件。

【问题讨论】:

只是快速提醒一下,钥匙很贵。当只需要更改状态时,您应该小心不要重建整个小部件。 【参考方案1】:

您可以在下面复制粘贴运行完整代码 1 和 2 您可以将UniqueKey() 提供给ScaffoldTextFormField 以强制重新创建 详情可以参考https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d

如果 Element 的 key 与对应的 Widget 的 key 不匹配。这会导致 Flutter 停用这些元素并删除对元素树中元素的引用

解决方案 1:

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

解决方案 2:

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);
                            ,
                          )
                        ],
                      ),
                    ),
                  );
                ),
          ),
        ),
      ),
    );
  

【讨论】:

【参考方案2】:

您不应该仅仅因为要更新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);
                        ,
                      )
                    ],
                  ),
                ),
              );
            ),

【讨论】:

这段代码有效吗?它对我来说失败了,因为你不能有初始值和控制器。 我不认为这段代码会起作用,因为在颤振中我们不能同时给出初始值和控制器。【参考方案3】:

我也遇到了同样的问题。在添加Unique Key 时,颤动会不断构建小部件,并且我的键盘每次都失焦。我解决它的方法是在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)),
        ),
      ),
    );
  

此编辑运行良好。

最终编辑:

我在我的 bloc 状态上添加了一个 key? key 并将此密钥传递给小部件。如果我需要再次重绘表单,我将事件中的密钥更改为UniqueKey。这是迄今为止我一起实现 bloc 和 form 的最简单的方法。如果需要解释,请在这里评论,我稍后会补充。

【讨论】:

以上是关于Flutter Bloc 不会改变 TextFormField 的初始值的主要内容,如果未能解决你的问题,请参考以下文章

从 v7.2.1 迁移到 flutter_bloc v 8.0.0 后不会触发 flutter_bloc 事件

Flutter Bloc 不断重建小部件而不改变状态

Flutter Bloc 状态管理

Flutter bloc:更新状态不会重新调用 BlocBuilder

Flutter Bloc A 在 Bloc B 中添加流,但此流不会通过 StreamBuilder 在 UI 中呈现

Flutter bloc emmitInOrder 不会发出初始状态,但会发出