플러터는 화면에 표시되는 UI를 위젯이란 개념을 사용하여 나타낸다. 이미지, 텍스트표시, 버튼, 레이아웃 배치 속성 등 전부 위젯이다.
플러터의 다양한 공식 위젯 종류를 한번에 보려면 여기로(위젯 카탈로그(widget catalog))
종류별로 보려면 여기로
위젯을 소개하기에 앞서, 플러터 애플리케이션의 생명 주기에 관한 정보는 다음과 같다.
다양한 위젯의 종류 중에서 크게 StatelessWidget 과 StatefulWidget 이 있다.
StatelessWidget
상태를 가지지 않는 위젯, 즉 위젯이 변화에 무감각하다는 것을 의미한다.
최초 한 번 build()함수를 통해 만들어진 뒤 변하지 않는다.
생명주기
flowchart TB
1["constructor"] --> 2["build()"]
StatefulWidget
생명주기
flowchart LR
subgraph StatefullWidget
1[constructor] --> 2["createState()"]
end
StatefullWidget--> 11
subgraph State
11["mounted = true"] --> 3["initState()"] --> 4["didChangeDependencies()"] --> 13["dirty = true"] --> 5["build()"] --> 14["dirty = false"] --> 7["deactivate()"] --> 9["dispose()"] --> 12["mounted = false"]
14 --> 6["didUpdateWidget()"] & 8["setState()"] --> 13
end
subgraph InheritedWidget
10["State Changed"] --> 4
end
style 11 fill:#f96
style 12 fill:#f96
style 13 fill:#f96
style 14 fill:#f96
상태 변경이 없는 경우의 생명주기
※번외로, hot reload가 실행될 때 마다 호출되는 reassemble() 함수가 있다. 이 함수가 실행되면 최초 생성자부터 다시 생명주기가 시작된다.
생성자의 매개변수가 변경됐을 때 생명주기
자체적으로 build()를 재실행할 때 생명주기
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
StatefulWidget을 상속받는 State<T> 클래스는 전역변수로 context를 가지고 있어서 클래스 어디서든 context에 접근할 수 있다.
class MyApp extends State<MyApp> {
@override
Widget build(BuildContext context) => //someCode;
void showDialog() {
showDialog();
//build 메서드 밖에서도 context 접근 가능
context;
}
//context 게터를 오버라이딩 할 수도 있다.
@override
BuildContext get context => super.context;
}
다만 예외적으로 initState 메서드 안에서는 context를 사용해도 컴파일 에러는 발생하지 않지만 런타임에서 에러가 발생한다.
initState에서는 State를 초기화하는 과정을 담고있기 때문에 context가 생성은 되지만 완전히 구성되지 않은 상태여서 코드가 실행은 되지만 접근 시 에러가 발생한다.
이 땐 트릭으로 Future.delayed를 사용하면 정상적으로 context에 접근할 수 있다.
@override
void initState() {
super.initState();
//Duration은 zero로 설정되어 있으나 실제 실행은 State초기화 함수가 끝난 상태에서 진행되기 때문에 context에 접근해도 에러가 발생하지 않는다.
Future.delayed(Duration.zero).then((value) => Theme.of(context).primaryColor,);
}
앱 실행시 최초 한번이 아니라 의존관계가 변경될 때 마다 접근하고자 할 때는 didChangeDependencies
함수에서 context를 사용하면 된다.
@override
void didChangeDependencies() {
super.didChangeDependencies();
final color = Theme.of(context).primaryColor);
}
StatefulWidget이나 StatelessWidget 클래스는 Build 메서드 안에서만 context에 접근할 수 있다.
Build에서 매개변수로 사용하는 BuildContext는 상위 위젯의 context라는 점을 명심해야 한다.
class MyApp extends State<MyApp> {
@override
Widget build(BuildContext context) => Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
//에러가 발생한다.
Scaffold.of(context).toString();
},
)
);
}
Scaffold.of() called with a context that does not contain a Scaffold.
라는 에러가 발생한다.
.of()
구문은 위젯트리에서 부모 위젯들을 타고 올라가면서 가장 가까운 객체를 찾는다. 여기서 Scaffold.of()
는 부모트리 중 가장 가까운 Scaffold 위젯을 찾겠다는 의미다.
하지만 여기서 Scaffold.of()
에 주어진 context는 부모 위젯의 context이다. 이 상황에서는 MyApp → MaterialApp 위젯트리의 context이며 여기서 아무리 타고 올라가도 Scaffold 위젯은 만들어진적이 없기 때문에 Scaffold.of()
은 적합한 위젯을 찾지 못해 에러를 발생시키는 것이다.
이런 경우에 정상적으로 작동하기 위해서 Builder 위젯이 존재한다.
Builder 위젯은 부모 위젯의 context를 받아 의도적으로 위젯 트리의 한 계층을 만들어서 context가 올바른 위젯을 찾을 수 있게 해준다.
class _main_stateState extends State<main_state> {
@override
Widget build(BuildContext context) => Scaffold(
floatingActionButton: Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
Scaffold.of(context).toString();
},
);
},
),
);
}
이를 통해 Scaffold.of()
는 MyApp → MaterialApp → Scaffold 의 위젯트리를 가진 context를 갖게되고, Scaffold를 찾을 수 있기 때문에 에러가 발생하지 않고 정상적으로 작동한다.
State 생성 → 트리의 특정 위치를 참조하는 BuildContext 존재 → 해당 BuildContext에 연결 → 연결된 각 BuildContext에 위젯 배치(인스턴스화) → 요소(Element) 생성
플러터는 애플리케이션의 생명주기 상태를 표시하는 몇가지 변수를 가지고 있다.