Home > Software design >  Problem updating text in TextFormField in flutter with provider state management?
Problem updating text in TextFormField in flutter with provider state management?

Time:07-09

I have an issue with updating text inside TextFormField when using Provider as state management.

I reduced my problem to abstract one (I removed all the clutter code) and here how it works:

  • there is a someValue in AppState
  • the someValue can be edited via Form->TextFormField
  • the someValue is to be reflected as a title of the AppBar when typing (onChange)
  • the someValue can be updated from external source (in the example it is a button that updates it)
  • when someValue is updated from external source, it MUST be updated in text Form->TextFormField as well

The last one is causing me the problem. Consider following code:

AppState.dart

import 'package:flutter/foundation.dart';

class AppState extends ChangeNotifier
{
  String someValue = '';

  updateSomeValue(String newValue)
  {
    someValue = newValue;
    notifyListeners();
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';

void main() {
  runApp(ChangeNotifierProvider(create: (_) => AppState(), child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  late TextEditingController _controller;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<AppState>(context);

// following line of code makes it possible for text to be changed by button
// and reflected in TextFormField
// but it causes nasty side effect, that when typing, cursor always goes to beginning of the line
    _controller.text = provider.someValue;

    return Scaffold(
      appBar: AppBar(
        title: Text(provider.someValue),
      ),
      body: Center(
        child: Form(
            key: _formKey,
            child: Column(children: [
              TextFormField(
                controller: _controller,
                onChanged: (v) {
                  provider.updateSomeValue(v);
                },
              ),
              ElevatedButton(
                  onPressed: () {
                    provider.updateSomeValue('foo_bar');
                  },
                  child: Text('change text external source'))
            ])),
      ),
    );
  }
}

The problem: When I added the line _controller.text = provider.someValue; it fixed the issue of updating TextFormField when button is clicked, but it create new issue, that when typing in TextFormField, it is also triggered, cause carret of text field to move to the beginning of the text field.

How to make it work so text (value) of a TextFormField can be updated externally, without causing carret issue when typing?

CodePudding user response:

An easy way of doing this by listening TextEditingController, while the TextFormField is the ruler here.

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  late TextEditingController _controller;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController()
      ..addListener(() {
        Provider.of<AppState>(context, listen: false)
            .updateSomeValue(_controller.text);
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(context.watch<AppState>().someValue),
      ),
      body: Center(
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _controller,
              ),
              ElevatedButton(
                  onPressed: () {
                    _controller.text = 'foo_bar';
                  },
                  child: Text('change text external source'))
            ],
          ),
        ),
      ),
    );
  }
}

Also, you can check riverpod

  • Related