Skip to content

CarouselView.builder: Lazy Loading for CarouselView #170692

Closed

Description

@exoad

Use case

Commonly, widgets like PageView and ListView lazily load items that are not in view. However, CarouselView does not. This can make things like fetching images from the internet or having to do heavy computations to get the widgets feel sluggish and can be taxing on the system if there are a lot of elements in the carousel.

However, it can be easier to use a PageView.builder for some cases, but this method enforces page snapping and additionally, the developer would need to program in the material effects of the CarouselView themselves which can be not trivial.

Description

The current implementation of CarouselView does not have a rigid form of lazy loading process akin to something like ListView.builder which allows for items in the list to load when they are deemed visible by Flutter.

Here is a demonstration of the difference between CarouselView and ListView.builder at handling 30 FutureBuilders.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MainApp()));
}

final class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  bool showListView = false;
  Axis dir = Axis.vertical;

  late List<Future<void>> _carouselFutures;

  static const int totalItems = 30;
  static const Duration futureDelayMS = Duration(milliseconds: 3500);

  @override
  void initState() {
    super.initState();
    _carouselFutures = List<Future<void>>.generate(
      totalItems,
      (int index) =>
          Future<void>.delayed(futureDelayMS, () => print("(CarouselView) $index finished!")),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: const SizedBox.shrink(),
        flexibleSpace: SafeArea(
          child: Row(
            spacing: 8,
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ElevatedButton(
                onPressed: () => setState(() {
                  showListView = !showListView;
                  if (!showListView) {
                    _carouselFutures = List<Future<void>>.generate(
                      totalItems,
                      (int index) => Future<void>.delayed(
                        futureDelayMS,
                        () => print("(CarouselView) $index finished!"),
                      ),
                    );
                  }
                }),
                child: Text("Show ${showListView ? "CarouselView" : "ListView"}"),
              ),
              ElevatedButton(
                onPressed: () =>
                    setState(() => dir = dir == Axis.horizontal ? Axis.vertical : Axis.horizontal),
                child: Text("Set ${dir == Axis.horizontal ? "Vertical" : "Horizontal"}"),
              ),
            ],
          ),
        ),
      ),
      body: SafeArea(
        left: false,
        right: false,
        child: Material(
          child: Column(
            children: <Widget>[
              Expanded(
                child: !showListView
                    ?
                      // carousel view here
                      CarouselView.weighted(
                        scrollDirection: dir,
                        flexWeights: const <int>[1, 2, 1],
                        itemSnapping: true,
                        shape: Border.all(color: Colors.transparent),
                        children: List<Widget>.generate(totalItems, (int index) {
                          return SizedBox(
                            height: MediaQuery.sizeOf(context).height,
                            width: MediaQuery.sizeOf(context).width * 0.95,
                            child: FutureBuilder<void>(
                              future: _carouselFutures[index],
                              builder: (BuildContext context, AsyncSnapshot<void> asyncSnapshot) {
                                if (asyncSnapshot.hasData ||
                                    asyncSnapshot.connectionState == ConnectionState.done) {
                                  return Container(
                                    decoration: BoxDecoration(
                                      color: index % 2 == 0 ? Colors.red : Colors.green,
                                      borderRadius: BorderRadius.circular(30),
                                    ),
                                    child: Center(
                                      child: Text(
                                        index.toString(),
                                        textAlign: TextAlign.center,
                                        textWidthBasis: TextWidthBasis.longestLine,
                                        style: const TextStyle(
                                          fontSize: 64,
                                          color: Colors.black,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                    ),
                                  );
                                } else if (asyncSnapshot.hasError) {
                                  return const Text(
                                    "Failed to load!",
                                    style: TextStyle(color: Colors.red),
                                  );
                                }
                                return const CircularProgressIndicator(color: Colors.amber);
                              },
                            ),
                          );
                        }),
                      )
                    :
                      // listview method
                      ListView.builder(
                        scrollDirection: dir,
                        itemCount: totalItems,
                        itemBuilder: (BuildContext context, int index) {
                          return SizedBox(
                            height: MediaQuery.sizeOf(context).height,
                            width: MediaQuery.sizeOf(context).width * 0.95,
                            child: FutureBuilder<void>(
                              future: Future<void>.delayed(
                                futureDelayMS,
                                () => print("(ListView) $index finished!"),
                              ),
                              builder: (BuildContext context, AsyncSnapshot<void> asyncSnapshot) {
                                if (asyncSnapshot.connectionState == ConnectionState.done) {
                                  return Container(
                                    decoration: BoxDecoration(
                                      color: index % 2 == 0 ? Colors.red : Colors.green,
                                      borderRadius: BorderRadius.circular(30),
                                    ),
                                    child: Center(
                                      child: Text(
                                        index.toString(),
                                        style: const TextStyle(
                                          fontSize: 64,
                                          color: Colors.black,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                    ),
                                  );
                                } else if (asyncSnapshot.hasError) {
                                  return const Text(
                                    "Failed to load!",
                                    style: TextStyle(color: Colors.red),
                                  );
                                }
                                return const CircularProgressIndicator(color: Colors.amber);
                              },
                            ),
                          );
                        },
                      ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Proposal

Using the code sample from above, it would be better to have some kind of way to build the items on demand with like a CarouselView.builder and CarouselView.buildWeighted that can be supplied a common Widget Function(BuildContext, int) to render the widget at that index.

Activity

  1. exoad commented on Jun 16, 2025

    @exoad
    Author

    After looking through the implementation for CarouselView it seems like the internal function _buildSliverCarousel():

    Widget _buildSliverCarousel(ThemeData theme) {
    if (_itemExtent != null) {
    return _SliverFixedExtentCarousel(
    itemExtent: _itemExtent!,
    minExtent: widget.shrinkExtent,
    delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
    return _buildCarouselItem(index);
    }, childCount: widget.children.length),
    );
    }
    assert(
    _flexWeights != null && _flexWeights!.every((int weight) => weight > 0),
    'flexWeights is null or it contains non-positive integers',
    );
    return _SliverWeightedCarousel(
    consumeMaxWeight: _consumeMaxWeight,
    shrinkExtent: widget.shrinkExtent,
    weights: _flexWeights!,
    delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
    return _buildCarouselItem(index);
    }, childCount: widget.children.length),
    );
    }

    Does provide a way to easily integrate this change as it is using SliverChildBuilderDelegate already.

    However, this is just my first impressions & speculations.

  2. changed the title [-]Lazy Loading for `CarouselView`[/-] [+]`CarouselView.builder`: Lazy Loading for `CarouselView`[/+] on Jun 16, 2025
  3. added
    in triagePresently being triaged by the triage team
    c: proposalA detailed proposal for a change to Flutter
    c: new featureNothing broken; request for a new capability
    p: material_uimaterial_ui package in flutter/packages
    frameworkflutter/packages/flutter repository. See also f: labels.
    team-designOwned by Design Languages team
    and removed
    in triagePresently being triaged by the triage team
    on Jun 17, 2025
  4. added
    P3Issues that are less important to the Flutter project
    triaged-designTriaged by Design Languages team
    on Jun 18, 2025
  5. added a commit that references this issue on Oct 8, 2025
    7100df2
  6. github-actions commented on Oct 22, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  7. locked as resolved and limited conversation to collaborators on Oct 22, 2025
  8. added a commit that references this issue on Dec 10, 2025
    8db2c5d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterframeworkflutter/packages/flutter repository. See also f: labels.p: material_uimaterial_ui package in flutter/packagesteam-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions