플러터 위젯(Widget)의 라이프 사이클을 알아보자

서론

플러터의 위젯은 화면에 보이는 것보다 훨씬 더 많은 것들이 숨겨져 있습니다. 이번 글에서는 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이 제대로 호출되지 않을 수 있습니다. 이것을 해결하기 위해서는 WidgetsBindingObserverdidChangeAppLifecycleState 메서드를 사용하여 업데이트를 해줘야할 수 있습니다.

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에서 사용할 수 없었던 다른 의존성에 접근하기 위해서 사용됩니다.
  • 만약 비용이 많이 드는 로직이라면, 이 곳에 추가하지 않는 것이 좋습니다. 여러번 호출 될 수도 있습니다.

 

Reference