🎯 이번 단계에서 배울 것
StatefulWidget
과 State
클래스 구조 이해하기
- 위젯이 가진 상태값(
counter
)을 변수로 선언하는 방법 익히기
- 버튼 탭 시 상태를 변경하는 로직을 작성하기
- setState를 호출하지 않으면 UI가 갱신되지 않는다는 문제 인식하기
📂 파일 구조
1 2
| 수정되는 파일 - lib/main.dart (앱을 StatefulWidget으로 전환)
|
전체 코드 (lib/main.dart @ 3.0):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import 'package:flutter/material.dart';
void main() { runApp(App()); }
class App extends StatefulWidget { @override State<App> createState() => _AppState(); }
class _AppState extends State<App> { int counter = 0;
void onClicked() { counter = counter + 1; }
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( backgroundColor: const Color(0xFFF4EDDB), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Click Count', style: TextStyle(fontSize: 30), ), Text( '$counter', style: const TextStyle(fontSize: 30), ), IconButton( iconSize: 40, onPressed: onClicked, icon: const Icon( Icons.add_box_rounded, ), ), ], ), ), ), ); } }
|
🔍 코드 상세 설명
1. StatefulWidget 선언
1 2 3 4
| class App extends StatefulWidget { @override State<App> createState() => _AppState(); }
|
StatefulWidget
은 화면이 다시 그려질 때 상태를 유지합니다.
createState
에서 상태 전용 클래스인 _AppState
를 반환합니다.
2. 상태 보관 변수를 State 클래스에 선언
1 2
| class _AppState extends State<App> { int counter = 0;
|
_AppState
는 App
과 짝을 이루는 상태 관리 클래스입니다.
counter
는 버튼을 누른 횟수를 저장하는 필드입니다.
3. 상태 변경 메서드 작성
1 2 3
| void onClicked() { counter = counter + 1; }
|
- 버튼이 눌렸을 때 호출되며, 숫자를 1 증가시킵니다.
- 아직
setState
를 호출하지 않아 UI는 바로 갱신되지 않습니다.
4. 상태값을 UI에 노출
- 문자열 보간을 이용해 현재 카운터 값을 화면에 보여줍니다.
비교 예시:
1 2 3 4 5 6
| class App extends StatelessWidget { ... }
class App extends StatefulWidget { ... } class _AppState extends State<App> { ... }
|
시각화:
1 2 3 4 5
| ┌───────────────┐ │ Click Count │ │ 0 │ ← counter │ [ + button ] │ └───────────────┘
|
📊 동작 흐름
1 2 3 4
| 1. 사용자가 + 버튼을 누르면 onClicked 실행 2. counter 변수가 1 증가하지만 3. setState가 없어 build()가 다시 호출되지 않음 4. 화면에는 여전히 마지막으로 그린 값이 남음
|
✅ 체크리스트
💡 연습 과제
counter = counter * 2;
로 바꿔보고 어떤 값이 저장되는지 확인하세요 (UI는 여전히 갱신되지 않습니다).
IconButton
대신 ElevatedButton
으로 바꾸고 onPressed
에 같은 함수를 연결해보세요.
counter
를 double
로 선언하면 어떤 변화가 생기는지 테스트하세요.
setState로 화면 다시 그리기
🎯 이번 단계에서 배울 것
setState
의 역할과 사용법 이해하기
- 상태 변경과 UI 갱신을 하나의 블록으로 묶기
- Closure 안에서 상태 변수를 안전하게 업데이트하기
📂 파일 구조
1 2
| 수정되는 파일 - lib/main.dart (setState 적용)
|
📝 1단계: onClicked에 setState 추가하기
1 2 3 4 5
| void onClicked() { setState(() { counter = counter + 1; }); }
|
🔍 코드 상세 설명
1. setState 호출 시점
setState
는 상태가 바뀌었다고 Flutter 프레임워크에 알려주는 메서드입니다.
- 전달한 콜백 실행이 끝나면 Flutter가
build()
를 다시 호출합니다.
2. 콜백 안에서 상태 수정
counter = counter + 1;
을 콜백 안에 넣어 상태 변경 범위를 명확히 합니다.
- 콜백 밖에서 상태를 바꾸면 프레임워크가 변화를 감지하지 못할 수 있습니다.
3. UI 갱신 확인
- 이제 버튼을 누를 때마다
Text('$counter')
가 최신 값으로 다시 렌더링됩니다.
- 핫리로드 없이도 실시간으로 숫자가 증가하는 것을 확인할 수 있습니다.
시각화:
1
| before: counter = 0 → setState 실행 → rebuild → 화면에 1 표시
|
📊 동작 흐름
1 2 3 4
| 1. onClicked → setState 시작 2. counter 값 증가 3. setState 콜백 종료 4. Flutter가 build() 호출, 새 counter 값을 Text에 반영
|
✅ 체크리스트
💡 연습 과제
setState(() => counter += 2);
처럼 축약 문법을 사용해보세요.
- counter가 10 이상이면 0으로 초기화하도록 조건문을 추가해보세요.
- setState 호출 전후에
print(counter)
를 찍어 값 변화를 콘솔에서 확인하세요.
List와 컬렉션 for로 여러 위젯 만들기
🎯 이번 단계에서 배울 것
- 리스트 상태(
List<int> numbers
)를 관리하는 방법 익히기
numbers.add(numbers.length)
로 연속 증가 값을 생성하기
- 컬렉션
for
문법을 사용해 Column
안에서 동적으로 위젯 생성하기
📂 파일 구조
1 2
| 수정되는 파일 - lib/main.dart (카운터 → 리스트 누적 방식으로 전환)
|
📝 1단계: 리스트 상태로 전환
전체 코드 (lib/main.dart @ 3.2):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class _AppState extends State<App> { List<int> numbers = [];
void onClicked() { setState(() { numbers.add(numbers.length); }); }
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( backgroundColor: const Color(0xFFF4EDDB), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Click Count', style: TextStyle(fontSize: 30), ), for (var n in numbers) Text('$n'), IconButton( iconSize: 40, onPressed: onClicked, icon: const Icon( Icons.add_box_rounded, ), ), ], ), ), ), ); } }
|
🔍 코드 상세 설명
1. 리스트 상태 선언
- 버튼을 누를 때마다 새 숫자를 리스트에 저장합니다.
numbers.length
를 추가하여 0,1,2,… 순서로 값이 쌓입니다.
2. setState에서 리스트 갱신
1
| numbers.add(numbers.length);
|
- 리스트의 길이를 그대로 추가하면 자연스럽게 증가하는 시퀀스를 만들 수 있습니다.
- setState가 리스트에 새 항목이 추가된 사실을 반영하게 합니다.
3. 컬렉션 for 문법
1
| for (var n in numbers) Text('$n')
|
- Dart 컬렉션 literal 안에서
for
문법을 사용할 수 있습니다.
- 리스트 길이에 따라
Text
위젯이 동적으로 늘어납니다.
비교 예시:
1 2 3 4
|
for (var n in numbers) Text('$n')
|
시각화:
(버튼을 누를수록 숫자 줄이 계속 추가됩니다.)
📊 동작 흐름
1 2 3 4
| 1. 버튼 클릭 → setState 실행 2. numbers에 현재 길이 값 추가 3. build()가 다시 호출되어 for 루프가 새로운 숫자를 포함한 Text 리스트 렌더링 4. 화면에 누적된 숫자가 순서대로 표시됨
|
✅ 체크리스트
💡 연습 과제
numbers.add(numbers.length * 2);
로 바꿔 짝수만 쌓아보세요.
ListView
로 교체해 스크롤 가능한 숫자 리스트를 만들어보세요.
- 리스트 길이가 5 이상이면 가장 오래된 값을 제거하도록 조건을 추가해보세요.
BuildContext와 Theme 활용
🎯 이번 단계에서 배울 것
ThemeData
와 TextTheme
으로 전역 스타일 정의하기
- 별도 위젯(
MyLargeTitle
)에서 Theme.of(context)
로 상위 테마 가져오기
BuildContext
가 위젯 트리를 탐색하는 열쇠라는 점 이해하기
📂 파일 구조
1 2
| 수정되는 파일 - lib/main.dart (Theme 적용 + 커스텀 위젯 분리)
|
📝 1단계: ThemeData 설정 및 커스텀 위젯 분리
전체 코드 (lib/main.dart @ 3.3):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class _AppState extends State<App> { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( textTheme: const TextTheme( titleLarge: TextStyle( color: Colors.red, ), ), ), home: Scaffold( backgroundColor: const Color(0xFFF4EDDB), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ MyLargeTitle(), ], ), ), ), ); } }
class MyLargeTitle extends StatelessWidget { const MyLargeTitle({ Key? key, }) : super(key: key);
@override Widget build(BuildContext context) { return Text( 'My Large Title', style: TextStyle( fontSize: 30, color: Theme.of(context).textTheme.titleLarge?.color, ), ); } }
|
🔍 코드 상세 설명
1. MaterialApp에 Theme 적용
1 2 3 4 5
| theme: ThemeData( textTheme: const TextTheme( titleLarge: TextStyle(color: Colors.red), ), ),
|
ThemeData
는 앱 전반에 사용할 색상·폰트 등을 정의합니다.
TextTheme.titleLarge
를 빨간색으로 지정해 커다란 제목 스타일을 통일합니다.
2. 커스텀 위젯 분리
1
| class MyLargeTitle extends StatelessWidget { ... }
|
- 제목 전용 위젯을 별도로 만들어 재사용성과 가독성을 높였습니다.
children: const [MyLargeTitle()],
로 const 생성자를 사용해 불필요한 리빌드를 막습니다.
3. BuildContext로 상위 테마 접근
1
| Theme.of(context).textTheme.titleLarge?.color
|
BuildContext
는 현재 위젯이 트리에서 어디 위치했는지 알려주는 객체입니다.
Theme.of(context)
를 통해 가장 가까운 Theme
위젯을 찾아옵니다.
?.
는 널 안전 연산자로, titleLarge
가 null이어도 에러 없이 처리합니다.
비교 예시:
1 2 3 4 5
| Text('$counter', style: TextStyle(color: Colors.black))
color: Theme.of(context).textTheme.titleLarge?.color
|
시각화:
1 2 3 4
| MaterialApp (ThemeData) └─ Scaffold └─ Column └─ MyLargeTitle → Theme.of(context) → TextTheme.titleLarge
|
📊 동작 흐름
1 2 3 4
| 1. MaterialApp이 ThemeData를 자식 위젯에 제공합니다. 2. Column이 MyLargeTitle 위젯을 렌더링합니다. 3. MyLargeTitle.build()가 전달받은 context로 Theme.of(context)를 호출합니다. 4. Theme에서 titleLarge 스타일을 읽어 Text에 반영합니다.
|
✅ 체크리스트
💡 연습 과제
ThemeData
에 primaryColor
나 appBarTheme
를 추가해 전역 스타일을 더 만들어보세요.
MyLargeTitle
안에서 Theme.of(context).textTheme.headlineMedium
등을 사용해 다른 스타일을 테스트하세요.
MediaQuery.of(context).size
를 출력해 BuildContext로 가져올 수 있는 다른 정보도 살펴보세요.
📚 전체 흐름 정리
- 앱을
StatefulWidget
으로 전환해 상태를 저장할 수 있게 했습니다.
setState
를 도입해 상태 변경이 즉시 UI에 반영되도록 수정했습니다.
- 숫자를 리스트로 누적하고 컬렉션
for
문법을 써서 동적 위젯 생성을 경험했습니다.
BuildContext
와 ThemeData
를 활용해 상위 위젯의 스타일을 하위 위젯에서 재사용했습니다.
✅ 최종 점검 리스트
출처 : https://nomadcoders.co/flutter-for-beginners