Nk C
Nk C

Reputation: 1

Unable to link DropdownButton to TextFormField in flutter using provider

Problem: I'm trying to link the value of a dropdown to a text field in Flutter. I'm using Provider for state management. The value from the dropdown updates the state correctly, but the text field is not reflecting the changes.

Steps to Recreate: Below is a simple code that reproduces the issue:

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

class ErrorRecreationScreen extends StatefulWidget {
  const ErrorRecreationScreen({super.key});

  @override
  State<ErrorRecreationScreen> createState() => _ErrorRecreationScreenState();
}

class _ErrorRecreationScreenState extends State<ErrorRecreationScreen> {
  @override
  Widget build(BuildContext context) {
    return Consumer<AppState>(builder: (context, appState, child){
      return Scaffold(
        appBar: AppBar(title: const Text("Error Recreation Screen"),),

        body: Column(
          children: [
            const Section1(),
            const SizedBox(height: 10,),

            const Card(child: Section2()),

            Text("val=${appState.val}"),
          ],
        ),

      );
    });

  }
}


class Section1 extends StatefulWidget {
  const Section1({super.key});

  @override
  State<Section1> createState() => _Section1State();
}

class _Section1State extends State<Section1> {
  List<String> items = ["Item 1", "Item 2", "Item 3", "Item 4"];

  @override
  Widget build(BuildContext context) {
    return Consumer<AppState>(builder: (context, appState, child){
      return Column(
        children: [
          Center(
            child: DropdownButton<String>(
              // The value that is currently selected
              value: items.contains(appState.val)? appState.val : null,
              hint: const Text("Select an item"),
              // Dropdown items generated from the list
              items: items.map((String item) {
                return DropdownMenuItem<String>(
                  value: item,
                  child: Text(item),
                );
              }).toList(),
              // When an item is selected
              onChanged: (String? newValue) {
                appState.setVal(newValue);
              },
            ),
          ),
        ],
      );
    });
  }
}

class Section2 extends StatefulWidget {
  const Section2({super.key});

  @override
  State<Section2> createState() => _Section2State();
}

class _Section2State extends State<Section2> {


  @override
  Widget build(BuildContext context) {
    return Consumer<AppState>(builder: (context, appState, child){
      return Column(
        children: [
          MyWidget(str: appState.val, onChange: appState.setVal)
        ],
      );
    });
  }
}

class MyWidget extends StatefulWidget {
  final String? str;
  final void Function(String?) onChange;
  const MyWidget({super.key, required this.str, required this.onChange});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late TextEditingController textEditingController;

  @override void initState() {
    super.initState();
    textEditingController = TextEditingController(text: widget.str);
  }

  @override
  Widget build(BuildContext context) {
    print("#[build-MyWidget-call] val=${widget.str}");
    return TextFormField(
      controller: textEditingController,
      onChanged: widget.onChange,
    );
  }
}



class AppState extends ChangeNotifier{
  String val = "";

  void setVal(String? val){
    if(val == null) return;
    this.val = val;
    notifyListeners();
  }
}

Images of Error:

  1. Initial Screen

  2. After selecting item from drop down

Initial Problem: When I select a value from the dropdown, it updates appState.val as expected, but the text field does not reflect this value.

I realized that initState() only runs once. So, I used didUpdateWidget() to update the TextEditingController when the dropdown value changes.

Updated Code Using didUpdateWidget():

class _MyWidgetState extends State<MyWidget> {
  late TextEditingController textEditingController;

  @override void initState() {
    super.initState();
    textEditingController = TextEditingController(text: widget.str);
  }

  @override void didUpdateWidget(covariant MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    textEditingController.text = widget.str ?? "";
  }

  @override
  Widget build(BuildContext context) {
    print("#[build-MyWidget-call] val=${widget.str}");
    return TextFormField(
      controller: textEditingController,
      onChanged: widget.onChange,
    );
  }
}

New Problem: This solution fixes the issue of the text field not reflecting the dropdown value. However, a new problem arises:

When I type in the text field, it replaces all the content with the character I am typing. It seems like the TextEditingController is resetting after each keystroke.

Error Images:

1. Initial Screen

  1. After Selecting item from drop down

  2. While Typing 'a'

  3. While Typing 'b'

Expectation: I want the dropdown value to populate the text field but allow users to freely edit the text in the field without overwriting input.

Upvotes: 0

Views: 48

Answers (0)

Related Questions