Skip to content

Two dimensional viewport throws Unexpected null value exception when no child is laid out yet #148255

@Amir-P

Description

@Amir-P

Steps to reproduce

  1. Run the example app

Expected results

App should run without any errors.

Actual results

You will get Unexpected null value exception but the app is in working state.

Code sample

Code sample
import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: false),
      home: Scaffold(
        body: SimpleBuilderTableView(
          mainAxis: Axis.vertical,
          verticalDetails: const ScrollableDetails.vertical(),
          horizontalDetails: const ScrollableDetails.horizontal(),
          diagonalDragBehavior: DiagonalDragBehavior.none,
          clipBehavior: Clip.hardEdge,
          delegate: TwoDimensionalChildBuilderDelegate(
              maxXIndex: 50,
              maxYIndex: 50,
              addAutomaticKeepAlives: false,
              addRepaintBoundaries: false,
              builder: (BuildContext context, ChildVicinity vicinity) {
                if (vicinity.xIndex > 10) {
                  return ColoredBox(
                    color: vicinity.xIndex.isOdd ? Colors.red : Colors.green,
                    child: SizedBox.square(dimension: 200),
                  );
                }
                return null;
              }),
        ),
      ),
    );
  }
}

class SimpleBuilderTableView extends TwoDimensionalScrollView {
  const SimpleBuilderTableView({
    super.key,
    super.primary,
    super.mainAxis = Axis.vertical,
    super.verticalDetails = const ScrollableDetails.vertical(),
    super.horizontalDetails = const ScrollableDetails.horizontal(),
    required TwoDimensionalChildBuilderDelegate delegate,
    super.cacheExtent,
    super.diagonalDragBehavior = DiagonalDragBehavior.none,
    super.dragStartBehavior = DragStartBehavior.start,
    super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    super.clipBehavior = Clip.hardEdge,
  }) : super(delegate: delegate);

  @override
  Widget buildViewport(BuildContext context, ViewportOffset verticalOffset,
      ViewportOffset horizontalOffset) {
    return SimpleBuilderTableViewport(
      horizontalOffset: horizontalOffset,
      horizontalAxisDirection: horizontalDetails.direction,
      verticalOffset: verticalOffset,
      verticalAxisDirection: verticalDetails.direction,
      mainAxis: mainAxis,
      delegate: delegate as TwoDimensionalChildBuilderDelegate,
      cacheExtent: cacheExtent,
      clipBehavior: clipBehavior,
    );
  }
}

class SimpleBuilderTableViewport extends TwoDimensionalViewport {
  const SimpleBuilderTableViewport({
    super.key,
    required super.verticalOffset,
    required super.verticalAxisDirection,
    required super.horizontalOffset,
    required super.horizontalAxisDirection,
    required TwoDimensionalChildBuilderDelegate delegate,
    required super.mainAxis,
    super.cacheExtent,
    super.clipBehavior = Clip.hardEdge,
  }) : super(delegate: delegate);

  @override
  RenderTwoDimensionalViewport createRenderObject(BuildContext context) {
    return RenderSimpleBuilderTableViewport(
      horizontalOffset: horizontalOffset,
      horizontalAxisDirection: horizontalAxisDirection,
      verticalOffset: verticalOffset,
      verticalAxisDirection: verticalAxisDirection,
      mainAxis: mainAxis,
      delegate: delegate as TwoDimensionalChildBuilderDelegate,
      childManager: context as TwoDimensionalChildManager,
      cacheExtent: cacheExtent,
      clipBehavior: clipBehavior,
    );
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderSimpleBuilderTableViewport renderObject) {
    renderObject
      ..horizontalOffset = horizontalOffset
      ..horizontalAxisDirection = horizontalAxisDirection
      ..verticalOffset = verticalOffset
      ..verticalAxisDirection = verticalAxisDirection
      ..mainAxis = mainAxis
      ..delegate = delegate
      ..cacheExtent = cacheExtent
      ..clipBehavior = clipBehavior;
  }
}

