관리 메뉴

나만을 위한 블로그

[Flutter] Stateless, Stateful 위젯의 생명주기 본문

Flutter

[Flutter] Stateless, Stateful 위젯의 생명주기

참깨빵위에참깨빵 2024. 8. 5. 22:39
728x90
반응형

이 포스팅에선 두 종류 위젯들의 생명주기를 확인한다. 관련 공식문서는 아래를 참고했다.

 

https://api.flutter.dev/flutter/widgets/State-class.html

 

State class - widgets library - Dart API

The logic and internal state for a StatefulWidget. State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that th

api.flutter.dev

 

https://docs.flutter.dev/ui#responding-to-widget-lifecycle-events

 

Building user interfaces with Flutter

Introduction to user interface development in Flutter.

docs.flutter.dev

 

먼저 Stateless 위젯이다. 상태 비저장 위젯이라고도 하는 이 위젯은 build()라는 하나의 생명주기 함수만 갖고 있다.

Stateless 위젯은 생성된 후 내부 상태가 변하지 않기 때문에 사용자에게 UI를 렌더링해서 표시하는 build() 하나만 갖고 있는 것이다.

 

다음은 Stateful 위젯이다. 상태를 다루는 위젯인 만큼 여러 생명주기 함수를 갖고 있다.

아래는 Stateful 위젯의 생명주기 함수들의 호출 순서를 도식화한 그림이다.

 

https://dev.to/pranjal-barnwal/the-journey-of-a-widget-understanding-the-lifecycle-in-flutter-3plp

 

createState

 

가장 먼저 호출되는 메서드는 createState()인 걸 볼 수 있다. 플러터 프레임워크가 위젯의 State 인스턴스를 만들어 리턴하는 역할을 하기 때문에 이 메서드는 반드시 존재해야 한다.

플러터로 UI를 만들어 봤다면 이 함수는 이미 본 적이 있다. Stateful 위젯을 만들 때 자동완성되는 코드들 중 createState()를 호출하는 부분이 있다.

 

class MyApp extends StatefulWidget {

  @override
  State<MyApp> createState() => _MyAppState();
}

 

MyApp은 Stateful 위젯으로 선언되서 상태를 스스로 관리할 수 있고, createState()는 위젯 상태를 저장하는 _MyAppState 인스턴스를 리턴한다.

공식문서에선 뭐라고 설명하는지 확인한다.

 

https://api.flutter.dev/flutter/widgets/StatefulWidget/createState.html

 

createState method - StatefulWidget class - widgets library - Dart API

State createState() Creates the mutable state for this widget at a given location in the tree. Subclasses should override this method to return a newly created instance of their associated State subclass: @override State createState() => _SomeWidgetState()

api.flutter.dev

트리의 지정된 위치에 이 위젯에 대한 변경 가능한 상태를 만든다. 서브클래스는 이 메서드를 재정의해서 관련된 State 서브클래스의 새로 생성된 인스턴스를 리턴해야 한다
프레임워크(플러터)는 Stateful 위젯의 생명주기 동안 이 메서드를 여러 번 호출할 수 있다. 위젯이 트리의 여러 위치에 삽입된 경우 프레임워크는 각 위치에 별도의 State 객체를 만든다. 마찬가지로 위젯이 트리에서 제거됐다가 나중에 트리에 다시 삽입되면 프레임워크는 createState()를 재호출해서 새 State 객체를 만들어 State 객체의 생명주기를 간소화한다

 

initState

 

위젯 트리에 삽입될 때 initState()가 한 번 호출된다. 위젯이 만들어져서 위젯 트리에 처음 삽입될 때 클래스 생성자가 호출된 후 호출되기 때문에 컨트롤러, 리스너 설정, 초기 데이터 불러오기 같은 1회성 초기화 또는 애니메이션 시작, 객체 생성, 구독을 설정하는 등 상태 변수를 초기화하거나 설정할 때 사용한다.

 

https://api.flutter.dev/flutter/widgets/State/initState.html

 

initState method - State class - widgets library - Dart API

void initState() Called when this object is inserted into the tree. The framework will call this method exactly once for each State object it creates. Override this method to perform initialization that depends on the location at which this object was inse

api.flutter.dev

객체가 트리에 삽입될 때 호출된다. 프레임워크는 생성하는 각 State 객체에 대해 이 메서드를 한 번 호출한다
이 객체가 트리에 삽입된 위치(즉 컨텍스트) 또는 이 객체 구성에 사용된 위젯, State의 build()가 자체적으로 상태를 바꿀 수 있는 객체(ChangeNotifier, Stream)나 알림을 수신하기 위해 구독할 수 있는 다른 객체에 의존하는 경우 initState, didUpdateWidget, dispose에서 제대로 구독, 구독 취소를 수행해야 한다

