Search code examples
flutterblocflutter-bloc

Flutter Bloc does not change TextFormField initialValue


I'm using Bloc library and noticed after yielding a new state my TextFormField initialValue does not change.

My app is more complicated than this but I did a minimal example. Also tracking the state it is changing after pushing the events.

Bloc is supposed to rebuild the entire widget right. Am I missing something?

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

Edit
As @chunhunghan noted adding a UniqueKey solves this. I should have also mentioned that my case. the app emits events from the onChanged method of two TextFormField. This causes the Form to reset and remove the keyboard. autofocus does not work because there are two TextFormField wgich emit events.


Solution

  • You can copy paste run full code 1 and 2 below
    You can provide UniqueKey() to Scaffold or TextFormField to force recreate
    You can referecne https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d for detail

    if the key of the Element doesn’t match the key of the corresponding Widget. This causes Flutter to deactivate those elements and remove the references to the Elements in the Element Tree

    Solution 1:

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

    Solution 2:

    TextFormField(
                   key: UniqueKey(),
    

    working demo

    enter image description here

    full code 1 Scaffold with UniqueKey

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

    full code 2 TextFormField with UniqueKey

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