class RenderSimpleBuilderTableViewport extends RenderTwoDimensionalViewport {
  RenderSimpleBuilderTableViewport({
    required super.horizontalOffset,
    required super.horizontalAxisDirection,
    required super.verticalOffset,
    required super.verticalAxisDirection,
    required TwoDimensionalChildBuilderDelegate delegate,
    required super.mainAxis,
    required super.childManager,
    super.cacheExtent,
    super.clipBehavior = Clip.hardEdge,
  }) : super(delegate: delegate);

  RenderBox? testGetChildFor(ChildVicinity vicinity) => getChildFor(vicinity);

  @override
  void layoutChildSequence() {
    // Really simple table implementation for testing.
    // Every child is 200x200 square
    final double horizontalPixels = horizontalOffset.pixels;
    final double verticalPixels = verticalOffset.pixels;
    final double viewportWidth = viewportDimension.width + cacheExtent;
    final double viewportHeight = viewportDimension.height + cacheExtent;
    final TwoDimensionalChildBuilderDelegate builderDelegate =
        delegate as TwoDimensionalChildBuilderDelegate;

    final int maxRowIndex;
    final int maxColumnIndex;
    maxRowIndex = builderDelegate.maxYIndex ?? 5;
    maxColumnIndex = builderDelegate.maxXIndex ?? 5;

    final int leadingColumn = math.max((horizontalPixels / 200).floor(), 0);
    final int leadingRow = math.max((verticalPixels / 200).floor(), 0);
    final int trailingColumn = math.min(
      ((horizontalPixels + viewportWidth) / 200).ceil(),
      maxColumnIndex,
    );
    final int trailingRow = math.min(
      ((verticalPixels + viewportHeight) / 200).ceil(),
      maxRowIndex,
    );

    double xLayoutOffset = (leadingColumn * 200) - horizontalOffset.pixels;
    for (int column = leadingColumn; column <= trailingColumn; column++) {
      double yLayoutOffset = (leadingRow * 200) - verticalOffset.pixels;
      for (int row = leadingRow; row <= trailingRow; row++) {
        final ChildVicinity vicinity =
            ChildVicinity(xIndex: column, yIndex: row);
        final RenderBox? child = buildOrObtainChildFor(vicinity);

        child?.layout(constraints.tighten(width: 200.0, height: 200.0));

        if (child != null) {
          parentDataOf(child).layoutOffset =
              Offset(xLayoutOffset, yLayoutOffset);
        }
        yLayoutOffset += 200;
      }
      xLayoutOffset += 200;
    }

    final double verticalExtent = 200 * (maxRowIndex + 1);
    verticalOffset.applyContentDimensions(
      0.0,
      clampDouble(
          verticalExtent - viewportDimension.height, 0.0, double.infinity),
    );
    final double horizontalExtent = 200 * (maxColumnIndex + 1);
    horizontalOffset.applyContentDimensions(
      0.0,
      clampDouble(
          horizontalExtent - viewportDimension.width, 0.0, double.infinity),
    );
  }
}

Logs

Logs
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following TypeErrorImpl was thrown during performLayout():
Unexpected null value.

The relevant error-causing widget was:
  SimpleBuilderTableViewport
  SimpleBuilderTableViewport:file:///lib/main.dart:62:12

