서론
플러터의 위젯은 화면에 보이는 것보다 훨씬 더 많은 것들이 숨겨져 있습니다. 이번 글에서는 StatefulWidget의 라이프 사이클에 대해서 알아볼 것입니다.
StatefulWidget의 라이프 사이클
1. 클래스 선언
StatefulWidget을 상속하게 되면 자체적으로 상태를 관리할 수 있게 됩니다. 우리는 ButtonBuddy라는 StatefulWidget을 선언했다고 가정하겠습니다.
class ButtonBuddy extends StatefulWidget {
@override
_ButtonBuddyState createState() => _ButtonBuddyState();
}
2. 초기화(initState)
initState를 통해 해당 위젯은 초기화하게 됩니다. 이 메서드를 통해 내부 상태 값을 설정하고 인스턴스가 만들어집니다. 여기서 initState는 위젯 트리에 처음 삽입될 때 단 한번만 호출 된다는 것을 기억해야합니다.
class _ButtonBuddyState extends State<ButtonBuddy> {
bool _isPressed = false; // 버튼의 눌림 상태를 나타내는 상태 변수
@override
void initState() {
super.initState();
print("ButtonBuddy가 초기화되었습니다! 초기 상태: $_isPressed");
}
}
3. 빌드(build)
build는 해당 위젯이 화면에 보여질 요소들을 시각적으로 변환하는 곳입니다. build는 최초, 변수가 업데이트 됐을 때마다 위젯이 렌더링 되는 메서드입니다.
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => _isPressed = !_isPressed),
child: Text(_isPressed ? '나 눌렀어!' : '눌러봐!'),
);
}
4. didUpdateWidget
만약 위젯의 상태값이 변경될 경우, didUpdateWidget을 통해 알림을 받게 됩니다. 즉 위젯의 구성이 변경될 때마다 호출이 됩니다.
@override
void didUpdateWidget(ButtonBuddy oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.color != oldWidget.color) {
print("색상이 업데이트되었습니다! 새 색상: ${widget.color}");
}
}
5. didUpdateDependencies
Widget이 메모리 해제가 되지 않기 위해 다른 위젯에 종속되어 있는 경우가 있습니다. 만약 이런 종속성이 변경된다면, didUpdateDependencies가 호출됩니다. 이 메서드를 사용하면 부모 위젯이나 상속된 위젯과의 상호 작용을 조정하는 역할을 합니다.
@override
void didChangeDependencies() {
super.didChangeDependencies();
final theme = Theme.of(context);
print("테마가 변경되었습니다! 현재 테마: $theme");
}
6. deactivate
위젯이 화면에서 보여지지 않을 때(스크롤 하거나 화면이 전환됐거나..) deactivate가 호출됩니다. 이 메서드에서는 일시적으로 위젯 트리에서 제거될 때 호출되는데, 애니메이션을 중지하거나 상태를 저장하는 로직을 구현할 수 있습니다.
@override
void deactivate() {
super.deactivate();
print("ButtonBuddy가 비활성화되었습니다! 애니메이션 일시 중지...");
}
7. dispose
위젯 트리에서 영구적으로 제거될 때 dispose가 호출됩니다.
@override
void dispose() {
print("ButtonBuddy가 삭제되었습니다! 리소스 해제 중...");
super.dispose();
}
🔍 라이프 사이클 메서드 자세히 살펴보기
라이프 사이클의 모든 단계가 중요하지만, 특히 더 중요한 didUpdateWidget, didChangeDependencies를 조금 더 알아보겠습니다.
didUpdateWidget
여기서 MyWidget은 title을 매개변수로 받습니다. 만약 부모 위젯에서의 title이 변경된다면, _MyWidgetState내의 didUpdateWidget을 통해 새 제목과 이전 제목을 비교하여 setState를 호출하여 UI를 다시 렌더링합니다.
class MyWidget extends StatefulWidget {
final String title;
MyWidget({required this.title});
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.title != oldWidget.title) {
// 제목 변경에 대응하기, 예를 들어 UI 업데이트
setState(() {}); // 새 제목을 반영하기 위해 다시 빌드
}
}
@override
Widget build(BuildContext context) {
return Text(widget.title);
}
}
주요 사항은 다음과 같습니다.
- didUpdateWidget은 이전 위젯 인스턴스를 인자로 받아 비교할 수 있습니다.
- 핫 리로드 중에는 모든 위젯에서 didUpdateWidget이 제대로 호출되지 않을 수 있습니다. 이것을 해결하기 위해서는 WidgetsBindingObserver나 didChangeAppLifecycleState 메서드를 사용하여 업데이트를 해줘야할 수 있습니다.
didChangeDependencies
didChangeDependencies는 특정 위젯이 위젯 트리로 부터 데이터에 의존하는 경우 중요한 메서드가 될 수 있습니다. 즉 의존성의 변경을 대응하기 위한 메서드로 사용됩니다.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
ThemeData _currentTheme;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_currentTheme = Theme.of(context);
}
@override
Widget build(BuildContext context) {
return Text('Current theme: ${_currentTheme.primaryColor}');
}
}
주요 사항은 다음과 같습니다.
- initState 이후에 호출되고, 의존성이 변경될 때마다 호출됩니다.
- initState에서 사용할 수 없었던 다른 의존성에 접근하기 위해서 사용됩니다.
- 만약 비용이 많이 드는 로직이라면, 이 곳에 추가하지 않는 것이 좋습니다. 여러번 호출 될 수도 있습니다.