- initState에서 객체를 구독
- 업데이트된 위젯 구성으로 인해 객체를 바꿔야 하는 경우 didUpdateWidget에서 이전 객체의 구독을 취소하고 새 객체를 구독
- dispose에서 객체 구독 취소

이 메서드에서 BuildContext.dependOnInheritedWidgetOfExactType을 사용하면 안 된다. 그러나 이 메서드 바로 다음에 didChangeDependencies가 호출되고, 여기서 BuildContext.dependOnInheritedWidgetOfExactType을 쓸 수 있다. 이 메서드의 구현은 super.initState() 같이 상속된 메서드를 호출하는 것으로 시작해야 한다

 

super.initState()는 initState()를 자동완성으로 구현한다면 반드시 생기기 때문에 이걸 지우지 않기만 하면 된다.

참고로 핫 리로드를 수행하면 reassemble()이 호출되는데 이 메서드는 initState()를 호출해서 모든 데이터를 초기화한다. reassemble()의 문서는 아래를 참고한다.

 

https://api.flutter.dev/flutter/widgets/State/reassemble.html

 

reassemble method - State class - widgets library - Dart API

void reassemble() Called whenever the application is reassembled during debugging, for example during hot reload. This method should rerun any initialization logic that depends on global state, for example, image loading from asset bundles (since the asset

api.flutter.dev

 

didChangeDependencies

 

https://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html

 

didChangeDependencies method - State class - widgets library - Dart API

void didChangeDependencies() Called when a dependency of this State object changes. For example, if the previous call to build referenced an InheritedWidget that later changed, the framework would call this method to notify this object about the change. Th

api.flutter.dev

State 객체의 종속성이 바뀔 때 호출된다. 이전에 빌드 호출이 나중에 변경된 inheritedWidget을 참조한 경우 프레임워크는 이 메서드를 호출해서 이 객체에 변경사항을 알린다. 이 메서드는 initState 직후 호출되기도 한다
이 메서드에서 BuildContext.dependOnInheritedWidgetOfExactType을 호출하는 게 안전하다. 프레임워크는 종속성이 바뀐 후에 항상 빌드를 호출하므로 서브클래스가 이 메서드를 재정의하는 경우는 거의 없다
일부 서브클래스가 이 메서드를 재정의하는 이유는 종속성이 바뀔 때 네트워크 가져오기 등 비용이 많이 드는 작업을 수행해야 하는데, 이 작업을 모든 빌드에 수행하기엔 비용이 너무 많이 들기 때문이다

 

이 메서드는 initState() 직후에 바로 호출되는 메서드로 위젯이 의존하는 데이터를 가진 객체가 호출될 때마다 호출된다.

업데이트되는 위젯을 상속한 경우, 비용이 많이 드는 State 객체의 종속성이 변경될 때, 네트워크 호출이 필요한 때 유용하다.

 

build

 

https://api.flutter.dev/flutter/widgets/State/build.html

 

build method - State class - widgets library - Dart API

Widget build(BuildContext context ) Describes the part of the user interface represented by this widget. The framework calls this method in a number of different situations. For example: This method can potentially be called in every frame and should not h

api.flutter.dev

이 위젯으로 표시되는 UI 일부를 설명한다. 프레임워크는 여러 상황에서 이 메서드를 호출한다

- initState 호출 후
- didUpdateWidget 호출 후
- setState 호출을 받은 후
- State 객체의 종속성이 바뀐 후(이전 빌드에서 참조한 InheritedWidget이 바뀌는 등)
- deactive 호출 후 다른 위치의 트리에 State 객체를 다시 삽입하는 경우

이 메서드는 잠재적으로 모든 프레임에서 호출될 수 있으며 위젯 빌드 이상의 사이드 이펙트가 없어야 한다. 프레임워크는 이 메서드가 리턴한 위젯이 기존 서브트리의 루트를 업데이트할 수 있는지 여부에 따라 기존 서브트리를 업데이트하거나 서브트리를 제거하고 새 서브트리를 부풀려서 이 위젯 아래의 서브트리를 위젯으로 대체한다(위젯 호출에 따라 결정됨)
일반적으로 구현은 이 위젯의 생성자, 지정된 BuildContext 및 State 객체 내부 상태의 정보로 구성된 새로 생성된 위젯 배열을 리턴한다. 지정된 BuildContext에는 이 위젯이 빌드되는 트리 위치 정보가 포함된다...(중략)

 

여기서 내가 작성한 위젯들의 빌드가 이뤄진다. 이 메서드는 위젯을 렌더링해야 할 때마다 호출되고 이 안에서 State 변수에 접근, 업데이트해서 UI를 동적으로 변경하거나 화면에 표현하는 처리가 이뤄진다.

 

didUpdateWidget

 

https://api.flutter.dev/flutter/widgets/State/didUpdateWidget.html

 

didUpdateWidget method - State class - widgets library - Dart API

void didUpdateWidget(covariant T oldWidget ) Called whenever the widget configuration changes. If the parent widget rebuilds and requests that this location in the tree update to display a new widget with the same runtimeType and Widget.key, the framework

api.flutter.dev

위젯 구성이 바뀔 때마다 호출된다. 부모 위젯이 재빌드되고 트리의 이 위치가 동일한 런타임 유형, Widget.Key를 가진 새 위젯을 표시하도록 요청하면 프레임워크는 새 위젯을 참조하도록 State 객체의 위젯 속성을 업데이트한 후 이전 위젯을 인수로 써서 이 메서드를 호출한다
위젯이 바뀔 때 응답하려면(암시적 애니메이션 시작 등) 이 메서드를 재정의한다. 프레임워크는 항상 이 메서드를 호출한 후 build()를 호출하므로 이 메서드에서 setState()를 호출하는 건 중복된다. build()가 자체적으로 상태를 바꿀 수 있는 객체(ChangeNotifier, Stream, 알림을 수신하도록 구독할 수 있는 다른 객체 등)에 의존한다면 initState, didUpdateWidget, dispose에서 제대로 구독 / 구독 취소를 해야 한다

- initState에서 객체 구독
- 업데이트된 위젯 구성으로 인해 객체를 바꿔야 한다면 didUpdateWidget에서 이전 객체의 구독을 취소하고 새 객체 구독
- dispose에서 객체 구독 취소

 

위젯의 부모가 바뀌면(버튼의 글자, 색 변경 등) 이 메서드를 통해 알림을 받는다. 이후 업데이트에 따라 모양을 바꿀 수 있다.

즉 위젯의 구성이 변경될 때(부모가 새 데이터를 전달할 때 등) 호출된다고 볼 수 있고, 이전 상태와 비교하기 위해 위젯 인스턴스를 매개변수로 받는다. 상태를 업데이트하거나 구성 변경에 따른 작업을 수행할 때 사용한다.

 

setState

 

https://api.flutter.dev/flutter/widgets/State/setState.html

 

setState method - State class - widgets library - Dart API

void setState(VoidCallback fn ) Notify the framework that the internal state of this object has changed. Whenever you change the internal state of a State object, make the change in a function that you pass to setState: setState(() { _myState = newValue; }

api.flutter.dev

객체의 내부 상태가 바뀌었음을 프레임워크에 알린다. State 객체의 내부 상태를 바꿀 때마다 setState에 전달하는 함수에서 변경을 수행한다. 
setState(() { _myState = newValue; });
제공된 콜백은 즉시 동기적으로 호출된다. 실제로 상태가 언제 설정됐는지 불분명하므로 콜백은 Future를 리턴하면 안 된다. 콜백은 비동기일 수 없다. setState()를 호출하면 이 서브트리의 UI에 영향을 줄 수 있는 방식으로 이 객체의 내부 상태가 바뀌었음을 프레임워크에 알리고, 프레임워크는 State 객체에 대한 빌드를 예약하게 된다
이 메서드 없이 상태를 직접 바꾸면 프레임워크가 빌드를 예약하지 않고 서브트리의 UI가 새 상태를 반영하도록 업데이트되지 않을 수 있다. 일반적으로 이 메서드는 변경과 연관될 수 있는 계산이 아닌 상태의 실제 변경을 래핑할 때만 쓰는 게 좋다...(중략)

 

이 메서드는 위젯의 내부 상태가 바뀌어서 업데이트해서 최신 상태를 보여줘야 한다고 프레임워크에 알리기 위해 사용한다. 상태를 바꿀 때 이 메서드를 쓰지 않으면 값은 변하더라도 UI는 최신화되지 않으니 적절하게 사용해야 한다.

 

deactivate

 

https://api.flutter.dev/flutter/widgets/State/deactivate.html

 

deactivate method - State class - widgets library - Dart API

void deactivate() Called when this object is removed from the tree. The framework calls this method whenever it removes this State object from the tree. In some cases, the framework will reinsert the State object into another part of the tree (e.g., if the

api.flutter.dev

객체가 트리에서 제거될 때 호출된다. 프레임워크는 이 상태 객체를 트리에서 제거할 때마다 이 메서드를 다시 호출한다. 경우에 따라 프레임워크는 State 객체를 트리의 다른 곳에 다시 삽입한다. 이 경우 프레임워크는 activate()를 호출해 상태 객체가 비활성화될 때 해제했던 리소스를 다시 얻을 기회를 준다. 그 다음 State 객체가 트리의 새 위치에 적응할 기회를 주기 위해 build()도 호출한다
프레임워크가 서브트리를 다시 삽입하는 경우 서브트리가 트리에서 제거된 애니메이션 프레임이 끝나기 전에 삽입한다. 따라서 State 객체는 프레임워크가 dispose()를 호출할 때까지 대부분의 리소스 해제를 연기할 수 있다. 서브클래스는 이 메서드를 재정의해서 이 객체와 트리의 다른 요소 간 링크를 정리해야 한다. 이 메서드의 구현은 super.deactivate() 같이 상속된 메서드 호출로 끝나야 한다

 

위젯이 스크롤 등의 이유로 화면에 더 이상 표시되지 않으면(트리에서 일시적으로 제거될 때) 이 메서드가 호출된다. 여기선 애니메이션의 일시 정지나 나중에 복원할 수 있도록 상태를 저장하는 등의 정리 작업을 수행할 수 있다.

 

dispose

 

https://api.flutter.dev/flutter/widgets/State/dispose.html

 

dispose method - State class - widgets library - Dart API

void dispose() Called when this object is removed from the tree permanently. The framework calls this method when this State object will never build again. After the framework calls dispose, the State object is considered unmounted and the mounted property

api.flutter.dev

객체가 트리에서 영구적으로 제거될 때 호출된다. 프레임워크는 이 State 객체가 다시 빌드되지 않을 때 이 메서드를 호출한다. dispose()가 호출되면 State 객체는 마운트 해제된 것으로 간주되고, 마운트된 프로퍼티는 false가 된다. 이 시점에서 setState()를 호출하는 것은 오류다. 생명주기에서 이 단계는 종료 단계이므로 폐기된 State 객체를 다시 마운트할 순 없다. 서브클래스는 이 메서드를 재정의해서 이 객체가 가진 모든 리소스를 해제해야 한다(활성 애니메이션 중지 등)
State의 build()가 ChangeNotifier 또는 Stream 같이 상태를 바꿀 수 있는 객체 또는 알림을 수신하도록 구독할 수 있는 다른 객체에 의존하는 경우 initState, didUpdateWidget, dispose에서 제대로 구독 / 구독 취소를 수행해야 한다. 이 메서드의 구현은 super.dispose() 같이 상속된 메서드 호출로 끝나야 한다...(중략)...앱이 언제 종료될지 예측할 순 없다. 배터리에 불이 나거나 기기를 수영장에 떨어뜨리거나 OS가 메모리 압박 때문에 일방적으로 종료할 수 있따. 앱은 이런 예기치 않은 갑작스런 종료에도 제대로 작동해야 할 책임이 있다. 전체 위젯 트리를 인위적으로 폐기하려면 Sizedbox.shrink 등의 위젯으로 runApp()을 호출하라. 플랫폼 종료 메시지를 수신하려면 AppLifecycleListener API 사용을 고려하라

 

위젯의 생명주기에서 마지막에 호출되는 메서드다. 위젯이 위젯 트리에서 영구적으로 제거되면 이 메서드가 호출되며 갖고 있던 모든 리소스를 해제할 수 있다.

 

 

참고한 사이트)

 

https://www.dhiwise.com/post/unveiling-the-flutter-widget-lifecycle

 

Understanding Flutter Widget Lifecycle

Dive into the fascinating world of Flutter widget lifecycle with examples.

www.dhiwise.com

 

반응형

'Flutter' 카테고리의 다른 글

[Flutter] AppBar란? AppBar 사용법  (0) 2024.08.06
[Dart] 반복문  (0) 2024.08.06
[Flutter] Future, async / await를 통한 비동기 처리 방법  (0) 2024.07.24
[Dart] final vs const  (0) 2024.07.22
[Dart] 생성자(constructor) 알아보기  (0) 2024.07.21
Comments