Flutter Basic Chapter 4 - Pomodoro 타이머 앱 만들기
UI 레이아웃과 화면 분리
🎯 이번 단계에서 배울 것
MaterialApp
에 테마를 적용하고 화면을 별도 위젯으로 분리하는 방법 이해Scaffold
+Flexible
+Expanded
조합으로 세로 레이아웃 설계- 테마 색상(
backgroundColor
,cardColor
,textTheme
)을 통해 일관된 스타일 적용 - 새로운 화면 파일(
lib/screens/home_screen.dart
)을 생성하고HomeScreen
을 루트로 사용
📂 파일 구조
1 | 수정되는 파일 |
📝 1단계: 테마 정의와 화면 분리
전체 코드 (lib/main.dart):
1 | import 'package:flutter/material.dart'; |
- 배경색(연한 빨강), 카드색(밝은 베이지),
headline1
컬러(남색)를 한 번에 정의해 후속 위젯이 재사용하도록 했습니다. HomeScreen
을 const 생성자로 호출해 불필요한 리빌드를 방지합니다.
📝 2단계: HomeScreen UI 뼈대 작성
핵심 코드 (lib/screens/home_screen.dart):
1 | class HomeScreen extends StatefulWidget { |
🔍 코드 상세 설명
- Flexible/Expanded:
Column
세 영역에flex
값을 부여해 타이머, 버튼, 통계 영역의 비율을 제어합니다. - Theme 재사용: 배경색·카드색·텍스트 색 모두
Theme.of(context)
에서 가져와 테마 값과 동기화합니다. - StatefulWidget 도입: 추후 타이머 상태를 저장하기 위해
HomeScreen
을 StatefulWidget으로 시작합니다(아직 상태 필드는 없음).
시각화:
1 | ┌──────────────────────────────┐ |
📊 동작 흐름
1 | 1. App → MaterialApp → ThemeData 설정 |
✅ 체크리스트
-
lib/screens
폴더에home_screen.dart
가 생성됐는가? - MaterialApp에서
home: const HomeScreen()
을 사용했는가? - Flexible 영역 비율이 1:3:1로 구성됐는가?
- UI 전체가 정의한 테마 색상을 사용하고 있는가?
💡 연습 과제
headline1
색상을 다른 색으로 바꿔 카드 텍스트 색 변화 확인하기- 하단 카드에
borderRadius
를 추가해 모서리를 둥글게 만들기 - 타이머 텍스트를 중앙 정렬이 아니라 좌측 정렬로 바꿔 UI 변화를 비교하기
Timer.periodic으로 카운트다운 구현
🎯 이번 단계에서 배울 것
dart:async
의Timer.periodic
으로 1초마다 콜백 실행하기- 타이머 상태(
totalSeconds
)를 감소시키고 setState로 UI 갱신하기 - 타이머 핸들을
late Timer
로 선언하고onTick
콜백으로 추출하기 - 타이머 시작 버튼에 실제 동작 연결하기
📂 파일 구조
1 | 수정되는 파일 |
📝 1단계: 타이머 상태와 콜백 추가
1 | class _HomeScreenState extends State<HomeScreen> { |
totalSeconds
는 25분(1500초)부터 내려가기 시작하는 타이머 값입니다.Timer.periodic
이 1초마다onTick
을 호출하며, 콜백 안에서setState
로 카운트다운 값을 갱신합니다.timer
를late
로 선언해 나중에 초기화하되, 반드시 타이머 생성 후 접근하도록 합니다.
📝 2단계: UI에서 남은 시간 출력
1 | Text( |
- 타이머 숫자를 그대로 출력해 정상적으로 감소하는지 확인합니다(아직 포맷팅 전 단계).
- 중앙의
IconButton
은onStartPressed
에 연결해 재생 버튼을 누르면 타이머가 시작됩니다.
📊 동작 흐름
1 | 1. 사용자가 재생 버튼을 누름 → onStartPressed 실행 |
✅ 체크리스트
-
dart:async
가 최상단에 import되어 있는가? -
Timer.periodic
이 Duration(seconds: 1)로 생성되는가? - 타이머 시작 후 숫자가 1초마다 감소하는가?
- 타이머를 멈추지 않아도 안전하게 동작하는가? (아직 종료 로직은 없음)
💡 연습 과제
totalSeconds
초기값을 5로 줄여 빠르게 타이머 완료를 테스트해보세요.Timer.periodic
대신Future.delayed
로 재귀 호출을 구현해 비교해보세요.- 남은 시간이 0이 되었을 때 경고 로그를 출력하도록 조건문을 추가하세요.
재생/일시정지 토글 구현
🎯 이번 단계에서 배울 것
- 타이머 실행 여부를 추적하는 불리언 상태(
isRunning
) 추가 - 타이머 중지 시
Timer.cancel()
을 호출해 자원 누수를 막기 - 하나의 버튼에서 재생/일시정지 아이콘과 동작을 토글하기
📂 파일 구조
1 | 수정되는 파일 |
📝 1단계: 상태 변수와 로직 확장
1 | bool isRunning = false; |
- 타이머 시작/정지 시
isRunning
값을 갱신하여 UI와 상태를 동기화합니다. - 타이머를 중지할 때는 반드시
timer.cancel()
을 호출해야 콜백이 더 이상 실행되지 않습니다.
📝 2단계: 단일 버튼에서 토글 구현
1 | IconButton( |
isRunning
이 true면 일시정지 아이콘과 핸들러를, false면 재생 아이콘과 핸들러를 사용합니다.- 단일 위젯으로 상태에 따라 동작이 바뀌는 것을 경험할 수 있습니다.
📊 동작 흐름
1 | 1. isRunning이 false → 재생 버튼 표시 → onStartPressed 실행 |
✅ 체크리스트
-
isRunning
기본값이 false로 설정되어 있는가? - 타이머를 정지하면 setState가 호출되는가?
- 버튼 아이콘/동작이 즉시 토글되는가?
- 타이머가 여러 번 start/pause 되어도 오류 없이 동작하는가?
💡 연습 과제
- 타이머가 멈췄을 때 배경색을 살짝 바꿔 상태 변화를 강조해보세요.
isRunning
대신 enum(TimerStatus.idle/running
)을 사용해 상태를 확장해보세요.- pause 후 resume 시 남은 시간을 유지하는지 확인하고, resume 버튼을 별도로 만들어보세요.
남은 시간 포맷과 포모도로 누적
🎯 이번 단계에서 배울 것
- 상수(
static const twentyFiveMinutes
)로 변하지 않는 값 관리하기 - 타이머 완료 시 포모도로 횟수(
totalPomodoros
) 증가 및 타이머 리셋 Duration
과 문자열 조작으로MM:SS
형식의 타이머 표시 만들기- README를 간단한 강의 안내 문구로 정리 (프로젝트 소개 업데이트)
📂 파일 구조
1 | 수정되는 파일 |
📝 1단계: 상수와 누적 상태 추가
1 | static const twentyFiveMinutes = 1500; |
- 초기 25분 값을 상수로 선언해 재사용합니다.
- 타이머가 끝나면
totalSeconds
를 상수 값으로 다시 세팅하고,totalPomodoros
를 1 증가시킵니다.
1 | void onTick(Timer timer) { |
- 남은 시간이 0이면 타이머를 멈추고 초기 상태로 복원한 뒤 누적 횟수를 증가시킵니다.
- 그렇지 않으면 기존과 같이 1초씩 감소시킵니다.
📝 2단계: 시간 포맷 함수 도입
1 | String format(int seconds) { |
Duration
객체를 문자열로 변환하면0:25:00.000000
형식이 나오므로,split
과substring
으로25:00
부분만 추출합니다.- 빌드 메서드에서는
format(totalSeconds)
를 호출해 항상MM:SS
형식으로 표시합니다.
1 | Text( |
📝 3단계: 누적 포모도로 출력
1 | Text( |
- 하단 카드에 누적 포모도로 수를 표시해 사용자에게 성취감을 제공합니다.
- README.md는 다음과 같이 간단한 소개와 강의 링크만 남기도록 정리했습니다:
1 | # Toonflix |
📊 동작 흐름
1 | 1. 타이머 시작 → onTick이 매초 totalSeconds 감소 |
✅ 체크리스트
-
twentyFiveMinutes
상수가 선언되고 재사용되는가? - 타이머가 0이 되면 자동으로 멈추고 값이 초기화되는가?
- 시간 표시가
25:00
형식으로 나온다는 것을 확인했는가? - 포모도로 누적 숫자가 증가하는가?
- README가 간결한 소개와 강의 링크만 포함하도록 업데이트됐는가?
💡 연습 과제
- 5분 휴식 타이머를 추가하고 완료 시 교차로 실행되도록 확장해보세요.
format
함수에서substring
대신padLeft
를 사용해 같은 결과를 만들어보세요.- 포모도로가 목표치에 도달하면 알림 다이얼로그를 띄우도록 기능을 추가해보세요.
📚 전체 흐름 정리
- 테마 적용과 화면 분리로 Pomodoro UI의 골격을 세웠습니다.
Timer.periodic
을 도입해 1초 간격으로 남은 시간이 감소하도록 만들었습니다.- 재생/일시정지 토글을 구현해 타이머 제어가 가능해졌습니다.
- 남은 시간을
MM:SS
형식으로 포맷하고, 타이머 완료 시 포모도로 횟수를 누적하도록 완성했습니다.
✅ 최종 점검 리스트
- 메인 앱이 HomeScreen을 불러오고 테마를 재사용하는가?
- Timer가 정상적으로 시작/일시정지/완료를 처리하는가?
- 남은 시간이 형식화되어 있고, 포모도로 누적 수가 표시되는가?