Reader Stacks

Flutter UI Extras: Vertical Scroll Feeds, Checkboxes, and Background Images

A PageView with vertical scroll direction is the core of a TikTok-style feed — combined with checkboxes, background images, and turning off the debug banner for a cleaner build.

Flutter UI Extras: Vertical Scroll Feeds, Checkboxes, and Background Images

A handful of common Flutter UI needs — a TikTok-style vertical swipe feed, checkboxes, background images, and cleaning up the default debug banner — come up often enough in real apps to cover together.

A TikTok-style vertical scroll feed with PageView

PageView.builder(
    scrollDirection: Axis.vertical,
    itemCount: videos.length,
    itemBuilder: (context, index) {
        return VideoPlayerWidget(video: videos[index]);
    },
)

PageView with scrollDirection: Axis.vertical is the core of this pattern — each swipe snaps fully to the next item, rather than allowing a partial scroll, which is exactly the full-screen, one-item-at-a-time feel a TikTok-style feed needs.

A standard scrollable list, for comparison

ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, index) {
        return ListTile(title: Text(items[index].name));
    },
)

ListView scrolls freely and continuously, showing multiple items at once — the key difference from PageView is that ListView suits a normal scrolling list, while PageView suits full-screen, one-at-a-time paged content.

A generic scrollable container with SingleChildScrollView

SingleChildScrollView(
    child: Column(
        children: [
            // any number of widgets that might overflow the screen height
        ],
    ),
)

SingleChildScrollView is the right choice when you have one long child (often a Column) that might not fit the screen, rather than a genuinely repeating list of items — using ListView for this instead works but is a slight misuse of a widget built for item-based, potentially very long lists.

A checkbox

bool _agreed = false;

CheckboxListTile(
    title: Text('I agree to the terms'),
    value: _agreed,
    onChanged: (value) {
        setState(() {
            _agreed = value ?? false;
        });
    },
)

CheckboxListTile combines the checkbox with a tappable label in one widget — using a bare Checkbox alongside a separate Text widget works too, but means the text itself isn't tappable to toggle the checkbox, which is usually the better UX.

A background image

Container(
    decoration: BoxDecoration(
        image: DecorationImage(
            image: AssetImage('assets/background.jpg'),
            fit: BoxFit.cover,
        ),
    ),
    child: Center(child: Text('Content over the background')),
)

BoxFit.cover scales the image to fill the container completely, cropping any excess — the standard choice for a full-screen background image where covering the whole area matters more than preserving the image's exact original aspect ratio.

Removing the debug banner

MaterialApp(
    debugShowCheckedModeBanner: false,
    home: HomePage(),
)

The red "DEBUG" banner in the top-right corner only appears in debug builds and never in a release build — setting this flag simply removes it during development for cleaner screenshots or client demos, since it has no effect on the actual release build regardless of this setting.