1. Flutter TikTok Clone - 인증(Authentication)
SignUp & Login Screen 기본 화면 만들기
🎯 이번 단계에서 배울 것
- Flutter 앱의 첫 화면 만들기
- 회원가입과 로그인 화면의 기본 구조
- 화면 간 이동(Navigation)하기
- 재사용 가능한 위젯 만들기
📂 파일 구조
이번 단계에서는 3개의 새로운 파일을 만듭니다:
1 | lib/ |
📝 1단계: 회원가입 화면 만들기 (sign_up_screen.dart)
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. SafeArea란?
1 | SafeArea(child: ...) |
- 스마트폰의 노치(화면 상단 카메라 부분)나 상태바를 피해서 UI를 안전하게 배치합니다
- SafeArea를 사용하지 않으면 텍스트가 노치에 가려질 수 있습니다
2. Padding이란?
1 | Padding( |
- 화면의 양 옆(좌우)에 20픽셀의 여백을 만듭니다
symmetric
은 “대칭적으로”라는 뜻입니다
3. Column이란?
1 | Column( |
- 자식 위젯들을 세로(↓)로 배치합니다
- Row는 가로(→)로 배치합니다
4. Navigator란?
1 | Navigator.of(context).push( |
- 새로운 화면으로 이동합니다
- 스마트폰의 “뒤로가기” 버튼을 누르면 이전 화면으로 돌아갑니다
📝 2단계: 로그인 화면 만들기 (login_screen.dart)
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
Navigator.pop()이란?
1 | Navigator.of(context).pop(); |
- 현재 화면을 닫고 이전 화면으로 돌아갑니다
- 스마트폰의 “뒤로가기” 버튼과 같은 동작입니다
📝 3단계: 버튼 위젯 만들기 (auth_button.dart)
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. FractionallySizedBox란?
1 | FractionallySizedBox( |
- 부모 위젯 크기의 비율로 자식 크기를 정합니다
widthFactor: 1
은 부모 너비의 100%를 차지합니다
2. BoxDecoration이란?
1 | BoxDecoration( |
- Container를 꾸미는 데 사용합니다
- 테두리, 배경색, 둥근 모서리 등을 설정할 수 있습니다
3. required 키워드란?
1 | AuthButton({ |
- 이 값을 반드시 전달해야 합니다
- 전달하지 않으면 에러가 발생합니다
📝 4단계: main.dart 수정하기
1 | import 'package:flutter/material.dart'; |
🎨 화면 미리보기
1 | ┌─────────────────────────┐ |
✅ 체크리스트
- SignUpScreen 파일을 만들었나요?
- LoginScreen 파일을 만들었나요?
- AuthButton 파일을 만들었나요?
- main.dart의 home을 SignUpScreen으로 변경했나요?
- “Log in”을 누르면 로그인 화면으로 이동하나요?
- 뒤로가기 버튼이 작동하나요?
💡 연습 과제
- 색상 바꾸기: 버튼의 테두리 색을 파란색으로 바꿔보세요
- 버튼 추가하기: “Continue with Twitter” 버튼을 추가해보세요
- 간격 조절하기: 버튼 사이의 간격을 늘려보세요 (Gaps 사용)
프로젝트 설정하기
🎯 이번 단계에서 배울 것
- const 키워드로 성능 개선하기
📝 코드 최적화 (sign_up_screen.dart)
1 | // 변경 전 |
1 | // 변경 전 |
🔍 const 키워드 설명
const란?
- 한번 만들어지면 절대 변하지 않는 값입니다
- Flutter는 const 위젯을 재사용해서 성능이 좋아집니다
예시로 이해하기:
1 | // const 없이 |
언제 const를 사용하나요?
- 절대 변하지 않는 UI 요소
- 예: 고정된 텍스트, 고정된 아이콘, 고정된 간격
언제 const를 사용하면 안 되나요?
- 변할 수 있는 값
- 예: 사용자 입력, API에서 받아온 데이터
✅ 체크리스트
- const 키워드를 추가했나요?
- 앱이 여전히 잘 작동하나요?
아이콘이 있는 버튼 만들기
🎯 이번 단계에서 배울 것
- 버튼에 아이콘 추가하기
- Font Awesome 아이콘 사용하기
- Stack을 사용해서 위젯 겹치기
📝 1단계: Font Awesome 패키지 확인 (pubspec.yaml)
1 | dependencies: |
📝 2단계: AuthButton에 아이콘 추가하기
전체 코드 (auth_button.dart):
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. Stack이란?
1 | Stack( |
- Stack은 위젯들을 겹쳐서 배치합니다
- 마치 종이를 겹쳐놓는 것과 같습니다
시각적 예시:
1 | ┌─────────────────────┐ |
2. Align이란?
1 | Align( |
- Stack 안에서 위젯의 위치를 정합니다
centerLeft
: 왼쪽 중앙center
: 정중앙centerRight
: 오른쪽 중앙
📝 3단계: SignUpScreen에서 아이콘 사용하기
1 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // 추가 |
📝 4단계: LoginScreen에도 아이콘 추가하기
1 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; |
🎨 화면 미리보기
1 | ┌────────────────────────────┐ |
✅ 체크리스트
- Font Awesome 패키지가 설치되어 있나요?
- 버튼에 아이콘이 표시되나요?
- 아이콘이 왼쪽에, 텍스트가 중앙에 있나요?
- 버튼 사이에 적절한 간격이 있나요?
💡 연습 과제
- 다른 아이콘 사용하기: Font Awesome 사이트에서 다른 아이콘을 찾아 사용해보세요
- 아이콘 크기 변경하기: FaIcon의 size 속성을 사용해보세요
- 아이콘 색상 바꾸기: FaIcon의 color 속성을 사용해보세요
화면 전환과 테마 설정
🎯 이번 단계에서 배울 것
- 버튼 클릭 시 화면 이동하기
- 앱 전체의 테마 설정하기
- AppBar 스타일 통일하기
📝 1단계: AuthButton에 클릭 기능 추가하기
1 | class AuthButton extends StatelessWidget { |
🔍 코드 설명
1. Function(BuildContext)? 란?
1 | final void Function(BuildContext)? _onTapAction; |
이것을 쉽게 풀어쓰면:
Function(BuildContext)
: BuildContext를 받아서 실행하는 함수void
: 함수가 아무것도 반환하지 않음?
: 이 값이 없을 수도 있음(null일 수도 있음)
2. ?. 연산자 (null-aware operator)
1 | _onTapAction?.call(context) |
_onTapAction
이 null이 아니면 실행- null이면 아무것도 하지 않음
- 에러를 방지합니다
예시로 이해하기:
1 | // _onTapAction이 null이 아닐 때 |
📝 2단계: UsernameScreen 만들기
1 | import 'package:flutter/material.dart'; |
📝 3단계: SignUpScreen에서 화면 이동 연결하기
1 | class SignUpScreen extends StatelessWidget { |
📝 4단계: 앱 전체 테마 설정하기 (main.dart)
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. ThemeData란?
- 앱 전체의 디자인 스타일을 한 번에 설정합니다
- 모든 화면에 같은 스타일이 적용됩니다
2. AppBarTheme 속성들:
1 | appBarTheme: const AppBarTheme( |
3. elevation이란?
1 | elevation: 0 // 그림자 없음 (평평함) |
- 숫자가 클수록 그림자가 진해집니다
- 0이면 완전히 평평합니다
🎨 화면 흐름
1 | SignUpScreen |
✅ 체크리스트
- “Use Phone or Email” 버튼을 클릭하면 UsernameScreen으로 이동하나요?
- AppBar가 흰색 배경에 검은색 텍스트로 표시되나요?
- AppBar에 그림자가 없나요?
- 뒤로가기 버튼이 작동하나요?
💡 연습 과제
- 테마 색상 바꾸기: seedColor를 다른 색으로 변경해보세요
- AppBar에 그림자 추가: elevation 값을 바꿔보세요
- 다른 화면 만들기: “Continue with Facebook” 버튼에도 화면을 연결해보세요
사용자명 입력 화면
🎯 이번 단계에서 배울 것
- 사용자 입력 받기 (TextField)
- 입력값에 따라 화면 업데이트하기 (StatefulWidget)
- 컨트롤러로 입력값 관리하기
- 애니메이션 적용하기
📝 1단계: UsernameScreen을 StatefulWidget으로 변경
🤔 StatelessWidget vs StatefulWidget
StatelessWidget (상태 없음):
1 | class MyWidget extends StatelessWidget { |
StatefulWidget (상태 있음):
1 | class MyWidget extends StatefulWidget { |
전체 코드 (username_screen.dart):
1 | import 'package:flutter/material.dart'; |
🔍 코드 상세 설명
1. TextEditingController란?
1 | final TextEditingController _userNameController = TextEditingController(); |
- TextField의 입력값을 관리합니다
- 입력값을 읽거나, 지우거나, 설정할 수 있습니다
사용 예:
1 | _userNameController.text // 현재 입력된 텍스트 가져오기 |
2. initState()란?
1 |
|
- 위젯이 화면에 처음 나타날 때 한 번만 실행됩니다
- 초기 설정을 여기서 합니다
3. addListener()란?
1 | _userNameController.addListener(() { |
- 입력값이 변경될 때마다 실행됩니다
setState()
를 호출하면 화면이 다시 그려집니다
동작 과정:
1 | 1. 사용자가 "A"를 입력 |
4. dispose()란?
1 |
|
- 위젯이 화면에서 사라질 때 실행됩니다
- 컨트롤러를 반드시 정리해야 메모리 누수가 없습니다
왜 중요할까요?
1 | // dispose 하지 않으면 |
5. setState()란?
1 | setState(() { |
- 상태(State)가 변경되었다고 Flutter에게 알립니다
- Flutter는 build() 메서드를 다시 실행합니다
6. AnimatedContainer란?
1 | AnimatedContainer( |
- Container의 속성이 변하면 자동으로 애니메이션이 적용됩니다
- duration: 애니메이션 시간
예시:
1 | 입력 전: [회색 버튼] |
7. TextField의 decoration이란?
1 | TextField( |
🎨 동작 흐름
1 | ┌────────────────────────┐ |
📊 상태 변화 시각화
1 | 1. 초기 상태 |
✅ 체크리스트
- TextField에 글자를 입력할 수 있나요?
- 입력하면 버튼 색이 바뀌나요?
- 입력을 모두 지우면 버튼이 다시 회색이 되나요?
- 색 변화가 부드럽게 애니메이션 되나요?
💡 연습 과제
글자수 표시: 입력한 글자수를 화면에 표시해보세요
1
Text('${_userName.length} / 20')
글자수 제한: 20자까지만 입력되게 해보세요
1
2
3TextField(
maxLength: 20,
)텍스트 스타일 변경: 입력되는 글자의 크기나 색을 바꿔보세요
커서 색상 변경: cursorColor를 다른 색으로 바꿔보세요
재사용 가능한 버튼 위젯
🎯 이번 단계에서 배울 것
- 공통 버튼 위젯 만들기
- 코드 중복 제거하기
- 다음 화면으로 데이터 전달하기
- 함수를 메서드로 분리하기
📂 새로운 파일
1 | lib/features/authentication/widgets/ |
📝 1단계: FormButton 위젯 만들기
form_button.dart 전체 코드:
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. AnimatedDefaultTextStyle이란?
1 | AnimatedDefaultTextStyle( |
- Text의 스타일이 변하면 자동으로 애니메이션됩니다
- 색상, 크기, 굵기 등이 부드럽게 변합니다
2. final 키워드란?
1 | final bool disabled; |
- 한 번 값이 정해지면 절대 변하지 않습니다
const
와의 차이:const
: 컴파일 시점에 값이 정해짐final
: 실행 시점에 값이 정해짐
예시:
1 | const int age = 10; // 항상 10 |
📝 2단계: UsernameScreen에서 FormButton 사용하기
수정된 username_screen.dart:
1 | import 'package:flutter/material.dart'; |
🔍 코드 설명
1. _buildUserNameInput() 메서드
1 | TextField _buildUserNameInput(BuildContext context) { |
왜 메서드로 분리할까요?
- build() 메서드가 너무 길어지는 것을 방지
- 코드를 읽기 쉽게 만듦
- 재사용 가능
이름 규칙:
_build...
: 위젯을 만드는 메서드_on...Tap
: 클릭 이벤트 처리 메서드_
: private (이 파일 안에서만 사용)
2. early return 패턴
1 | void _onNextTap() { |
장점:
- 코드가 간결해짐
- 중첩된 if문을 피할 수 있음
비교:
1 | // ❌ 중첩된 if (읽기 어려움) |
📝 3단계: EmailScreen 만들기
email_screen.dart:
1 | import 'package:flutter/material.dart'; |
🎨 화면 흐름
1 | UsernameScreen |
📊 FormButton 재사용의 장점
재사용 전 (코드 중복):
1 | // UsernameScreen |
재사용 후 (간결):
1 | // UsernameScreen |
✅ 체크리스트
- UsernameScreen에서 Next 버튼이 작동하나요?
- EmailScreen이 표시되나요?
- 두 화면 모두 버튼 색이 입력에 따라 변하나요?
- 버튼의 텍스트 색도 함께 변하나요?
💡 연습 과제
- 버튼 텍스트 변경 가능하게: FormButton에 text 파라미터를 추가해보세요
- 애니메이션 시간 변경: duration을 바꿔서 빠르게/느리게 만들어보세요
- 버튼 모양 바꾸기: borderRadius를 조절해서 더 둥글게 만들어보세요
- 전화번호 입력 화면: EmailScreen을 참고해서 PhoneScreen을 만들어보세요
이메일 입력과 유효성 검사
🎯 이번 단계에서 배울 것
- 이메일 형식 검사하기 (정규식)
- 에러 메시지 표시하기
- 키보드 타입 설정하기
- 키보드 닫기
- 화면 터치 시 키보드 자동 닫기
📝 1단계: constants.dart 파일 만들기
constants/constants.dart:
1 | export 'gaps.dart'; |
🔍 export란?
1 | // constants.dart |
장점:
1 | // export 사용 전 |
📝 2단계: EmailScreen 완성하기
전체 코드 (email_screen.dart):
1 | import 'package:flutter/material.dart'; |
🔍 코드 상세 설명
1. 정규식(RegExp)이란?
1 | final regExp = RegExp( |
정규식은 문자열 패턴을 표현하는 방법입니다.
쉽게 풀어쓰면:
1 | ^ 시작 |
검증 예시:
1 | "test@example.com" ✅ 통과 |
2. hasMatch() 메서드
1 | if (!regExp.hasMatch(_email)) { |
hasMatch()
: 문자열이 정규식 패턴과 일치하는지 확인!
: not (반대)- “일치하지 않으면” 에러 메시지 반환
3. FocusScope.unfocus()란?
1 | void _onScaffoldTap() { |
- 현재 포커스를 제거 → 키보드가 내려감
- 사용자 경험(UX) 개선
동작 예시:
1 | 1. TextField 클릭 → 키보드 올라옴 |
4. keyboardType이란?
1 | TextField( |
다양한 키보드 타입:
1 | TextInputType.emailAddress // 이메일 (@포함) |
5. autocorrect란?
1 | TextField( |
- 이메일 주소는 자동으로 수정되면 안 됩니다!
- 예: “gmail”이 “Gmail”로 바뀌면 안 됨
6. onEditingComplete란?
1 | TextField( |
- 키보드의 “완료”, “다음”, “검색” 버튼을 눌렀을 때 실행
- 엔터키를 누른 것과 같음
7. errorText란?
1 | decoration: InputDecoration( |
null
이면: 에러 없음 (정상)"문자열"
이면: 에러 메시지 표시
동작 예시:
1 | 입력: "" |
📝 3단계: PasswordScreen 기본 틀 만들기
password_screen.dart:
1 | import 'package:flutter/material.dart'; |
🎨 이메일 검증 흐름
1 | 1. 입력: "" |
📱 키보드 동작
1 | ┌──────────────────────┐ |
✅ 체크리스트
- 이메일 형식이 틀리면 에러 메시지가 보이나요?
- 올바른 이메일을 입력하면 Next 버튼이 활성화되나요?
- 키보드가 이메일 타입으로 표시되나요? (@ 포함)
- 화면을 터치하면 키보드가 내려가나요?
- 키보드의 완료 버튼이 작동하나요?
💡 연습 과제
다른 정규식 시도하기: 더 간단한 이메일 검증 정규식을 만들어보세요
1
RegExp(r"^.+@.+\..+$") // 가장 간단한 형태
에러 메시지 다국어: 한글 에러 메시지를 추가해보세요
1
return "이메일 형식이 올바르지 않습니다";
이메일 도메인 확인: 특정 도메인만 허용하도록 만들어보세요
1
2
3if (!_email.endsWith("@gmail.com")) {
return "Gmail만 사용 가능합니다";
}대문자 자동 변환 끄기: TextField에 다음을 추가해보세요
1
textCapitalization: TextCapitalization.none,
비밀번호 입력 화면
🎯 이번 단계에서 배울 것
- 비밀번호 입력 필드 만들기
- 비밀번호 보이기/숨기기 기능
- 실시간 비밀번호 강도 검사
- 정규식으로 복잡한 조건 검증하기
- TextField suffix 사용하기
📂 새로운 파일
1 | lib/constants/ |
📝 1단계: PasswordScreen 완성하기
전체 코드 (password_screen.dart):
1 | import 'package:flutter/material.dart'; |
🔍 코드 상세 설명
1. obscureText란?
1 | TextField( |
true
: 비밀번호가 점(•)으로 표시됨false
: 비밀번호가 그대로 보임
2. 복잡한 정규식 이해하기
1 | r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$" |
각 부분 설명:
1 | ^ 시작 |
검증 예시:
1 | "password" ❌ (대문자, 숫자, 특수문자 없음) |
3. suffix란?
1 | decoration: InputDecoration( |
- TextField의 오른쪽 끝에 위젯을 배치합니다
- prefix: 왼쪽 끝
- suffix: 오른쪽 끝
시각화:
1 | ┌────────────────────────┐ |
4. MainAxisSize.min이란?
1 | Row( |
MainAxisSize.max
: 가능한 모든 공간 차지 (기본값)MainAxisSize.min
: 자식들의 크기만큼만 차지
비교:
1 | // MainAxisSize.max (기본값) |
5. !_isPasswordValid()의 의미
1 | FormButton(disabled: !_isPasswordValid()) |
!
: not (반대)_isPasswordValid()
: true면 유효, false면 무효!_isPasswordValid()
: true면 무효, false면 유효
논리:
1 | 비밀번호 유효 → _isPasswordValid() = true |
📝 2단계: BirthdayScreen 빈 파일 만들기
birthday_screen.dart:
1 | import 'package:flutter/material.dart'; |
🎨 비밀번호 검증 흐름
1 | 1. 입력: "" |
📱 비밀번호 필드 동작
1 | ┌──────────────────────────┐ |
눈 아이콘 클릭 후:
1 | ┌──────────────────────────┐ |
✅ 체크리스트
- 비밀번호가 점(•)으로 표시되나요?
- 눈 아이콘을 클릭하면 비밀번호가 보이나요?
- X 버튼을 클릭하면 입력값이 지워지나요?
- 조건을 만족하면 체크 아이콘이 초록색으로 변하나요?
- 유효한 비밀번호일 때 Next 버튼이 활성화되나요?
💡 연습 과제
개별 조건 체크: 각 조건(글자수, 대문자, 소문자, 숫자, 특수문자)을 개별적으로 체크하는 함수를 만들어보세요
1
2bool _hasMinLength() => _password.length >= 8;
bool _hasUpperCase() => _password.contains(RegExp(r'[A-Z]'));비밀번호 강도 표시: 약함/보통/강함을 색으로 표시해보세요
비밀번호 확인 필드: 비밀번호 확인 입력 필드를 추가하고 일치 여부를 확인해보세요
실시간 힌트: 입력 중에 부족한 조건을 빨간색으로 표시해보세요
생년월일 선택 화면
🎯 이번 단계에서 배울 것
- iOS 스타일 날짜 선택기 사용하기
- CupertinoDatePicker 사용법
- DateTime 다루기
- TextField를 읽기 전용으로 만들기
- BottomAppBar에 위젯 배치하기
📝 1단계: BirthdayScreen 완성하기
전체 코드 (birthday_screen.dart):
1 | import 'package:flutter/cupertino.dart' show CupertinoDatePicker, CupertinoDatePickerMode; |
🔍 코드 상세 설명
1. DateTime이란?
1 | DateTime initialDateTime = DateTime.now(); // 현재 날짜와 시간 |
DateTime 속성들:
1 | DateTime now = DateTime.now(); |
DateTime 만들기:
1 | DateTime birthday = DateTime(2000, 1, 15); // 2000년 1월 15일 |
2. .toString().split(“ “).first 설명
1 | final textDate = date.toString().split(" ").first; |
단계별 분해:
1 | DateTime date = DateTime(2025, 10, 5, 18, 30); |
시각화:
1 | DateTime(2025, 10, 5, 18, 30) |
3. TextEditingValue란?
1 | _birthdayController.value = TextEditingValue(text: textDate); |
- TextField의 값을 직접 설정합니다
_birthdayController.text = textDate
와 비슷하지만 더 안전합니다
4. readOnly란?
1 | TextField( |
- 사용자가 키보드로 직접 입력할 수 없습니다
- 날짜 선택기로만 값을 변경할 수 있습니다
- 키보드가 올라오지 않습니다
5. CupertinoDatePicker 속성들
1 | CupertinoDatePicker( |
mode 옵션들:
1 | CupertinoDatePickerMode.date // 날짜만 (년/월/일) |
6. import 구문 설명
1 | import 'package:flutter/cupertino.dart' show CupertinoDatePicker, CupertinoDatePickerMode; |
show
: 특정 클래스만 import- 전체를 import하지 않고 필요한 것만 가져옵니다
- 파일 크기를 줄이고 충돌을 방지합니다
비교:
1 | // 전체 import (무거움) |
📝 2단계: InterestsScreen 빈 파일 만들기
onboarding/interests_screen.dart:
1 | import 'package:flutter/material.dart'; |
📝 3단계: main.dart 수정
main.dart:
1 | theme: ThemeData( |
useMaterial3를 false로 하는 이유:
- CupertinoDatePicker가 Material 3에서 제대로 작동하지 않을 수 있습니다
- 일관된 디자인을 위해 Material 2를 사용합니다
🎨 화면 구성
1 | ┌──────────────────────────┐ |
📊 날짜 선택 흐름
1 | 1. 화면 로드 |
✅ 체크리스트
- 화면 로드 시 오늘 날짜가 표시되나요?
- TextField를 클릭해도 키보드가 올라오지 않나요?
- 날짜 선택기에서 날짜를 바꾸면 TextField가 업데이트되나요?
- 오늘 이후의 날짜는 선택할 수 없나요?
- iOS 스타일의 날짜 선택기가 표시되나요?
💡 연습 과제
최소 연령 제한: 13세 이상만 가입 가능하도록 만들어보세요
1
DateTime maximumDate = DateTime.now().subtract(Duration(days: 365 * 13));
날짜 형식 변경: “2025-10-05” 대신 “2025년 10월 5일”로 표시해보세요
1
"${date.year}년 ${date.month}월 ${date.day}일"
나이 표시: 선택한 생년월일로 현재 나이를 계산해서 표시해보세요
1
int age = DateTime.now().year - selectedDate.year;
날짜 선택기 높이 조절: BottomAppBar의 height를 바꿔보세요
로그인 폼 만들기
🎯 이번 단계에서 배울 것
- Form과 FormState 사용하기
- TextFormField로 입력 검증하기
- 여러 입력값을 한 번에 검증하기
- GlobalKey 사용하기
- Map으로 데이터 저장하기
📝 1단계: FormButton에 텍스트 파라미터 추가
수정된 form_button.dart:
1 | import 'package:flutter/material.dart'; |
사용 예:
1 | FormButton(disabled: false) // "Next" 표시 |
📝 2단계: LoginFormScreen 완성하기
전체 코드 (login_form_screen.dart):
1 | import 'package:flutter/material.dart'; |
🔍 코드 상세 설명
1. GlobalKey
1 | final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); |
- Form의 상태(State)를 제어하는 열쇠입니다
- 이 Key로 Form의 메서드를 호출할 수 있습니다
Key 사용법:
1 | _formKey.currentState?.validate() // 모든 필드 검증 |
2. Form 위젯이란?
1 | Form( |
- 여러 TextFormField를 그룹으로 묶습니다
- 모든 필드를 한 번에 검증/저장할 수 있습니다
3. TextFormField vs TextField
TextField:
1 | TextField( |
TextFormField:
1 | TextFormField( |
4. validator란?
1 | validator: (value) { |
반환값의 의미:
1 | return null; // 검증 통과 ✅ |
5. onSaved란?
1 | onSaved: (value) { |
validate()
가 통과한 후에만 호출됩니다- 입력한 값을 저장하는 곳입니다
6. Map이란?
1 | final Map<String, String> _formData = {}; |
- Key-Value 쌍으로 데이터를 저장합니다
Map<Key타입, Value타입>
예시:
1 | { |
7. ?. 연산자 (null-safe)
1 | _formKey.currentState?.validate() |
currentState
가 null이 아니면validate()
호출- null이면 아무것도 하지 않음
- 에러를 방지합니다
8. ?? 연산자 (null coalescing)
1 | final isValid = _formKey.currentState?.validate() ?? false; |
- 왼쪽 값이 null이면 오른쪽 값 사용
validate()
가 null이면 false 사용
📝 3단계: LoginScreen에서 연결하기
수정된 login_screen.dart:
1 | import 'login_form_screen.dart'; // 추가 |
SignUpScreen도 동일하게 수정:
1 | bottomNavigationBar: BottomAppBar( |
🎨 Form 검증 흐름
1 | 1. 사용자가 입력 |
📊 데이터 흐름
1 | TextFormField (이메일) |
✅ 체크리스트
- 빈 값으로 Login 버튼을 누르면 에러 메시지가 표시되나요?
- 올바른 값을 입력하면 콘솔에 데이터가 출력되나요?
- 비밀번호 보이기/숨기기 버튼이 작동하나요?
- LoginScreen에서 “Use Email & password”를 누르면 LoginFormScreen으로 이동하나요?
💡 연습 과제
비밀번호 확인: 비밀번호 확인 필드를 추가하고 일치 여부를 검증해보세요
1
2
3
4
5
6String? _confirmPassword(String? value) {
if (value != _formData['password']) {
return "Passwords don't match";
}
return null;
}로그인 유지: “Remember me” 체크박스를 추가해보세요
비밀번호 찾기: “Forgot password?” 링크를 추가해보세요
실시간 검증: TextFormField의
autovalidateMode
를 사용해보세요1
autovalidateMode: AutovalidateMode.onUserInteraction,
스플래시 화면 추가
🎯 이번 단계에서 배울 것
- 앱 시작 시 표시되는 스플래시 화면 만들기
- Timer를 사용한 자동 화면 전환
- Navigator.pushReplacement 사용하기
- mounted 속성 이해하기
- Transform.translate로 위젯 이동하기
📝 1단계: SplashScreen 만들기
전체 코드 (splash_screen.dart):
1 | import 'dart:async'; |
🔍 코드 상세 설명
1. Timer란?
1 | Timer(Duration(seconds: 2), () { |
- 지정된 시간 후에 코드를 실행합니다
- 딱 한 번만 실행됩니다 (반복 아님)
다른 사용 예:
1 | Timer(Duration(milliseconds: 500), () { }); // 0.5초 후 |
2. mounted란?
1 | if (mounted) { |
- 위젯이 여전히 화면에 있는지 확인합니다
true
: 화면에 있음false
: 화면에서 사라짐
왜 필요할까?
1 | // mounted 체크 없이 |
3. Navigator.pushReplacement란?
1 | Navigator.pushReplacement( |
- 현재 화면을 새 화면으로 교체합니다
- 뒤로가기 버튼을 눌러도 이전 화면으로 돌아갈 수 없습니다
비교:
1 | // push: 화면 추가 |
언제 사용할까?
- 스플래시 화면 → 메인 화면
- 로그인 화면 → 홈 화면
- 온보딩 화면 → 메인 화면
4. Transform.translate란?
1 | Transform.translate( |
- 위젯의 위치를 이동시킵니다
- 실제 공간은 차지하지 않고 겉보기만 이동합니다
Offset 설명:
1 | Offset(x, y) |
TikTok 로고 효과 원리:
1 | 1. 청록색 아이콘: 왼쪽 위 (-3, -3) |
5. letterSpacing이란?
1 | TextStyle( |
- 글자 사이의 간격을 조절합니다
- 양수: 간격 넓어짐
- 음수: 간격 좁아짐
예시:
1 | letterSpacing: 0 // TikTok Clone (기본) |
📝 2단계: main.dart 수정
main.dart:
1 | import 'package:flutter/material.dart'; |
🎨 스플래시 화면 시각화
1 | ┌──────────────────────────┐ |
📊 앱 시작 흐름
1 | 1. 앱 실행 |
✅ 체크리스트
- 앱을 실행하면 먼저 스플래시 화면이 보이나요?
- 2초 후 자동으로 SignUpScreen으로 이동하나요?
- TikTok 로고가 3D 효과로 보이나요?
- SignUpScreen에서 뒤로가기를 누르면 앱이 종료되나요? (스플래시로 돌아가지 않음)
💡 연습 과제
로딩 인디케이터 추가: 스플래시 화면에 로딩 애니메이션을 추가해보세요
1
2
3CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
)페이드 인 애니메이션: 로고가 서서히 나타나도록 만들어보세요
대기 시간 변경: Timer의 시간을 1초나 3초로 바꿔보세요
로고 회전: Transform.rotate를 사용해서 로고를 회전시켜보세요
실제 로고 사용: 이미지 파일을 추가해서 실제 TikTok 로고를 사용해보세요
🎉 완성! 전체 플로우 정리
📱 완성된 앱의 전체 흐름
1 | SplashScreen (2초) |
📂 최종 파일 구조
1 | lib/ |
🎓 배운 내용 요약
위젯
- StatelessWidget: 변하지 않는 UI
- StatefulWidget: 변하는 UI
- Scaffold: 화면의 기본 틀
- SafeArea: 안전 영역
- Column/Row: 세로/가로 배치
- Stack: 위젯 겹치기
- GestureDetector: 터치 감지
- AnimatedContainer: 자동 애니메이션
- Form: 폼 관리
입력 관련
- TextField: 기본 입력 필드
- TextFormField: 검증 기능 있는 입력 필드
- TextEditingController: 입력값 관리
- InputDecoration: 입력 필드 꾸미기
상태 관리
- setState(): 화면 다시 그리기
- initState(): 초기화
- dispose(): 정리
- mounted: 위젯 존재 여부
유효성 검사
- RegExp: 정규식
- validator: 입력 검증
- onSaved: 값 저장
- GlobalKey: Form 제어
Navigation
- Navigator.push(): 화면 추가
- Navigator.pop(): 화면 닫기
- Navigator.pushReplacement(): 화면 교체
기타
- Timer: 지연 실행
- DateTime: 날짜/시간
- CupertinoDatePicker: iOS 날짜 선택기
- Transform.translate: 위치 이동
- Theme: 앱 전체 스타일
🏆 최종 체크리스트
기본 기능
- 스플래시 화면이 2초간 표시된다
- 회원가입 화면에서 로그인 화면으로 이동할 수 있다
- 모든 입력 필드에서 입력이 가능하다
- 입력값이 유효하지 않으면 에러 메시지가 표시된다
- 모든 Next 버튼이 작동한다
UI/UX
- 버튼 색상이 입력 상태에 따라 변한다
- 비밀번호를 보이기/숨기기 할 수 있다
- 키보드가 적절한 타입으로 표시된다
- 화면을 터치하면 키보드가 내려간다
- 모든 애니메이션이 부드럽게 작동한다
데이터 검증
- 이메일 형식이 올바른지 검사한다
- 비밀번호 강도를 검사한다
- 빈 값으로는 진행할 수 없다
- 조건을 만족하면 체크 아이콘이 초록색으로 변한다
🎯 다음 단계 제안
추가 기능 구현
- 소셜 로그인: Firebase Authentication 연동
- 프로필 사진: 이미지 선택 기능
- 약관 동의: 체크박스와 약관 화면
- 이메일 인증: 인증 코드 전송/확인
개선 사항
- 에러 처리: try-catch로 안전하게
- 로딩 상태: 로딩 인디케이터 추가
- 다국어 지원: 한국어/영어 전환
- 다크 모드: 테마 전환 기능
고급 기능
- 상태 관리: Provider/Riverpod 도입
- API 연동: HTTP 통신
- 로컬 저장소: SharedPreferences
- 애니메이션: 화면 전환 애니메이션
📚 추가 학습 자료
Flutter 공식 문서
추천 패키지
provider
- 상태 관리dio
- HTTP 클라이언트shared_preferences
- 로컬 저장소firebase_auth
- Firebase 인증