Flutter로 작업을 하다 보면 UI 관련된 오류를 가장 많이 접할 거라고 예상되는데요. 그럴 때마다 콘솔에 표시되는 에러를 보면 너무 길고 복잡해서 뭘 어떻게 해결해야 좋을지 모르겠는 상황이 많았어요. 그래서 이번에 출력 메시지를 하나씩 확인하면서 어떻게 접근하면 좋을지 정리해 보려고 해요!
오류 메시지#
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. In this case, consider using a Column or Wrap instead. Otherwise, consider using a CustomScrollView to concatenate arbitrary slivers into a single scrollable.
The relevant error-causing widget was:
ListView ListView:file:///Users/ittae/development/ittae/lib/src/features/calendar/presentation/attendance_pages/widgets/attendance_result.dart:21:21
When the exception was thrown, this was the stack:
#0 debugCheckHasBoundedAxis.<anonymous closure> (package:flutter/src/rendering/debug.dart:337:13)
#1 debugCheckHasBoundedAxis (package:flutter/src/rendering/debug.dart:396:4)
#2 RenderViewport.computeDryLayout (package:flutter/src/rendering/viewport.dart:1384:12)
#3 RenderBox.performResize (package:flutter/src/rendering/box.dart:2668:12)
#4 RenderObject.layout (package:flutter/src/rendering/object.dart:2587:9)
#5 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#6 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#7 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#8 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#9 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#10 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#11 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#12 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#13 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#14 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#15 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#16 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#17 ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:61:11)
#18 RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:985:73)
#19 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:1051:32)
#20 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#21 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:234:12)
#22 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#23 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:234:12)
#24 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#25 MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:173:12)
#26 _ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:1092:7)
#27 MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:237:7)
#28 RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:404:14)
#29 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#30 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#31 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#32 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#33 _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1448:11)
#34 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#35 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#36 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#37 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#38 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#39 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#40 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#41 ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:61:11)
#42 RenderStack._computeSize (package:flutter/src/rendering/stack.dart:595:43)
#43 RenderStack.performLayout (package:flutter/src/rendering/stack.dart:622:12)
#44 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#45 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#46 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#47 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#48 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#49 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#50 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#51 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#52 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#53 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#54 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#55 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#56 RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3728:13)
#57 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#58 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#59 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#60 _RenderTheaterMixin.layoutChild (package:flutter/src/widgets/overlay.dart:1002:13)
#61 _RenderTheater.performLayout (package:flutter/src/widgets/overlay.dart:1311:9)
#62 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#63 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#64 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#65 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#66 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#67 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#68 RenderCustomPaint.performLayout (package:flutter/src/rendering/custom_paint.dart:569:11)
#69 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#70 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#71 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#72 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#73 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#74 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#75 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#76 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#77 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#78 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#79 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#80 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#81 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#82 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:111:21)
#83 RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#84 RenderView.performLayout (package:flutter/src/rendering/view.dart:281:14)
#85 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2446:7)
#87 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1065:15)
#88 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:602:23)
#89 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1164:13)
#90 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:468:5)
#91 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1397:15)
#92 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1318:9)
#93 SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:1040:9)
#94 PlatformDispatcher.scheduleWarmUpFrame.<anonymous closure> (dart:ui/platform_dispatcher.dart:837:16)
#98 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
(elided 3 frames from class _Timer and dart:async-patch)
The following RenderObject was being processed when the exception was fired: RenderViewport#d5613 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
needs compositing
parentData: <none> (can use size)
constraints: BoxConstraints(0.0<=w<=398.0, 0.0<=h<=Infinity)
size: MISSING
axisDirection: down
crossAxisDirection: right
offset: ScrollPositionWithSingleContext#b7b6f(offset: 0.0, range: null..null, viewport: null, ScrollableState, AlwaysScrollableScrollPhysics -> BouncingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#4402f, ScrollDirection.idle)
anchor: 0.0
center child: RenderSliverPadding#bbdeb NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
padding: EdgeInsets.zero
textDirection: ltr
child: RenderSliverList#ccfb1 NEEDS-LAYOUT NEEDS-PAINT
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
no children current live
RenderObject: RenderViewport#d5613 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
needs compositing
parentData: <none> (can use size)
constraints: BoxConstraints(0.0<=w<=398.0, 0.0<=h<=Infinity)
size: MISSING
axisDirection: down
crossAxisDirection: right
offset: ScrollPositionWithSingleContext#b7b6f(offset: 0.0, range: null..null, viewport: null, ScrollableState, AlwaysScrollableScrollPhysics -> BouncingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#4402f, ScrollDirection.idle)
anchor: 0.0
center child: RenderSliverPadding#bbdeb NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
padding: EdgeInsets.zero
textDirection: ltr
child: RenderSliverList#ccfb1 NEEDS-LAYOUT NEEDS-PAINT
parentData: paintOffset=Offset(0.0, 0.0)
constraints: MISSING
geometry: null
no children current live
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: RenderViewport#d5613 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2164 pos 12: 'hasSize'
The relevant error-causing widget was:
ListView ListView:file:///Users/ittae/development/ittae/lib/src/features/calendar/presentation/attendance_pages/widgets/attendance_result.dart:21:21
════════════════════════════════════════════════════════════════════════════════
이 뒤로 몇개 혹은 몇십개씩 있는 RenderBox was not laid out 오류는 생략할게요!
오류 메시지의 핵심을 파악하기#
오류 메시지에 모든 내용을 살펴보는 것도 좋지만 실제로 제가 개발한 부분보다는 프레임워크나 SDK에서 처리되는 위치에서 출력되는 메시지인 경우가 대부분이에요. 그래서 하나씩 살펴봐도 프레임워크 소스 코드를 파악하고 있지 않아서 빠르게 도움이 되진 못했어요.
오류가 발생한 원인을 쉽게 파악할 수 있는 방법으로 로그를 해석하는 게 도움이 될 거예요. 긴 에러 로그에서 주로 첫 번째 오류 메시지와 발생한 위젯 부분이 가장 중요해요. 플러터에서 나오는 긴 로그는 보통 여러 단계의 렌더링 과정을 거쳐 문제가 발생한 결과인데, 가장 첫 번째에 나온 오류가 핵심적인 오류일 가능성이 커요.
전체 출력의 집중하기보다는 몇 가지 중요한 키워드를 찾아야 해요. 플러터에서 에러가 나는 주된 이유로는 어떤 위젯을 추가, 변경할 때 레이아웃과 관련된 문제일 확률이 높아요.
에러 유형 살펴보기#
제 환경에서 디버그 콘솔을 보면 어떤 건 빨갛고 어떤 건 파란색 글씨인데요. 누가 봐도 빨간색 부분을 자세히 봐야 할 것 같아요. 빨간색 메시지가 위치한 앞뒤 설명도 함께 읽으면 문제를 해결하기 더더욱 좋아요. 보통 출력이 길기 때문에 전체적으로 눈에 안 들어오는데 첫 번째 오류 메시지를 시간을 들여 확인해 보는 것이 문제를 빠르게 해결할 수 있어요.
이 오류 메시지는 제가 ListView를 사용하고 있었을 때 나왔던 메시지예요. ListView를 보통 상황에서는 잘 되지만 저는 ListView를 Column의 자식으로 추가했고 크기를 지정하지 않았기 때문에 높이가 무한한 상황이라 에러가 난 것이였어요.
해결하기 위한 방법#
Column에 자식으로 있는 ListView를 Expanded로 감싸기
Expanded(
child: ListView(
children: [...],
),
)
Container나 SizedBox 위젯으로 높이를 지정해 주기
SizedBox(
height: 300.0, // 원하는 높이
child: ListView(
children: [...],
),
)
LisView에 shrinkWrap: true를 추가하기
ListView(
shrinkWrap: true,
children: [...],
)
정리
- 최상단의 첫 오류 메시지를 집중적으로 보기 대부분의 해결책은 첫 오류에 기반하여 찾을 수 있음
- 오류 메시지에서 위젯과 라인 번호를 추적하여 어디에서 문제가 발생했는지 정확히 파악하기
- Flutter 공식 문서나 Stack Overflow를 활용해 비슷한 오류 해결책을 찾아보기
Here and now...breathe and relax...in battle and in life.
— Dan Millman