WidgetsBinding.instance.addPostFrameCallback이 필요한 순간

Uno·2024년 2월 5일
0

flutter

목록 보기
15/16
post-thumbnail

작성 동기: build 호출마다 레이아웃의 정보가 가져오고 싶었다

  • 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

  • 앱 전체에서 가지고 있는 유일한 데이터를 의미한다. 이 키가 있다면, 특정 엘리멘트를 위젯트리에서 찾아낼 수 있다. UUID 라고 생각하면 쉽다

문제: build 메서드 내에서 레이아웃 프로퍼티에 접근하려고 한 점

여기 코드에서 문제가 하나 있다.
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 메서드 호출횟수에 맞게 특정 메서드가 호출되어야 하는데, 거기서 호출하게되면 아직 레이아웃이 구성되지 않은 상태이므로 호출할 수 없다.


해결책: "addPostFrameCallback"을 통해, 레아이웃이 모두 구성된 이후 호출한다

결론먼저
코드를 아래와 같이 수정하면, 에러가 없어진다.

코드


Widget build(BuildContext context) {
	WidgetBinding.instance
		.addPostFrameCallback((_) => _measureTextWidth());
	...
	return Container();
}

출력결과

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 안에 파라미터로 전달하면 해결된다.


이유: addPostFrameCallback 의 호출 시점 때문이다

WidgetsBinding.instance.addPostFrameCallback

  • 현재 프레임의 랜더링이 모두 완료된 직후, 호출되는 콜백메서드이다.
  • 그 덕분에 Layout에 대한 데이터기 구성된 이후에 접근할 수 있다.

레이아웃을 모두 구성한 이후, 호출할 필요가 있는 경우 사용하면 되는 콜백메서드이다.


다른 사용 예시

1. 위젯의 크기나 위치를 얻고 싶을 때

: 랜더링된 후, 크기나 위치가 필요한 경우가 있다. 동적으로 위젯의 크기를 결정하는 경우게 대표적인 예시이다.

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 할당
    // 위젯 구성
  );
}

2. 초기 애니메이션 실행

앱이 시작할 때, 특정 애니메이션을 보여주고 싶은 경우, 사용할 수 있다.


void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_) => _startAnimation());
}

void _startAnimation() {
  // 애니메이션 컨트롤러 초기화 및 시작 로직
}

3. 데이터 로딩 및 UI 업데이트

위젯이 화면에 그려지는 시점에, 외부 API로 부터 데이터를 가져와야하는 경우, 사용할 수 있다.
(다만 이 경우는 화면호출액션 호출 시점에 해도 된다.)


void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}

void _loadData() async {
  final data = await fetchDataFromNetwork(); // 네트워크에서 데이터를 비동기적으로 로드
  setState(() {
    // 데이터를 사용하여 상태 업데이트
  });
}

참고자료

profile
iOS & Flutter

0개의 댓글