[Flutter] DatePicker

2023-03-18 hit count image

Flutter 앱에서 사용자로부터 날짜를 입력받기 위해 달력(DatePicker)을 표시하는 방법에 대해서 알아봅시다.

개요

앱에서 사용자로부터 날짜를 입력받기 위해 다음과 같이 달력(DatePicker)을 표시하는 UI를 자주 볼 수 있습니다.

Flutter showDatePicker - DatePicker

이번 블로그 포스트에서는 showDatePicker를 사용하여 Flutter에서 DatePicker를 표시하는 방법에 대해서 알아보도록 하겠습니다.

여기서 소개하는 소스코드는 GitHub에서 확인하실 수 있습니다.

프로젝트 준비

showDatePicker의 사용 방법을 확인하기 위해 다음과 같이 버튼을 표시하는 간단한 앱을 만들어 보겠습니다.

Flutter showDatePicker - button for showDatePicker

그럼 다음 명령어를 실행하여 새로운 Flutter 프로젝트를 생성합니다.

flutter create show_date_picker

새로운 Flutter 프로젝트가 생성되었다면, main.dart 파일을 열고 다음과 같이 수정합니다.

class _MyHomePageState extends State<MyHomePage> {
  DateTime date = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Date Picker Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {},
          child: Text(
            "${date.year.toString()}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}",
          ),
        ),
      ),
    );
  }
}

DatePicker를 사용하여 버튼에 표시되고 있는 날짜를 변경할 예정이므로 Stateful 위젯으로 생성하였습니다. 이렇게 main.dart 파일을 수정하고 저장하면 다음과 같이 버튼이 잘 표시되는 것을 확인할 수 있습니다.

Flutter showDatePicker - button for showDatePicker

지금은 버튼 이벤트에 그 어떤 코드도 추가하지 않았으므로, 버튼을 클릭해도 아무 동작도 하지 않는 것을 확인할 수 있습니다.

showDatePicker

그럼 이제 showDatePicker를 사용하여 DatePicker를 표시하는 방법에 대해서 알아봅시다. 버튼을 클릭했을 때, showDatePicker을 표시하기 위해 ElevatedButton 위젯의 onPressed 함수를 다음과 같이 수정합니다.

...
ElevatedButton(
  onPressed: () async {
    final selectedDate = await showDatePicker(
      context: context,
      initialDate: date,
      firstDate: DateTime(2000),
      lastDate: DateTime.now(),
    );
    if (selectedDate != null) {
      setState(() {
        date = selectedDate;
      });
    }
  },
  ...,
),
...

showDatePicker 함수는 Future<DateTime?> 타입을 반환하므로, async-await를 사용해야 합니다. 또한 사용자가 날짜를 선택하지 않으면 null이 반환되므로 사용자가 선택한 날짜를 저장하기 위해 if문을 사용하였습니다. 마지막으로, 사용자가 선택한 날짜를 setState을 사용하여 State을 변경하여 화면에 표시된 날짜를 변경하도록 하였습니다.

이렇게 코드를 수정하고 저장하면, 다음과 같이 여전히 버튼이 잘 표시되는 것을 확인할 수 있습니다.

Flutter showDatePicker - button for showDatePicker

그럼 이제 버튼을 클릭해 봅니다. 버튼을 클릭하면 이전과 다르게 다음과 같이 달력이 표시되는 것을 확인할 수 있습니다.

Flutter showDatePicker - DatePicker

이제 달력에서 원하는 날짜를 선택하면, 다음과 같이 버튼에 표시된 날짜가 잘 변경되는 것을 확인할 수 있습니다.

Flutter showDatePicker - change date by DatePicker

initialEntryMode

현재 표시된 DatePicker의 오른쪽 상단에 수정 아이콘을 누르면 다음과 같이 날짜를 직접 변경할 수 있는 화면을 확인할 수 있습니다.

Flutter showDatePicker - edit date mode by DatePicker

이 기능을 사용하지 못하게 하기 위해서는 다음과 같이 showDatePicker 함수의 initialEntryMode 옵션에 DatePickerEntryMode.calendarOnly을 설정하면 됩니다.

