Search code examples
flutterdartflutter-state

Clear button not changing state


I have a clear button that should be visible if the date and time fields are filled and not visible if they are empty. The logic part works but the state does not change and if both the textfields are filled the checkbox doesn't appear. I can't use the default suffix icon as both the textfields are wrapped within a gesture detector.

class TodoScreen extends StatefulWidget {
  final int? todoIndex;
  final int? arrayIndex;

  const TodoScreen({Key? key, this.todoIndex, this.arrayIndex})
      : super(key: key);

  @override
  State<TodoScreen> createState() => _TodoScreenState();


class _TodoScreenState extends State<TodoScreen> {

  @override
  Widget build(BuildContext context) {
    String date = '';
    String? time = '';

    if (widget.todoIndex != null) {
      date = arrayController
          .arrays[widget.arrayIndex!].todos![widget.todoIndex!].date!;
      time = arrayController
          .arrays[widget.arrayIndex!].todos![widget.todoIndex!].time;
    }

    TextEditingController _dateController = TextEditingController(text: date);
    TextEditingController _timeController = TextEditingController(text: time);

    late String _setTime, _setDate;
    late String _hour, _minute, _time;
    late String dateTime;
    DateTime selectedDate = DateTime.now();
    TimeOfDay selectedTime = TimeOfDay(
        hour: (TimeOfDay.now().minute > 55)
            ? TimeOfDay.now().hour + 1
            : TimeOfDay.now().hour,
        minute: (TimeOfDay.now().minute > 55) ? 0 : TimeOfDay.now().minute + 5);

    Future<DateTime?> _selectDate() => showDatePicker(
        builder: (context, child) {
          return datePickerTheme(child);
        },
        initialEntryMode: DatePickerEntryMode.calendarOnly,
        context: context,
        initialDate: selectedDate,
        initialDatePickerMode: DatePickerMode.day,
        firstDate: DateTime.now(),
        lastDate: DateTime(DateTime.now().year + 5));

    Future<TimeOfDay?> _selectTime() => showTimePicker(
        builder: (context, child) {
          return timePickerTheme(child);
        },
        context: context,
        initialTime: selectedTime,
        initialEntryMode: TimePickerEntryMode.input);

    Future _pickDateTime() async {
      DateTime? date = await _selectDate();
      if (date == null) return;
      if (date != null) {
        selectedDate = date;
        _dateController.text = DateFormat("MM/dd/yyyy").format(selectedDate);
      }
      TimeOfDay? time = await _selectTime();
      if (time == null) {
        _timeController.text = formatDate(
            DateTime(
                DateTime.now().year,
                DateTime.now().day,
                DateTime.now().month,
                DateTime.now().hour,
                DateTime.now().minute + 5),
            [hh, ':', nn, " ", am]).toString();
      }
      if (time != null) {
        selectedTime = time;
        _hour = selectedTime.hour.toString();
        _minute = selectedTime.minute.toString();
        _time = _hour + ' : ' + _minute;
        _timeController.text = _time;
        _timeController.text = formatDate(
            DateTime(2019, 08, 1, selectedTime.hour, selectedTime.minute),
            [hh, ':', nn, " ", am]).toString();
      }
    }

    bool visible =
        (_dateController.text.isEmpty && _timeController.text.isEmpty)
            ? false
            : true;

    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: SafeArea(
        child: Container(
          width: double.infinity,
          child: GestureDetector(
                onTap: () async {
                  await _pickDateTime();
                  visible = true;
                },
                child: Container(
                    margin: const EdgeInsets.only(top: 20.0),
                    width: double.infinity,
                    padding: const EdgeInsets.symmetric(
                        horizontal: 24.0, vertical: 15.0),
                    decoration: BoxDecoration(
                        color: tertiaryColor,
                        borderRadius: BorderRadius.circular(14.0)),
                    child: Column(
                      children: [
                        Row(
                          children: [
                            Flexible(
                              child: TextField(
                                enabled: false,
                                controller: _dateController,
                                onChanged: (String val) {
                                  _setDate = val;
                                },
                                decoration: InputDecoration(
                                    hintText: "Date",
                                    hintStyle: hintTextStyle,
                                    border: InputBorder.none),
                                style: todoScreenStyle,
                              ),
                            ),
                            visible
                                ? IconButton(
                                    onPressed: () {
                                      _dateController.clear();
                                      _timeController.clear();
                                      visible = false;
                                    },
                                    icon: const Icon(
                                      Icons.close,
                                      color: Colors.white,
                                    ))
                                : Container()
                          ],
                        ),
                        primaryDivider,
                        TextField(
                          onChanged: (String val) {
                            _setTime = val;
                          },
                          enabled: false,
                          controller: _timeController,
                          decoration: InputDecoration(
                              hintText: "Time",
                              hintStyle: hintTextStyle,
                              border: InputBorder.none),
                          style: todoScreenStyle,
                        )
                      ],
                    )),
              ),
        ),
      ),
    );
  }
}}

If the code doesn't fully make sense it's because I removed some of the irrelevant code that do not add any real value to the question

Full code on Github


Solution

  • First, move all your TextEditController outside of build method, it should be initialized in the initState method, like this :

      late TextEditingController _dateController;
      late TextEditingController _timeController;
      late TextEditingController titleEditingController;
      late TextEditingController detailEditingController;
    
      @override
      void initState() {
        super.initState();
        String title = '';
        String detail = '';
        String date = '';
        String? time = '';
    
        if (widget.todoIndex != null) {
          title = arrayController.arrays[widget.arrayIndex!].todos![widget.todoIndex!].title ?? '';
          detail = arrayController.arrays[widget.arrayIndex!].todos![widget.todoIndex!].details ?? '';
          date = arrayController.arrays[widget.arrayIndex!].todos![widget.todoIndex!].date!;
          time = arrayController.arrays[widget.arrayIndex!].todos![widget.todoIndex!].time;
        }
    
        _dateController = TextEditingController(text: date);
        _timeController = TextEditingController(text: time);
        titleEditingController = TextEditingController(text: title);
        detailEditingController = TextEditingController(text: detail);
      }
    

    Then, change your onPressed function:

     onPressed: () {
       _dateController.clear();
       _timeController.clear();
       setState(() {});
     },
    

    For your question about CheckBox, you don't need a StreamBuilder for that, just put your CheckBox like this:

    Checkbox(
            shape: const CircleBorder(),
            checkColor: Colors.white,
            activeColor: primaryColor,
            value: done,
            side: Theme.of(context)
                .checkboxTheme
                .side,
            onChanged: (value) {
              setState(() {
                done = value;
              });
            })
    

    And of cource, try to move your done variable outside build method too:

      late bool done;
    
      @override
      void initState() {
        super.initState();
    
        done =
        (widget.todoIndex == null) ? false : arrayController.arrays[widget.arrayIndex!].todos![widget.todoIndex!].done!;
      }