In a Flutter CustomScrollView, why does a Container placed directly in the slivers list throw, and how do you fix it?
answer
- two layout protocols
- RenderViewport wants RenderSliver children
- SliverToBoxAdapter bridges one box
- SliverPadding, not Padding
- many rows: SliverList, not a Column
basics
~20 sCustomScrollView's slivers list accepts only sliver widgets, and a Container is a box widget, so the viewport reports it expected a RenderSliver. Wrap a single box in SliverToBoxAdapter, use sliver versions such as SliverPadding, and use SliverList for many rows.
solid answer
~40 sFlutter has two layout protocols. Box widgets like `Container`, `Padding` and `ListView` produce `RenderBox` objects that take `BoxConstraints`; the viewport inside a `CustomScrollView` expects `RenderSliver` children that take `SliverConstraints`. Put a box there and you get `A RenderViewport expected a child of type RenderSliver but received a child of type ...`. The fixes: wrap one box in `SliverToBoxAdapter`; swap box wrappers for their sliver twins (`SliverPadding`, `SliverOpacity`, `SliverIgnorePointer`, `DecoratedSliver`); and replace a nested `ListView` or `GridView` with `SliverList` or `SliverGrid`. The reverse mistake — a `SliverList` inside a `Column` — throws the mirror error. Don't dodge it by putting a long `Column` in one adapter, because that builds every row.
code
dart · 34 linesimport 'package:flutter/material.dart';
class HotelReviews extends StatelessWidget {
const HotelReviews({super.key, required this.reviews});
final List<String> reviews;
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
// Wrong: Container(height: 48, child: Text('Reviews')) here would throw.
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.fromLTRB(16, 24, 16, 8),
child: Text('Guest reviews'),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList.builder(
itemCount: reviews.length,
itemBuilder: (context, index) => Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(reviews[index]),
),
),
),
),
],
);
}
}go deeper
Recognise the 'expected a child of type RenderSliver' error and know SliverToBoxAdapter and SliverList fix it.
Explain the box and sliver protocols and map each box wrapper to its sliver twin, including the sliver: parameter.
Catch the adapter-around-a-Column pattern in review, since it hides the error while building every row.
Set a convention for composing scroll screens from slivers so teams do not reach for adapters by habit.
## Two layout protocols Every Flutter render object speaks one of two layout languages: - **Box protocol** — `RenderBox`. The parent passes `BoxConstraints` (minimum and maximum width and height) and the child returns a `Size`. `Container`, `Padding`, `Column`, `Text`, `Image` and even `ListView` (from the outside) are box widgets. - **Sliver protocol** — `RenderSliver`. The viewport passes `SliverConstraints` (scroll offset, remaining paint extent, overlap) and the child returns a `SliverGeometry`. `SliverList`, `SliverGrid`, `SliverAppBar`, `SliverPadding` and `SliverToBoxAdapter` are sliver widgets. A render object that coordinates layout with its children checks their type, because a sliver cannot understand box constraints and a box cannot understand sliver constraints. A `CustomScrollView` builds a viewport (`RenderViewport`) whose children must all be `RenderSliver`. ## Reading the error Put a `Container` directly into `slivers:` and the framework throws a `FlutterError` whose summary reads: `A RenderViewport expected a child of type RenderSliver but received a child of type RenderDecoratedBox.` The last class name varies with what the box widget builds (`RenderPadding`, `RenderConstrainedBox`, `RenderRepaintBoundary` for a nested scroll view). The description adds that render objects expect specific child types because they coordinate with their children during layout and paint. The fix is always to hand the viewport a sliver. ## Sliver equivalents of common box widgets | You wrote (box) | Use instead (sliver) | |---|---| | one heading, banner or card | `SliverToBoxAdapter(child: ...)` | | `Padding` around a list | `SliverPadding(padding: ..., sliver: ...)` | | `ListView` / `ListView.builder` | `SliverList` / `SliverList.builder` | | `GridView.builder` | `SliverGrid.builder` | | `Opacity` / `IgnorePointer` / `Visibility` | `SliverOpacity` / `SliverIgnorePointer` / `SliverVisibility` | | `DecoratedBox` behind a section | `DecoratedSliver` | | a `Column` grouping several sections | `SliverMainAxisGroup(slivers: ...)` | Note the parameter name: sliver wrappers take a **`sliver:`** child, not `child:`, which is itself a reminder of which protocol the child must speak. ## The reverse mistake The same check fires the other way. A `SliverList` placed in a `Column` produces `A RenderFlex expected a child of type RenderBox but received a child of type RenderSliverList.` A `Column` lays out boxes; if you need a list there, use a `ListView` (a box that contains its own viewport), or restructure the screen so the whole thing is a `CustomScrollView`. ## The laziness trap The quickest way to silence the error is to wrap everything in a single `SliverToBoxAdapter` holding a `Column`. It compiles and it scrolls, but a `SliverToBoxAdapter` lays out **one** box child in full, so every row in that `Column` is built and measured whenever the sliver is laid out. The framework's own docs recommend `SliverList`, `SliverFixedExtentList`, `SliverPrototypeExtentList` or `SliverGrid` over multiple adapters, because those build only the children visible through the viewport. Rules of thumb: 1. One widget that is not a repeated row: `SliverToBoxAdapter`. 2. Repeated rows or tiles: `SliverList` or `SliverGrid` with a builder. 3. A wrapper that changes paint or hit testing around a sliver: its `Sliver*` twin. 4. A section made of several slivers: `SliverMainAxisGroup`, so the section can be padded, decorated or pinned as a unit. Following those four rules removes the type error and keeps the scroll view lazy, which is the reason for using slivers in the first place.
- Can you put a ListView inside CustomScrollView.slivers if you wrap it in SliverToBoxAdapter?It compiles, but the adapter gives the `ListView` unbounded height, which fails unless the list shrink-wraps, and a shrink-wrapped inner list measures every item and adds a second scrollable. Replace it with a `SliverList` so the rows join the outer scroll and stay lazy.
- How do you group a header and its list so they can be padded or decorated as one section?Use `SliverMainAxisGroup(slivers: [...])`, which lays out several slivers one after another as a single sliver. Wrap it in `SliverPadding` or `DecoratedSliver` to style the whole section, and a pinned header inside it stays pinned only while its group is on screen.
saying these in an interview costs you the question
- Wrapping the Container in Padding makes it a valid sliver.
- Putting the whole page in one SliverToBoxAdapter Column is fine.
- A SliverList can go straight into a Column.
- Sliver wrappers take a child parameter like box widgets.
- The viewport just ignores box children it cannot lay out.