Flutter Text Field + Date Picker-img

Flutter Text Field + Date Picker

Create a form text field that triggers the date picker and updates its value without opening the on-screen keyboard.
Flutter has a wide range of functions classes and widgets to use. In this guide, we will go through creating a TextFormField that opens a date picker popup when clicked without showing a keyboard. Here's a list of what we will cover:

  • Creating a simple form
  • Using Date picker and a text field jointly
  • Stopping any other unwanted behavior (keyboard trigger).

Getting Started

Let's start from scratch by having a Scaffold with a Container to hold our form. You should have something that looks like this as a starting point.
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text("Date Field"),
        ),
        body: Container(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: /* We will add our form here */,
          ),
        ),
      ),
    );
  }
}
Let's also create a basic model to structure our form models/task.dart. Let's give it a name and date properties
class Task {
  String name;
  DateTime date;

  Task({this.name, this.date});
}

Creating Our Form

Now that we have things set up let's create a new file new_task.dart to hold our StatefullWidget that will contain the form.
class NewTaskForm extends StatefulWidget {
  NewTaskForm({Key key}) : super(key: key);

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

class _NewTaskFormState extends State<NewTaskForm> {
  Task task = new Task();
  final _formKey = GlobalKey<FormState>();
  TextEditingController _dateController = TextEditingController();
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
    );
  }
}
  • We create a task to hold our input values once the form is saved
  • _formKey is used as a global identifier to our form that we will use when submitting and validating the form
  • _dateController is a TextEditingController that we use to manually change the text inside the date text field that we are going to create
  • selectedDate is used to store the date that is returned from the date picker

Let's import and add the NewTaskForm widget to our main.dart file in order to see our changes as we work
import 'package:date_time_field/new_task_form.dart';
Our Scaffold body should look like this
body: Container(
  child: Padding(
    padding: const EdgeInsets.all(8.0),
    child: NewTaskForm(),
  ),
),
Once you save you will get an error that the form child cannot be empty. Let's add the name and a date text fields and the submit button to our form.
Form(
  key: _formKey,
  child: ListView(
    children: [
      Column(
        children: [
          TextFormField(
            onSaved: (val) => task.name = val,
            decoration: InputDecoration(
              labelText: 'Task Name',
              icon: Icon(Icons.account_circle),
            ),
          ),
          TextFormField(
            onSaved: (val) {
              task.date = selectedDate;
            },
            controller: _dateController,
            keyboardType: TextInputType.datetime,
            decoration: InputDecoration(
              labelText: "Date",
              icon: Icon(Icons.calendar_today),
            ),
            validator: (value) {
              if (value.isEmpty)
                return "Please enter a date for your task";
              return null;
            },
          ),
          FlatButton(
            child: Text("Submit"),
            textColor: Colors.white,
            color: Colors.blueAccent,
            onPressed: () {
              if (_formKey.currentState.validate()) {
                _formKey.currentState.save();
              }
            },
          )
        ],
      )
    ],
  ),
);
  • We use a ListView in order to have scrollable content if the form is ever bigger than the widget it lies in
  • A column is used to layout the form widgets
  • In our text fields we use onSaved to assign the values to the task we created earlier.
  • We also create a simple button to submit and save the form details after they are validated

This guide will not focus on form features such as validations and decoration. If you would like to know more about these you can take a look at this guide Advanced Advanced Flutter Forms (Part 1) & (Part 2).

Date Picker

Now that we have a form with the appropraite fields we can now trigger the date picker once the date field is clicked. To do that we could use the onTap property of the TextFormField widget, but that would trigger the keyboard which is something we do not want.

Let's wrap our TextFormField with a GestureDetector and use the onTap property of this widget. We will also need to create a function that is triggered when the tap event happens. This will not stop the keyboard from opening but we will get there in a second. Our date field inside our column should look like this now.
GestureDetector(
  onTap: () => _selectDate(context),
  child: TextFormField(
    onSaved: (val) {
      task.date = selectedDate;
    },
    controller: _dateController,
    decoration: InputDecoration(
      labelText: "Date",
      icon: Icon(Icons.calendar_today),
    ),
    validator: (value) {
      if (value.isEmpty)
        return "Please enter a date for your task";
      return null;
    },
  ),
),
Let's create the _selectDate() function that opens the date picker and assigns the selected value to the selectedDate variable in our class outside the build function.
_selectDate(BuildContext context) async {
  final DateTime picked = await showDatePicker(
      context: context,
      initialDate: selectedDate,
      firstDate: DateTime(2019, 8),
      lastDate: DateTime(2100));
  if (picked != null && picked != selectedDate)
    setState(() {
      selectedDate = picked;
      var date =
          "${picked.toLocal().day}/${picked.toLocal().month}/${picked.toLocal().year}";
      _dateController.text = date;
    });
}
  • We use an async function because the showDatePicker is a Future.
  • We store the value that is returned from the date picker in a variable called picked
  • showDatePicker opens the date picker
    • context takes the context of the parent widget
    • initialDate is the selected date when the picker first opens
    • to create a max and min values in the date picker we use firstDate and lastDate
  • We then create a condition to edit the TextFormField text and update the selectedDate.
    • _dateController.text is used to manually set the text field value

This will still trigger the keyboard and not the date picker when the text field is tapped but let's stop the keyboard from popping up and get the functionality we need.

Disabling the Keyboard

The TextFormField widget does not have a property for us to stop showing the keyboard. So we would need to wrap our date text field with an AbsorbPointer. This disables touch events from all the children of the AbsorbPointer. That is why we had to use a GestureDetector to manually apply onTap functionality. Our GestureDetector should look like this now:
GestureDetector(
  onTap: () => _selectDate(context),
  child: AbsorbPointer(
    child: TextFormField(
      onSaved: (val) {
        task.date = selectedDate;
      },
      controller: _dateController,
      decoration: InputDecoration(
        labelText: "Date",
        icon: Icon(Icons.calendar_today),
      ),
      validator: (value) {
        if (value.isEmpty)
          return "Please enter a date for your task";
        return null;
      },
    ),
  ),
),
This should now trigger the date picker without triggering the keyboard while still maintaining all the text field functionality such as validation. Once the form is submitted the selected value will be assigned to our task date.

Conclusion

We just created a form text field that triggers the date picker and updates its value and the underlying class value accordingly without using or opening the on-screen keyboard

Like the post?

feel free to share it anywhere

Related Guides

Advanced Flutter Forms (part 2)-img

Advanced Flutter Forms (part 2)

Typeaheads, auto fill and snack bars
Advanced Flutter Forms (part 1)-img

Advanced Flutter Forms (part 1)

Textfields, Dropdowns and Validations