모든 것은 Widget이다. Widget은 UI를 구성하는 가장 기본적인 단위이다. Flutter는 Widget으로 시작하여, Widget으로 끝난다고 해도 과언이 아니다.
Flutter는 Widget 구성으로 생성된 Layer tree를, Skia
라는 그래픽 라이브러리를 통해 랜더링한다.
Android, IOS, Chrome, Firefox 등 다양한 환경에서 공통 API를 통해 랜더링 할 수 있는 2D 오픈 소스 라이브러리이며,
Flutter의 Widget은, Stateless / Stateful Widget을 상속받아 만들어지며, 내부 Build 메서드를 통하여 Layer Tree를 만든다.
Stateful : Widget의 상태를 저장하여 지속적으로 추적 / 보존, State의 변화에 따라 상호작용하는 동적인 Widget
Stateless : 어떠한 상호작용도 없이 정적인 Widget
Stateless Widget은 단 한번의 Build 과정으로 화면이 계속 유지되며, Stateful Widget은 state의 변화(setState)가 발생할 때 마다 Build 과정이 일어나, 동적 화면을 구현할 수 있게 한다.
모든 flutter app은, main() 함수 내부에서 runApp() 메소드를 통해 Root Widget을 만드는 것으로 시작된다.
void main() {
runApp(MyApp( // Root Widget, Origin of Widget
app Router: //...
));
}
모든 Widget은 build 메소드를 통해 형성되며, 메소드 내부에서 BuildContext 클래스를 인가받는다.
void func1(int value){}
void func2(int value){}
//value at func1 != value at func2
Text()
,MaterialApp()
,MaterialButton()
... 와 같은 Widget은, 별도로 build() 메소드를 작성하지 않고 만들 수 있는데, 이 또한 내부적으로 build() 메소드를 호출함으로서 Widget이 생성된다.하지만, 위와 같은 Widget은 내부적으로 익명의 context를 갖기 때문에, 해당 widget에서 갖는 Child widget의 context 정보는 자신의 widget context가 아닌, 자기 자신의 부모의 context 정보를 넘겨주게 되는 것이다.
이 때, 만약 Child widget이 자기 자신의 context 정보를 갖게 하기 위해선, Builder
widget을 별도로 사용하여, 자신과 자기 자식의 widget 사이에 별도의 widget을 생성하게 하여, 그 context 정보를 넘겨줘야 한다. (아래 Widget tree 예시에서 참고)
Widget은 tree 계층 구조로 이루어져 있으며 Widget은 부모 / 자식을 가질 수 있다. (Parent widget = Widget container)
각 Widget은 어떻게 자기가 어느 위치에 있는지 알 수 있을까?
Widget은 내부 Build 메서드를 가지고 있으며, 이 때 BuildContext를 인자로 넘겨받는다. 이 BuildContext는 상호 Widget간의 위치정보를 담고 있으며, Navigator
,MediaQuery
, ListView
등 다양한 builder등에서 사용된다.
완전하게 자세하게는 코드분석을 할 수 없지만, 공식 Flutter 유투브에서 설명하는 바는 아래와 같다.
Widget은, Widget 클래스를 상속받거나, implement 받는데, Widget 클래스는 내부에 createElement() 함수를 실행한다. 이 때 각 상호 Widget간의 관계정보가 정의된 Element를 반환하게 되는것이다. Element는 BuildContext 타입을 상속받는 클래스로, 즉 BuildContext가 생성된다고 할 수 있다.
더 알아봐야 하겠지만, 특정 Widget에서 받는 BuildContext 인자는 자기 자신의 context 정보가 아닌, 부모 Widget의 Context를 받는 것 같다. 따라서 각 Widget의 context는, 수직적 tree 구조의 위치(부모 / 자식) 구조는 알 수 있으나, 수평적 구조(형제)의 위치는 알 수 없다.
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
Widget build(BuildContext MyPagecontext) {
return Scaffold( //Anonymous context
appBar: AppBar(
title: Text('Snack Bar'),
centerTitle: true,
),
body: Builder(builder: (BuildContext Buildercontext) {
return Center(
child: TextButton(
child: Text(
'Show me',
style: TextStyle(color: Colors.white),
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
),
onPressed: () {
Scaffold.of(Buildercontext).showSnackBar(
SnackBar(content: Text('Hello')));
},
),
);
}),
);
}
}
위 예시는, Mypage
자식으로 Scaffold
widget이 있는 간단한 예시이다. Scaffold
내부에선, Snackbar 기능을 활용하기 위해 flutter 2.0 이전 버젼에서 지원하는(deprecated now) Scaffold.of().showSnackBar()
메소드를 사용한다.Something.of(context)
는, 인가된 context
부모를 따라 Something
객체를 탐색한다.
Mypage
build 메소드 인자인 context
는 Mypage
의 context
이기 때문에 자식 widget인 Scaffold
의 정보를 알지 못하며, Scaffold.of().showSnackBar()
에 사용되는 context
는 Scaffold
자식 widget의 context
여야만 한다.
이를 위해, Scaffold
body에 Builder widget을 활용하여, 새로운 Buildercontext
인 Buildercontext를 정의, 이는 Scaffold
하위 Builder의 context
정보를 갖게 만들기 때문에 이를 활용하여, Scaffold
widget을 탐색할 수 있다.
Widget의 인스턴스를 생성할 때 인가받을 변수등을 정의할 수 있는 생성자.
Widget이 생성될 때, 상태의 초기화를 위해 한번만 호출되는 메소드.
InitState와 동일하게 최초 생성될 때 한번 호출 됨. InitState와는 다르게, Context에 접근할 수 있다.(InitState 단계에서는 아직 context가 생성되지 않음.) 따라서 대부분 Provider
, MediaQuery
등을 사용할 때 호출함.
화면에 표시 될 위젯등을 반환하며, 상태가 변화할 때 마다(setState()
) 다시 호출되게 된다.
따라서 build 내부에 쓸모 없는 연산 로직이 들어가계 될 경우, 앱의 부하가 상당히 증가할 수 있음을 알아야 함.
부모 위젯에 의해 rebuild 되어야 할 필요가 있을 때, build() 메소드 직전에 호출된다. 보통 부모 위젯의 변경으로 인해 애니메이션을 다시 실행할 필요가 있을 때 자주 사용되며, 이전 상태를 가지고 있는 oldWidget을 사용할 수 있다.
상태가 제거 되었을 때 deactivate가 호출되며, 위젯이 widget tree에서 제거되었을 경우 dispose가 호출된다.