When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 296:3       throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 586:18  nullCheck
packages/flutter/src/widgets/two_dimensional_viewport.dart 1321:28                [_reifyChildren]
packages/flutter/src/widgets/two_dimensional_viewport.dart 1260:7                 <fn>
packages/flutter/src/rendering/object.dart 2688:51                                <fn>
packages/flutter/src/rendering/object.dart 1098:7                                 [_enableMutationsToDirtySubtrees]
packages/flutter/src/rendering/object.dart 2688:7                                 invokeLayoutCallback
packages/flutter/src/widgets/two_dimensional_viewport.dart 1250:5                 performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/custom_layout.dart 173:10                          layoutChild
packages/flutter/src/material/scaffold.dart 1097:7                                performLayout
packages/flutter/src/rendering/custom_layout.dart 237:7                           [_callPerformLayout]
packages/flutter/src/rendering/custom_layout.dart 404:5                           performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/proxy_box.dart 1448:11                             performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/layout_helper.dart 61:10                           layoutChild
packages/flutter/src/rendering/stack.dart 595:43                                  [_computeSize]
packages/flutter/src/rendering/stack.dart 622:12                                  performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/proxy_box.dart 3728:13                             performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/widgets/overlay.dart 1002:12                                 layoutChild
packages/flutter/src/widgets/overlay.dart 1311:9                                  performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/custom_paint.dart 569:11                           performLayout
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/proxy_box.dart 111:21                              <fn>
packages/flutter/src/rendering/object.dart 2577:7                                 layout
packages/flutter/src/rendering/view.dart 281:7                                    performLayout
packages/flutter/src/rendering/object.dart 2416:7                                 [_layoutWithoutResize]
packages/flutter/src/rendering/object.dart 1052:17                                flushLayout
packages/flutter/src/rendering/object.dart 1065:14                                flushLayout
packages/flutter/src/rendering/binding.dart 602:5                                 drawFrame
packages/flutter/src/widgets/binding.dart 1138:13                                 drawFrame
packages/flutter/src/rendering/binding.dart 468:5                                 [_handlePersistentFrameCallback]
packages/flutter/src/scheduler/binding.dart 1392:7                                [_invokeFrameCallback]
packages/flutter/src/scheduler/binding.dart 1313:9                                handleDrawFrame
packages/flutter/src/scheduler/binding.dart 1035:9                                <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:11           internalCallback

The following RenderObject was being processed when the exception was fired: RenderSimpleBuilderTableViewport#c3adc NEEDS-LAYOUT NEEDS-PAINT:
  needs compositing
  creator: SimpleBuilderTableViewport ← IgnorePointer-[GlobalKey#86c87] ← Semantics ← Listener ←
    _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#a96f5] ←
    Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#02c3d] ←
    NotificationListener<ScrollMetricsNotification> ←
    _HorizontalInnerDimension-[LabeledGlobalKey<ScrollableState>#4de8e] ←
    IgnorePointer-[GlobalKey#6a5e6] ← ⋯
  parentData: <none> (can use size)
  constraints: BoxConstraints(0.0<=w<=1138.0, 0.0<=h<=682.0)
  size: Size(1138.0, 682.0)
This RenderObject has no descendants.
════════════════════════════════════════════════════════════════════════════════════════════════════

Flutter Doctor output

Doctor output
[✓] Flutter (Channel master, 3.22.0-31.0.pre.23, on macOS 14.4.1 23E224
    darwin-arm64, locale en-US)
    • Flutter version 3.22.0-31.0.pre.23 on channel master at
      /flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 9abde3e615 (12 hours ago), 2024-05-13 04:47:24 -0400
    • Engine revision fab7a74178
    • Dart version 3.5.0 (build 3.5.0-147.0.dev)
    • DevTools version 2.36.0-dev.5

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /SDK
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /SDK
    • Java binary at: /Applications/Android
      Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      17.0.10+0-17.0.10b1087.21-11572160)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15E204a
    • CocoaPods version 1.14.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build
      17.0.10+0-17.0.10b1087.21-11572160)

[✓] VS Code (version 1.89.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.88.0

[✓] Connected device (4 available)
    • macOS (desktop)                 • macos                     • darwin-arm64
      • macOS 14.4.1 23E224 darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad     • darwin
      • macOS 14.4.1 23E224 darwin-arm64
    • Chrome (web)                    • chrome                    •
      web-javascript • Google Chrome 119.0.6045.123

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: error messageError messages from the Flutter frameworkf: scrollingViewports, list views, slivers, etc.found in release: 3.22Found to occur in 3.22frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: two_dimensional_scrollablesIssues pertaining to the two_dimensional_scrollables packageteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework teamwaiting for PR to land (fixed)A fix is in flight

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions