Widget build(BuildContext context) { ... }
호출 시점에, Layout의 크기를 확인하기 위해서 메서드를 호출했다.final GlobalKey _textKey = GlobalKey();
Widget build(BuildContext context) {
_measureTextWidth(); <--- 문제의 코드!!!
return Container(...);
}
void _measureTextWidth() {
final RenderBox renderBox
= _textKey.currentContext?.findRenderObject() as RenderBox;
if (renderBox != null) {
final size = renderBox.size;
print('Text Width: ${size.width}');
}
}
_measureTextWidth()
메서드는 GlobalKey
를 통해서 위젯트리에서 위젯을 찾는다. 그리고 그 위젯이 있을 경우에, size.width 값을 인쇄하는 로직이다.build의 호출마다 특정 로직을 호출시키고 싶은 상황이다. 그런데 그 상황에서 에러가 발생해서 이 글에 공유하고자 한다.
cf) GlobalKey
여기 코드에서 문제가 하나 있다.
Widget build(BuildContext context)
메서드는 위젯트리를 할 때, 호출되는 메서드이다. 즉, 레이아웃을 만들고 렌더링하는 설계도가 적혀있는 곳이다. 그곳에서 위젯을 찾는 코드가 있다.
_textKey.currentContext?.findRenderObject()
그리고 있는 도중에, 그려진 값에 대한 정보를 찾고 있는 것이다. 로직을 보면 다음과 같다.
1. build() 가 호출된다.
2. 그리기 위한 절차를 수행한다.
3. 절차 수행중에 findRenderObject()가 레이아웃에 대한 정보를 조회하려고한다.
4. 아직 그려진 상태가 아니므로, 조회할 수 없다. (실패)
실행하면 다음과 같은 에러가 출력된다.
Exception has occurred.
_TypeError (type 'Null' is not a subtype of type 'RenderBox' in type cast)
여기 에러는 단순한 TypeCasting 에러이다. 코드를 수정하면 위 에러는 안볼 수 있다.
그렇다고 해서, 원하는 동작이 호출되지는 않을 것이고, Null에 접근하는 부분에서 다시 에러가 발생할 것이다.
정리하면, build 메서드 호출횟수에 맞게 특정 메서드가 호출되어야 하는데, 거기서 호출하게되면 아직 레이아웃이 구성되지 않은 상태이므로 호출할 수 없다.
결론먼저
코드를 아래와 같이 수정하면, 에러가 없어진다.
코드
build(BuildContext context) {
WidgetBinding.instance
.addPostFrameCallback((_) => _measureTextWidth());
...
return Container();
}
Widget
출력결과
flutter: Text width: 164.8751983642578
flutter: Text width: 165.16001892089844
flutter: Text width: 160.7894287109375
flutter: Text width: 164.8751983642578
flutter: Text width: 163.7818603515625
호출하려던 코드를 WidgetBinding.instance.addPostFrameCallback
안에 파라미터로 전달하면 해결된다.
WidgetsBinding.instance.addPostFrameCallback
레이아웃을 모두 구성한 이후, 호출할 필요가 있는 경우 사용하면 되는 콜백메서드이다.
: 랜더링된 후, 크기나 위치가 필요한 경우가 있다. 동적으로 위젯의 크기를 결정하는 경우게 대표적인 예시이다.
GlobalKey _key = GlobalKey();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _afterLayout());
}
void _afterLayout() {
final RenderBox renderBox = _key.currentContext?.findRenderObject() as RenderBox;
final size = renderBox.size;
final position = renderBox.localToGlobal(Offset.zero);
print("Size: $size, Position: $position");
}
Widget build(BuildContext context) {
return Container(
key: _key, // 위젯에 GlobalKey 할당
// 위젯 구성
);
}
앱이 시작할 때, 특정 애니메이션을 보여주고 싶은 경우, 사용할 수 있다.
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _startAnimation());
}
void _startAnimation() {
// 애니메이션 컨트롤러 초기화 및 시작 로직
}
위젯이 화면에 그려지는 시점에, 외부 API로 부터 데이터를 가져와야하는 경우, 사용할 수 있다.
(다만 이 경우는 화면호출액션 호출 시점에 해도 된다.)
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
void _loadData() async {
final data = await fetchDataFromNetwork(); // 네트워크에서 데이터를 비동기적으로 로드
setState(() {
// 데이터를 사용하여 상태 업데이트
});
}