State(상태)란 위젯에 대한 속성을 의미한다. 예를 들어서 Text위젯의 text를 상태라고 할 수 있고, style, fontWeight, color 등 해당 위젯이 가지고 있는 속성을 모두 상태라고 부른다. 이렇게 위젯은 모두 상태를 가지고 있고, 위젯은 위젯 트리에 의해서 관리되고 있다. 즉, state는 한마디로 상태를 다루는 객체들
이다.
Stateless widget도 State를 가지고, 한 번 생성되면 절대 바뀌지않는다. 즉, 위젯을 한 번만 그리고 다시 그리지 않는다. Stateless를 바꾸려면 완전히 파괴(destroy)하고, 다시 rebuild하는 방법 밖에 없다.
그래서 Stateless widget의 라이프 사이클은 매우 간단하다. 신경써야하는 부분은 build method 뿐이다. 즉, 매 번 Stateless widget이 build 될 때마다 build method가 호출되고, 이 build method 내에서 사용자가 원하는 내용의 stateless widget을 새롭게 생성하면 된다.
Stateful widget은 상태가 변경되면 build를 여러 번 하는 위젯이다. Stateful widget은 State object와 결합하게 된다. State object의 역할은 위젯의 구성요소나 위젯의 속성들을 지속적으로 추적하는 것이기때문에 사용자가 원하는 입 맛대로 위젯의 구성요소나 속성에 관련된 변수들을 setState 메서드를 사용해서 언제라도 업데이트 할 수 있다. Stateful widget은 화면을 여러 번 그리기 때문에 Stateless widget보다 관리하기 어렵고, 성능도 조금 낮아질 수 있다. 하지만, UI가 변경된 상태에 따라 업데이트가 되기 때문에 사용자에게 더 나은 경험을 선사해 줄 수 있다.
Stateful widgets live longer than Stateless widgets
Stateful 위젯은 Stateless 위젯보다 더 긴 생명주기를 갖는다
Flutter가 Stateful widget을 build 할 때, 처음으로 위젯의 constructor(생성자) function을 실행한다. 그리고나서, createState() method를 호출한다. Stateful widget에서는 constructor function이 처음으로 실행되는 것을 볼 수 있다. 반면에, Stateful widget의 State object에서는 State의 생명 주기가 createState() method가 호출될 때, 시작하는 것을 볼 수 있다.
constructor function은 실행될 때, widget 속성이 비어있는 상태이기때문에 생명주기의 한 부분을 차지하지는 않는다.
createState() method는 state object(객체)를 생성한다. 이 object는 해당 widget에 대한 모든 변경 가능한 state가 유지되는 곳이다. 이 method는 StatefulWidget 내에서 필요하다.
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
State object를 생성하면, 프레임워크는 mounted라는 boolean 속성을 true로 설정해서, State object를 BuildContext와 연결한다. 이 속성은 이 State object가 현재 위젯 트리에 있는지, 없는 지에 대한 정보를 제공한다.
이 단계는 생명 주기의 실제 단계로 표시되지않지만, background에서 진행 중인 작업을 아는 것이 중요하다.
object가 트리에 주입되면(mounted 속성은 true로 설정), initState()가 class constructor 다음으로 자동으로 실행된다. initState()는 state obejct가 처음 생성될 때, 한 번만 호출된다.
initState()에는 BuildContext를 사용할 수 없다.
Tip: initState()를 HTTP request를 관리하고, 위젯의 데이터를 변경할 수 있는 stream 구독, 알림변경 또는 기타 다른 object를 핸들링하는데 사용하자.
void initState() {
super.initState();
// TODO: implement initState
}
프레임워크는 initState() 다음으로 didChangeDependencies()를 호출한다. didChangeDependencies()는 위젯이 변화에 의존하는 object일 때도 호출된다. build method는 항상didChangeDependencies() 다음에 호출된다. 그래서, didChangeDependencies()은 거의 필요하지 않다. 하지만, didChangeDependencies()는 BuildContext.inheritFromWidgetOfExactType을 호출할 수 있는 첫 번째 method다.
build() method는 필수적이고, 생명 주기 동안 여러 번 호출되지만, 처음 호출되는 것은 didChangeDependencies() method가 호출된 다음이다. 따라서, state에 속한 위젯이 업데이트될 때마다 프레임워크는 항상 build() 메서드를 실행한다.(예, didUpdateWidget() 또는 setState() method가 호출될 때마다)
didUpdateWidget() method는 부모 위젯이 구성을 변경하고, 위젯을 다시 build해야하는 경우에 호출된다. 프레임워크는 이전 위젯을 새 위젯과 비교하는데 사용할 수 있는 argument를 준다. Flutter는 didUpdateWidget() 이후에 build() method를 호출한다.
새 위젯을 이전 위젯과 비교해야 하는 경우에 didUpdateWidget() 사용하자.
void didUpdateWidget(covariant MyHomePage oldWidget) {
super.didUpdateWidget(oldWidget);
// TODO: implement didUpdateWidget
}
setState() method는 자주 Flutter 프레임워크 자체와 개발자로부터 호출된다. setState() method는 현재 object 내부 상태가 "dirty"라는 것을 프레임워크에 알려준다. 즉, UI에 영향을 줄 수도 있는 방식으로 변경되었음을 의미한다. 이 알림 후에 프레임워크는 build() method를 호출해서 위젯을 업데이트하고 다시 build한다.
State object의 내부 상태를 변경할 때마다, setState() method에서 변경하자.
setState(() {
// implement setState
});
setState는 개발자가 호출하는 유일한 메서드이기 때문에 생명 주기 method의 단계로 표시하지않았다.
deactivate() method는 위젯 트리에서 위젯이 제거될 때 호출되지만, state가 위젯 트리의 한 지점에서 다른 지점으로 이동할 때, 현재 프레임 변경이 완료되기 전에 다시 주입될 수 있다. deactivate() method는 거의 사용되지 않는다.
void deactivate() {
super.deactivate();
// TODO: implement deactivate
}
dispose() method는 위젯 트리에서 state object가 영구적으로 제거될 때 호출된다.
data listeners 또는 life connections를 clean up하는 데에 dispose() method를 사용하자.
DataListener is an event listener interface that supports processing events produced by a corresponding DataProvider instance.
DataListener는 해당 DataProvider 인스턴스에서 생성된 이벤트 처리를 지원하는 이벤트 리스너 인터페이스다.
void dispose() {
super.dispose();
// TODO: implement dispose
}
dispose() method 다음에는 State object가 현재 트리에 없으므로 mounted 속성은 이제 false다. state object는 다시 mount할 수 없고, setState()가 호출되면 에러가 발생한다.
reassemble()는 hot reload를 실행할 때마다 호출된다. reassemble()이 호출된 이후에 위 그림처럼 생명주기가 차례대로 진행된다.