TECH

Flutter | Dynamically Add & Remove Textfields

Do your flutter form require you to add and remove TextFields dynamically? How many text editing controllers will you initialize at starting? How will

Hitesh Verma May 12, 2023 · 5 min. read

Do your flutter form require you to add and remove TextFields dynamically? How many text editing controllers will you initialize at starting? How will you dispose these controllers properly when the field(s) got removed? This article will help you with these problems.

Dynamic textfields in flutter
Dynamic textfields in flutter

We will create a form in which we can input names of any number of friends. No state management except setState() is used for the sake of simplicity.

Create a stateful widget containing the text field

Each dynamic text field will be a stateful widget to properly handle the initialization & disposing of the TextEditingController. We can provide an initial value to be displayed in the text field & an onChanged callback which will be triggered every time the text field value changes.

class DynamicTextfield extends StatefulWidget {
  final String? initialValue;
  final void Function(String) onChanged;

  const DynamicTextfield({
    super.key,
    this.initialValue,
    required this.onChanged,
  });

  @override
  State createState() => _DynamicTextfieldState();
}

class _DynamicTextfieldState extends State {
  late final TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController();
    _controller.text = widget.initialValue ?? '';
  }

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

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: _controller,
      onChanged: widget.onChanged,
      decoration: const InputDecoration(hintText: "Enter your friend's name"),
      validator: (v) {
        if (v == null || v.trim().isEmpty) return 'Please enter something';
        return null;
      },
    );
  }
}

Create the list of text fields

We will display the dynamic text-fields equal to the length of the friends list. This list will hold the values from each text field. We have initialized the friends list with an empty string to display a single text field initially.

class _DynamicTextfieldsAppState extends State {
  final _formKey = GlobalKey();
  final List _friendsList = [''];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dynamic TextFormFields')),
      body: Form(
        key: _formKey,
        child: Column(
          children: [
            Expanded(
              child: ListView.separated(
                itemCount: _friendsList.length,
                padding: const EdgeInsets.all(20),
                itemBuilder: (context, index) => Row(
                  children: [
                    Expanded(
                      child: DynamicTextfield(
                        key: UniqueKey(),
                        initialValue: _friendsList[index],
                        onChanged: (v) => _friendsList[index] = v,
                      ),
                    ),
                    const SizedBox(width: 20),
                    _textfieldBtn(index),
                  ],
                ),
                separatorBuilder: (context, index) => const SizedBox(
                  height: 20,
                ),
              ),
            ),
            // submit button
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState?.validate() ?? false) {
                    print(_friendsList);
                  }
                },
                child: const Text('Submit'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Create add / remove text-field button

We will show the add button with the last text field and a remove button with all the other text fields. Pressing the add button will add an empty string to the friends list & thus a new text field is added. Pressing the remove button will remove value at the given index from the friends list & thus the text field is deleted.

Widget _textfieldBtn(int index) {
    bool isLast = index == _friendsList.length - 1;

    return InkWell(
      onTap: () => setState(
        () => isLast ? _friendsList.add('') : _friendsList.removeAt(index),
      ),
      borderRadius: BorderRadius.circular(15),
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(15),
          color: isLast ? Colors.green : Colors.red,
        ),
        child: Icon(
          isLast ? Icons.add : Icons.remove,
          color: Colors.white,
        ),
      ),
    );
  }

Complete Code

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: DynamicTextfieldsApp()));

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

  @override
  State createState() => _DynamicTextfieldsAppState();
}

class _DynamicTextfieldsAppState extends State {
  final _formKey = GlobalKey();
  final List _friendsList = [''];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dynamic TextFormFields')),
      body: Form(
        key: _formKey,
        child: Column(
          children: [
            Expanded(
              child: ListView.separated(
                itemCount: _friendsList.length,
                padding: const EdgeInsets.all(20),
                itemBuilder: (context, index) => Row(
                  children: [
                    Expanded(
                      child: DynamicTextfield(
                        key: UniqueKey(),
                        initialValue: _friendsList[index],
                        onChanged: (v) => _friendsList[index] = v,
                      ),
                    ),
                    const SizedBox(width: 20),
                    _textfieldBtn(index),
                  ],
                ),
                separatorBuilder: (context, index) => const SizedBox(
                  height: 20,
                ),
              ),
            ),
            // submit button
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState?.validate() ?? false) {
                    print(_friendsList);
                  }
                },
                child: const Text('Submit'),
              ),
            )
          ],
        ),
      ),
    );
  }

  /// last textfield will have an add button, tapping which will add a new textfield below
  /// and all other textfields will have a remove button, tapping which will remove the textfield at the index
  Widget _textfieldBtn(int index) {
    bool isLast = index == _friendsList.length - 1;

    return InkWell(
      onTap: () => setState(
        () => isLast ? _friendsList.add('') : _friendsList.removeAt(index),
      ),
      borderRadius: BorderRadius.circular(15),
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(15),
          color: isLast ? Colors.green : Colors.red,
        ),
        child: Icon(
          isLast ? Icons.add : Icons.remove,
          color: Colors.white,
        ),
      ),
    );
  }
}

class DynamicTextfield extends StatefulWidget {
  final String? initialValue;
  final void Function(String) onChanged;

  const DynamicTextfield({
    super.key,
    this.initialValue,
    required this.onChanged,
  });

  @override
  State createState() => _DynamicTextfieldState();
}

class _DynamicTextfieldState extends State {
  late final TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController();
    _controller.text = widget.initialValue ?? '';
  }

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

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: _controller,
      onChanged: widget.onChanged,
      decoration: const InputDecoration(hintText: "Enter your friend's name"),
      validator: (v) {
        if (v == null || v.trim().isEmpty) return 'Please enter something';
        return null;
      },
    );
  }
}

Thank you for reading this article. I hope it helped you :-)

Read next

Customized Calendar in Flutter

Hey, have you ever wanted a date-picker in your app but not in a dialog box? And you tried all the dart packages out there but cannot change their UIs

Hitesh Verma May 28, 2023 · 9 min read

Flutter: Custom Cupertino Date Picker

Hitesh Verma in TECH
May 28, 2023 · 6 min read

Flutter | Highlight searched text in results

Hitesh Verma in TECH
May 12, 2023 · 4 min read