> Frontend/Flutter
Flutter_12. DatePicker
Janku
2023. 5. 6. 13:02
0) 학습 내용
- 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라는 함수가 실행될 예정
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는 비동기 작업의 결과이며, 미완료 || 완료 - 두 가지 상태를 가질 수 있음.
- 비동기 작업의 결과물:
|
- 두가지 상태:
- 미완성 (value를 생성하기 전)
비동기 함수를 호출하면 완료되지 않은 미래가 반환됩니다. 미래에는 함수의 비동기 작업이 완료되거나 오류가 발생하기를 기다리고 있습니다. - 완료(value 생성)
비동기 작업이 성공하면 future는 값(유형 Future값으로 완료) 으로, 실패하면 오류와 함께 완료.
- 미완성 (value를 생성하기 전)
- 이러한 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이 안된다고 가정