...
ElevatedButton(
  onPressed: () async {
    final selectedDate = await showDatePicker(
      context: context,
      initialDate: date,
      firstDate: DateTime(2000),
      lastDate: DateTime.now(),
      initialEntryMode: DatePickerEntryMode.calendarOnly,
    );
    if (selectedDate != null) {
      setState(() {
        date = selectedDate;
      });
    }
  },
  ...,
),
...

그럼 다음과 같이 오른쪽 상단에 표시되었던 수정 아이콘이 사라진 것을 확인할 수 있습니다.

Flutter showDatePicker - disable edit date mode by DatePicker

다국어

달력에 표시된 언어를 영어가 아닌 다른 언어로 표시하기 위해서는 flutter_localizations 패키지를 설치할 필요가 있습니다. 다음 명령어를 사용하여 flutter_localizations 패키지를 설치합니다.

flutter pub add flutter_localizations --sdk=flutter

또는 pubspec.yaml 파일을 다음과 같이 수정하여 flutter_localizations 패키지를 설치합니다.

...
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
...

그럼 다음, 다음과 같이 MaterialApplocalizationsDelegates 옵션을 추가합니다.

import 'package:flutter_localizations/flutter_localizations.dart';
...
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      ...,
    );
  }
}
...

그런 다음, showDatePicker 함수의 locale 옵션에 표시하고자하는 언어를 설정합니다. 여기서는 일본어(Japanese)를 설정하였습니다.

...
final selectedDate = await showDatePicker(
  context: context,
  initialDate: date,
  firstDate: DateTime(2000),
  lastDate: DateTime.now(),
  initialEntryMode: DatePickerEntryMode.calendarOnly,
  locale: const Locale('ja', 'JP'),
);
...

이렇게 코드를 수정하고 저장하면, 다음과 같이 달력에 표시되는 언어가 영어가 아닌 일본어가 잘 표시되는 것을 확인할 수 있습니다.

Flutter showDatePicker - change locale of date picker

테스트 코드

다음과 같이 테스트 코드를 작성하여, showDatePicker가 잘 동작하는지 확인할 수 있습니다.

...
testWidgets('Change date by DatePicker', (WidgetTester tester) async {
  DateTime date = DateTime.now();

  await tester.pumpWidget(const MyApp());

  final year = date.year.toString();
  final month = date.month.toString().padLeft(2, '0');
  final day = date.day.toString().padLeft(2, '0');

  expect(find.text('$year-$month-$day'), findsOneWidget);

  // Press cancel test
  await tester.tap(find.text('$year-$month-$day'));
  await tester.pump();
  await tester.tap(find.text('キャンセル')); // Cancel
  await tester.pump();
  expect(find.text('$year-$month-$day'), findsOneWidget);
  expect(find.text('キャンセル'), findsNothing);

  // Change date
  await tester.tap(find.text('$year-$month-$day'));
  await tester.pump();
  await tester.tap(find.text('15'));
  await tester.tap(find.text('OK'));
  await tester.pump();
  expect(find.text("$year-$month-15"), findsOneWidget);

  await tester.tap(find.text('$year-$month-15'));
  await tester.pump();
  await tester.tap(find.text('1'));
  await tester.tap(find.text('OK'));
  await tester.pump();
  expect(find.text("$year-$month-01"), findsOneWidget);
});
...

이 테스트 코드에서는 버튼을 클릭하여 DatePicker를 표시한 후, 일본어로 표시된 취소(キャンセル) 버튼을 눌러 날짜의 변경없이 DatePicker를 닫는 테스트와 실제로 날짜를 클릭해서 날짜를 변경하는 테스트를 작성해 보았습니다.

완료

이것으로 showDatePicker를 사용하여 Flutter에서 달력을 표시하여 날짜를 변경하는 기능을 구현하는 방법에 대해서 알아보았습니다. 날짜를 변경이 필요한 경우, 날짜를 직접 수정하도록 만들기보다는 DatePicker를 표시하여 사용자가 좀 더 편하게 날짜를 변경할 수 있도록 만들어 보시길 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통
Posts