티스토리 뷰
[Flutter] 상태가 변화하지 않았어도 모든 위젯을 강제로 리빌드시키는 법 (모든 위젯을 리빌드)
Dev Ttangkong 2023. 2. 2. 17:19
Flutter를 처음 접하는 분들은 상태(State)가 변경되었을 때 setState()를 호출하여 프레임워크에 이를 알립니다.
이 과정에서 Flutter는 효율성을 위해 상태가 실제로 변경된 위젯만 리빌드합니다.
그렇다면, 만약 앱 전체를 감싸는 루트 위젯(RootPage)에서 단 한 번의 호출로 모든 위젯을 리빌드하려면 어떻게 해야 할까요?
하지만 이러한 방법은 의도된대로 동작하지 않을 것입니다. 해당 상태 위젯만이 업데이트되었다고 알리는 행위이기 때문입니다.
(그 하위 위젯들 모두 상태가 변화했다고 정의하는 것이 아닙니다.)
이는 당연히 비효율적인 빌드를 방지하는 매우 효율적인 기법입니다.
하지만 그렇다고 해서 생명주기 함수인 didUpdateWidget() 함수를 오버라이드하여 상위 위젯이 변화했을 때 자신도 상태도 같이 변경하게 한다면 이는 명백히 잘못된 접근입니다.
이런 방식은 불필요한 연산을 유발하며, Flutter의 렌더링 구조를 역행하는 비효율적인 방법이기 때문입니다.
그렇다면 어떻게 해야할까요?
Flutter는 Element 단위로 위젯 트리를 구성하며,
markNeedsBuild()를 통해 해당 Element를 다음 프레임에서 리빌드 대상으로 지정할 수 있습니다.
이를 활용하면 상태 변화가 없더라도 모든 하위 위젯을 강제로 리빌드할 수 있습니다.
그리고 보통은 아래와 같이 호출합니다.
element.markNeedsBuild();
아래는 실제로 제가 사용하는 RootPage 예제입니다.
/// [RootPage]는 모든 위젯의 상위 위젯입니다.
///
/// - [RootPage.rebuild]를 호출하여 모든 위젯을 다시 그릴 수 있습니다.
class RootPage extends StatefulWidget {
const RootPage({super.key});
static late RootPageRebuild rebuild;
@override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> with WidgetsBindingObserver {
/// 모든 자식 위젯을 다시 빌드합니다.
static void rebuildAllChildren(BuildContext context) {
// 해당 위젯의 모든 하위 위젯들을 다음 프레임에 재구성해야할 위젯으로 정의합니다.
void rebuild(Element element) {
// 해당 Element를 다음 프레임에 재구성해야할 위젯으로 정의합니다.
//
// - 한 프레임에 Element를 두 번 빌드하는 것은 비효율적이기 때문에,
// 애플리케이션과 위젯은 빌드 자체가 아니라 프레임이 시작되기 전 이벤트 핸들러 동안에만
// 위젯을 재구성하도록 구조화되어야 합니다.
// (그냥 위젯들을 리빌드시킨 답시고 일일이 한 위젯을 빌드시키지 말라는 뜻입니다
// 한 프레임을 기준으로 빌드를 하라는 뜻입니다)
//
element.markNeedsBuild();
element.visitChildren(rebuild);
}
// 현재 context를 Element로 정의하고 하위 위젯을 참조합니다.
(context as Element).visitChildren(rebuild);
}
@override
void initState() {
super.initState();
RootPage.rebuild = () {
// 현재 화면이 그려지고 있는 상태라면 (렌더링 중이면)
final isFrameRendering = WidgetsBinding.instance.schedulerPhase != SchedulerPhase.idle;
if (isFrameRendering) {
setState(() => rebuildAllChildren(context));
} else {
// 화면이 그려지지 않는 상태라면 해당 작업이 완료된 후 호출됩니다,
// 그렇지 않은 경우라면 호출되지 않습니다 다시 그릴때까지 호출을 보류합니다.
//
// 만약 그리고 있지 않은 상태라면 addPostFrameCallback 함수를 사용하지 마세요.
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() => rebuildAllChildren(context));
});
}
};
}
@override
Widget build(BuildContext context) {
return ...;
}
}
이제 루트 레벨에서 전체 위젯 트리를 다시 그려야 할 때,
불필요한 비효율 없이 markNeedsBuild()를 활용하는 방법을 잘 이해하셨을 것이라고 생각이 듭니다.
관련 패키지
flutter_rebuildable | Flutter package
This package that provides widgets to help rebuild all descendant widgets or all widgets in the entire application.
pub.dev
'플러터 (Flutter)' 카테고리의 다른 글
| [Flutter] 위젯이 화면 상에서 사라지거나 위젯 트리 상에 존재하지 않았을 때도 상태를 유지하는 방법 (0) | 2023.02.13 |
|---|---|
| [Flutter] 자식 위젯의 제스처 또는 포인터 수신을 막는 방법 (0) | 2023.02.12 |
| [Flutter] 자식 위젯의 State 또는 부모 위젯의 State를 참조하는 방법 (0) | 2023.01.30 |
| [Flutter] 자식 Listener의 트리거가 부모 Listener에게도 전파되는 것을 방지하는 방법 (0) | 2023.01.29 |
| [Flutter] 기기 테마가 변경되면 콜백되는 함수 (0) | 2023.01.08 |
- Total
- Today
- Yesterday
- JavaScript
- github
- 타입스크립트
- android
- pageroute
- 최적화
- 팩토리 메서드
- pagetransitionsbuilder
- Flutter
- keystore_signature
- StretchEffect
- jetpack compose
- web
- 플러터
- 안드로이드
- 디자인 패턴
- webpack
- 깃허브
- Reflow
- flutter contribution
- TypeScript
- svg
- Factory Method
- 객체지향
- compose_appbar
- 안드로이드 개발
- 조건부 타입
- 플러터 기여
- 객체 지향
- omit
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |