Flutter multiple email TextField

3,667

Solution 1

The trick here would be to show the deirable UI as follows, InputDecoration.collapsed() helps us to make the TextField minimal, u may add a Divider() as the last widget of the main column to portray that all of this goes on inside the TextField

I have tried to simulate the behaviour you seek to implement. I check for spaces at the end of string input in TextField using onChange method and for enter button i use onEditingComplete function. I have laid constrains on the row of emails to grow only upto a certain width of the viewport you can use a different layout too.

class Test extends StatefulWidget {
  @override
  _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
  List<String> emails = [];
  TextEditingController _emailController;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
            child: Center(
          child: Row(
            children: <Widget>[
              Container(
                constraints: BoxConstraints(maxWidth: 200, minWidth: 0),
                child: SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      ...emails
                          .map((email) => Chip(label: Text(email)))
                          .toList(),
                    ],
                  ),
                ),
              ),
              Expanded(
                child: TextField(
                  decoration: InputDecoration.collapsed(hintText: 'EMail'),
                  controller: _emailController,
                  onChanged: (String val) {
                    if (val.endsWith(' '))
                      setState(() {
                        emails.add(_emailController.text);
                        _emailController.text = '';
                      });
                  },
                  onEditingComplete: () {
                    setState(() {
                      emails.add(_emailController.text);
                      _emailController.text = '';
                    });
                  },
                ),
              )
            ],
          ),
        )),
      ),
    );
  }
}

Solution 2

If someone come here for the same features that i asked for there is my template, you can paste this in a new file and just call EmailInput() where you need it. You just want to use the setList attribute with the function to update your List<String> to get the data back into your parent component :) It also check if the string entered is a valid email or not.

It look something like this :

enter image description here

import 'package:flutter/material.dart';

class EmailInput extends StatefulWidget {
  final Function setList;
  final String hint;
  final List<String> parentEmails;

  const EmailInput({Key key, this.setList, this.hint, this.parentEmails}) : super(key: key);

  @override
  _EmailInputState createState() => _EmailInputState();
}

class _EmailInputState extends State<EmailInput> {
  TextEditingController _emailController;
  String lastValue = '';
  List<String> emails = [];
  FocusNode focus = FocusNode();
  @override
  void initState() {
    super.initState();
    _emailController = TextEditingController();

    focus.addListener(() {
      if (!focus.hasFocus) {
        updateEmails();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Center(
      child: Column(
        children: <Widget>[
          Container(
            constraints: BoxConstraints(
              minWidth: 0,
            ),
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Column(
                children: <Widget>[
                  ...emails
                      .map(
                        (email) => Chip(
                          avatar: CircleAvatar(
                            backgroundColor: Colors.black,
                            child: Text(
                              email.substring(0, 1),
                              style: TextStyle(color: Colors.white),
                            ),
                          ),
                          labelPadding: EdgeInsets.all(4),
                          backgroundColor: Color.fromARGB(255, 39, 182, 192),
                          label: Text(
                            email,
                            style: TextStyle(fontSize: 16, color: Colors.white),
                          ),
                          onDeleted: () => {
                            setState(() {
                              emails.removeWhere((element) => email == element);
                            })
                          },
                        ),
                      )
                      .toList(),
                ],
              ),
            ),
          ),
          TextField(
            keyboardType: TextInputType.emailAddress,
            decoration: InputDecoration.collapsed(hintText: widget.hint),
            controller: _emailController,
            focusNode: focus,
            onChanged: (String val) {
              setState(() {
                if (val != lastValue) {
                  lastValue = val;
                  if (val.endsWith(' ') && validateEmail(val.trim())) {
                    if (!emails.contains(val.trim())) {
                      emails.add(val.trim());
                      widget.setList(emails);
                    }
                    _emailController.clear();
                  } else if (val.endsWith(' ') && !validateEmail(val.trim())) {
                    _emailController.clear();
                  }
                }
              });
            },
            onEditingComplete: () {
              updateEmails();
            },
          )
        ],
      ),
    ));
  }

  updateEmails() {
    setState(() {
      if (validateEmail(_emailController.text)) {
        if (!emails.contains(_emailController.text)) {
          emails.add(_emailController.text.trim());
          widget.setList(emails);
        }
        _emailController.clear();
      } else if (!validateEmail(_emailController.text)) {
        _emailController.clear();
      }
    });
  }

  setEmails(List<String> emails) {
    this.emails = emails;
  }
}

bool validateEmail(String value) {
  Pattern pattern =
      r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
  RegExp regex = new RegExp(pattern);
  return regex.hasMatch(value);
}

Share:
3,667
Anthony D
Author by

Anthony D

Anthony

Updated on November 25, 2022

Comments

  • Anthony D
    Anthony D over 1 year

    For learning purpose i'm trying to do a gmail clone, i'm struggling with the multi email input you have when you are editing a new mail. I need to be abble to listen either for "ENTER" or "SPACE" so that i can modify the input (after validation) with a email "block" like this :

    enter image description here

    I think i can work with the onFieldSubmitted tag for the "ENTER" key but how can i change the input text ? I tried :

    controller.text = Container();

    but the "text" only accepts String (which seems logic).

    The goal is to do the exact same kind of inputField where you put the "Tags" on StackOverflow.

    I also found the EmailInputElement but i can't figure out how to use it properly, it seems like it's not a widget.

    If someone have any idea it would be much appreciated, thanks.

  • Anthony D
    Anthony D almost 4 years
    Thanks a lot @Aadiyaara your example make a lot of sense after fideling a bit with it,It's really a good starting point, i'll continue based on that Thanks dude !