[Flutter] Flutter 로 웹툰 앱 만들기: State/buildContext/Widget Life Cycle
목차
- 요약
- 버튼 클릭 시 카운트 증가: Stateful Widget
- 버튼 클릭 시 Text ui 증가: StatefulWidget + 반복문
- buildContext: 부모에서 정의한 Theme을 자식이 사용하게 하기
- 버튼 클릭 시 ui(Text) 변경: Widget Life Cycle
1. 요약
StatelessWidget: build 메서드를 통해 ui 출력
StatefulWidget: 상태에 따라 데이터 변하고, 이에따라 ui도 변경
widget life cycle을 가지고 있다.
setState(): State클래스에서 데이터 변경됨을 알리는 함수, build가 다시 실행됨
BuildContext context: 부모 요소에 쉽게 접근 가능
2. 버튼 클릭 시 카운트 증가: Stateful Widget
class App extends StatelessWidget{...} // 에서 커맨트 + . 눌러 stateful widget으로 변경
=>
class App extends StatefulWidget{...}
앞에서는 StatelessWidget을 사용했는데, 커맨드 + . 을 눌러 StatefulWidget으로 변경해보면,
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {...}
}
이렇게 된다.
State<App>을 상속받은 _AppState를 보자.
여기서 상태를 관리하고, 상태에 따른 ui 변경을 해볼 것이다.
카운터 변수를 하나 만들고,
버튼 클릭 시 카운터가 하나 증가하는 함수를 만들어서
ui에 적용해주면 완성..!!
class _AppState extends State<App> {
int counter = 0; // 상태 변수(그냥 Dart 프로퍼티이다.)
// 클릭 시 변할 부분을 setState 안에 써준다.
void onClicked() {
setState(() {
// 카운트 1씩 증가
counter = counter + 1;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
...
body: Center(
child: Column(
...
children: [
...
// 카운터
Text('$counter'),
// 버튼에 클릭 메서드 달기
IconButton(
iconSize: 40,
onPressed: onClicked,
icon: const Icon(
Icons.add_box_rounded,
),
),
],
),
),
),
);
}
}
3. 버튼 클릭 시 Text ui 증가: StatefulWidget + 반복문
_AppState 내부에 number 배열을 만들고,
클릭 시 배열에 숫자를 추가도록 하는 함수를 만들다.
List<int> numbers = [];
void onClicked() {
setState(() {
numbers.add(numbers.length);
});
}
그리고 이걸 for문을 사용해 출력해주면 완성,,,,
for문만으로 ui가 아름답게 생성되다니..
for (var n in numbers) Text('$n'),
4. buildContext: 부모에서 정의한 Theme을 자식이 사용하게 하기
부모에서 Text color가 red인 Theme을 생성했다.
이 Theme 을 사용해 자식도 Text color를 변경하려고 한다.
이를 설명하기 위해 MyLargeTitle이라는 위젯을 만들었다.
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MaterialApp(
// theme 정의
theme: ThemeData(
textTheme: const TextTheme(
titleLarge: TextStyle(
color: Colors.red,
),
),
),
home: Scaffold(
// ...
body: Center(
child: Column(
// ...
children: const [
MyLargeTitle(),
],
),
),
),
);
}
}
자식(MyLargeTitle)에서는 아래와 같이 context를 통해 부모의 Theme에 접근할 수 있다.!
color: Theme.of(context).textTheme.titleLarge?.color)
자식의 전체 코드는 이러함.
class MyLargeTitle extends StatelessWidget {
const MyLargeTitle({
super.key,
});
@override
Widget build(BuildContext context) {
return Text(
'My Large Title',
style: TextStyle(
// color 부분 주목!!
fontSize: 30, color: Theme.of(context).textTheme.titleLarge?.color),
);
}
}
5. 버튼 클릭 시 ui(Text) 변경: Widget Life Cycle
Widget에는 아래와 같은 주요 Life Cycle이 있다.
간단하게 설명하면
- initState: 초기화 목적. 한 번만 불림
- build: 위젯 생성
- dispose: 위젯 사라질 때 불림
_AppState의 변수로 제목을 보일지 안 보일지 정하는 변수를 만들고,
이 변수를 true-false로 변하게 하는 함수를 만들어 IconButton에 적용해준다.
bool showTitle = true;
void toggleTitle() {
setState(() {
showTitle = !showTitle;
});
}
그리고 위젯 클래스에
class _MyLargeTitleState extends State<MyLargeTitle> {
// 부모 요소에 의존하는 데이터를 초기화하는 경우 사용, api 업데이트 등...
// 한 번만 호출됨
@override
void initState() {
super.initState();
print('init');
}
// 스크린에서 위젯이 사라질 때 호출됨, api 업데이트, 이벤트 리스너 구독 취소
// 등 무언가를 취소하는 곳...
@override
void dispose() {
super.dispose();
print('dispose');
}
@override
Widget build(BuildContext context) {
print('build');
return Text(
'My Large Title',
style: TextStyle(
fontSize: 30, color: Theme.of(context).textTheme.titleLarge?.color),
);
}
}
출력문은 다음과 같다.
// <엡 시작>
init
build
// <눈 버튼 클릭: 제목 사라짐>
dispose
// <눈 버튼 클릭: 제목 생김>
init
build
// <눈 버튼 클릭: 제목 사라짐>
dispose
느낀 점
- 더 많은 라이프 사이클이 있을텐데 멀까, 안드랑 비슷할까
- 부모 Theme에 접근할 때 . 을 사용해 깊게 들어가는 건 너무 귀찮고 코드도 길어보이는데(물론 직관적이긴 함) 다른 간단한 방법 없을까?
- State가 달라질 때마가 ui를 재호출하는 방법은 안드의 그것과 너무나도 비슷하다. 더 간편해보이기도 한다.
- 비개발자가 보기에 좋을 듯한 강의다. 전공자는 더 심화 버전 들어도 ㄱㅊ을듯,,!