> Frontend/Flutter

Flutter_12. DatePicker

Janku 2023. 5. 6. 13:02

 

 

 

0) 학습 내용

  1.  DatePicker를 사용하여 선택된 Date를 저장하기. 

 

1)  완성된 코드

 

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

final formatter = DateFormat.yMd();

class NewExpense extends StatefulWidget {
  const NewExpense({Key? key}) : super(key: key);

  @override
  State<NewExpense> createState() => _NewExpenseState();
}

class _NewExpenseState extends State<NewExpense> {
  // => currently no need
  var _enteredTitle = '';

  void _saveTitleInput(String inputValue) {
    // since there is no change in UI, no need to use setState. => currently no need
    _enteredTitle = inputValue;
  }

  // need to tell Flutter to delete TextEditingController when this modal is closed => otherwise, way of wasting memory.
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  DateTime? _selectedDate;

  Future<void> _presentDatePicker() async {
    final now = DateTime.now();
    final firstDate = DateTime(now.year - 1, now.month, now.day);
    // showDatePicker => Flutter's Date picker Method
    final pickedDate = await showDatePicker(
        context: context,
        initialDate: now,
        firstDate: firstDate,
        lastDate: now);
    // => showDatePicker return value of type 'future'
    // => need to async await or then method

    setState(() {
      _selectedDate = pickedDate;
    });
  }

  @override
  void dispose() {
    // dispose is a part of StatefulWidget lifecycle. called when widget is destroyed.
    _titleController.dispose();
    _amountController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          TextField(
            // onChanged: _saveTitleInput, // first way of storing text value => onChanged
            controller: _titleController,
            // second way of storing text value => controller
            maxLength: 50,
            keyboardType: TextInputType.text,
            // input tag type in JS
            decoration: const InputDecoration(
                label: Text('Title')), // title of input (Text Field)
          ),
          Row(
            children: [
              Expanded(
                child: TextField(
                  // TextField wants to take as much space horizontally as possible, and Row do not restrict the amount of space  which will cause error => Expanded
                  controller: _amountController,
                  keyboardType: TextInputType.number,
                  decoration: const InputDecoration(
                      // 앞에 붙는 $ 표시
                      prefixText: '\$ ',
                      // title of input (Text Field)
                      label: Text('Amount')),
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  // row controls the horizontal alignment to push content to the end
                  crossAxisAlignment: CrossAxisAlignment.center,
                  // center the content vertically.
                  children: [
                    Text(
                      _selectedDate == null
                          ? 'No Dated Date'
                          : formatter.format(
                              _selectedDate!), // ! assume this wont be null
                    ),
                    IconButton(
                        onPressed: _presentDatePicker,
                        icon: const Icon(Icons.calendar_month))
                  ],
                ),
              )
            ],
          ),
          Row(
            children: [
              TextButton(
                  onPressed: () {
                    // Navigator class's pop makes modal close
                    Navigator.pop(context);
                  },
                  child: const Text('Cancel')),
              ElevatedButton(
                  onPressed: () {
                    print(_titleController.text);
                    print(_amountController.text);
                  },
                  child: const Text('Save Expense')),
            ],
          )
        ],
      ),
    );
  }
}

 

 

2) 기존에 만든 Title TextField 에 amount 와 Date 를 추가한다.

 

Row(
  children: [
    Expanded(
      child: TextField(
        // TextField wants to take as much space horizontally as possible, and Row do not restrict the amount of space  which will cause error => Expanded
        controller: _amountController,
        keyboardType: TextInputType.number,
        decoration: const InputDecoration(
            // 앞에 붙는 $ 표시
            prefixText: '\$ ',
            // title of input (Text Field)
            label: Text('Amount')),
      ),
    ),
    const SizedBox(width: 16),
    Expanded(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        // row controls the horizontal alignment to push content to the end
        crossAxisAlignment: CrossAxisAlignment.center,
        // center the content vertically.
        children: [
          Text(
            _selectedDate == null
                ? 'No Dated Date'
                : formatter.format(
                    _selectedDate!), // ! assume this wont be null
          ),
          IconButton(
              onPressed: _presentDatePicker,
              icon: const Icon(Icons.calendar_month))
        ],
      ),
    )
  ],
),

 

  • 한 행(Row: 가로)에  2개의 열 (Column: 세로)를 가질 예정임으로, Row로 감싸준다. 
  • 이때, 1) TextField 의 경우는 최대한 많은 수평적 space를 갖는데, Row는 이를 제한하지 않음으로, 에러발생  => Expanded 사용
  • 또한, 2) 두번째 Row는 그안에 중첩된 Row (Text와 IconButton)을 가질 예정임으로 => Expanded 사용 
  • 아래 사진에서, 달력 아이콘을 선택하면 _presendDatePicker라는 함수가 실행될 예정

 

현재 showModalBottomSheet 상황

 

3) DatePicker (달력 클릭 시, _presentDatePicker 실행)

 

  •  달력 아이콘을 클릭하면, _presentDatePicker 함수가 실행된다.
Future<void> _presentDatePicker() async {
  final now = DateTime.now();
  final firstDate = DateTime(now.year - 1, now.month, now.day);
  // showDatePicker => Flutter's Date picker Method
  final pickedDate = await showDatePicker(
      context: context,
      initialDate: now,
      firstDate: firstDate,
      lastDate: now);
  // => showDatePicker return value of type 'future'
  // => need to async await or then method

  setState(() {
    _selectedDate = pickedDate;
  });
}

 

 

  •  showDatePicker는 달력을 보여주는 flutter 자체적인 datepicker
  • 날짜를 선택하고 OK 버튼을 누르면 값을 리턴해준다.
  • 이때 Return 되는  값의 타입은 Future이다. 
  • future는 비동기 작업의 결과이며, 미완료 ||  완료 - 두 가지 상태를  가질 수 있음.
  • 비동기 작업의 결과물: 
  • API Data 통신
  • 테이터 베이스를 거쳐 CRUD
  • 파일에서 데이터 읽기
  • 두가지 상태:    
    • 미완성 (value를 생성하기 전)
      비동기 함수를 호출하면 완료되지 않은 미래가 반환됩니다. 미래에는 함수의 비동기 작업이 완료되거나 오류가 발생하기를 기다리고 있습니다.
    • 완료(value 생성)
      비동기 작업이 성공하면 future는 값(유형 Future값으로 완료) 으로, 실패하면 오류와 함께 완료.
  • 이러한 type을 가진 future은 async-await을 통해서 작업하면 값을 받을 수 있다. 
DateTime? _selectedDate;

 

  • DateTime?으로 _selectedDate를 생성하고, setState안에 Future의 값을 대입 (UI가 변경되기 때문)

 


... 생략


Expanded(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.end,
    // row controls the horizontal alignment to push content to the end
    crossAxisAlignment: CrossAxisAlignment.center,
    // center the content vertically.
    children: [
      Text(
        _selectedDate == null
            ? 'No Dated Date'
            : formatter.format(
                _selectedDate!), // ! assume this wont be null
      ),
      
      
      ... 생략

 

  • 삼항 연상자를 써서 _selectedDate 가 null일 경우, No Dated Date 노출되며, null이 아닐때는 intl의 formatter를 사용해서 _selectedDate을 y-M-d 형태로 format한다.
  • 이때, Dart는 해당 일자가 null일 수도 있다고 가정해서, _selectedDate 뒤에 느낌표를 붙여 절대 null이 안된다고 가정