very_good_infinite_list icon indicating copy to clipboard operation
very_good_infinite_list copied to clipboard

`.separated` factory constructor

Open stargazing-dino opened this issue 4 years ago • 1 comments

Hello ! I know matching feature parity with a standard Flutter widget is usually pretty tough but I'd love if the .separated constructor was added here to allow users to space their content easily. Conditional padding or space filling widgets are the only alternative and are kind of troublesome to use imho.

stargazing-dino avatar Jul 06 '21 18:07 stargazing-dino

Actually, I don't have much experience in ListView's so pardon my ignorance. I'm also just curious to hear from a package maintainer of a ListView package if is there a technical reason why someone has not built a wrapper kind of thing around ListView instead of recreating it? Looking into this recently it looks like there's a NotificationListener widget that can be used to listen to scroll events.

Given that, I thought there would be a package that would just use that to handle events. I wrote this quick proof of concept though I haven't tested it:

lazy list builder
import 'package:flutter/material.dart';

typedef LazyBuilder = Widget Function(
  BuildContext context,
  // TODO: Specify whether loading before or after scroll extent
  Widget? loadingMore,
);

class LazyListBuilder extends StatefulWidget {
  /// A lazy builder that will be used to create the list widget. It also
  /// carries the context of the child and a loading widget to be inserted into
  /// the ListViews.
  final LazyBuilder builder;

  /// Called when more content is needed. Only works at the END of the main axis
  final VoidCallback onLoadMore;

  const LazyListBuilder({
    Key? key,
    required this.builder,
    required this.onLoadMore,
  }) : super(key: key);

  @override
  State<LazyListBuilder> createState() => _LazyListBuilderState();
}

class _LazyListBuilderState extends State<LazyListBuilder> {
  bool isLoading = false;

  // TODO: Debounce this
  void loadMore() {
    setState(() {
      isLoading = true;
    });

    widget.onLoadMore();

    if (mounted) {
      setState(() {
        isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollNotification>(
      child: widget.builder(
        context,
        isLoading ? const CircularProgressIndicator() : null,
      ),
      onNotification: (notification) {
        if (notification is ScrollEndNotification) {
          // TODO: Do extentBefore
          if (notification.metrics.extentAfter == 0) {
            loadMore();
          }
        }

        return false;
      },
    );
  }
}

Usage
return LazyListBuilder(
  onLoadMore: () {
    print('loading more');
  },
  builder: (context, loadingMore) {
    return ListView(
      children: [
        ...List<Widget>.generate(40, (index) {
          return ListTile(title: Text('hello $index'));
        }),
        // This package could also come with an helper extensions for lists
        // that just add loadingMore to the end of the list when its not null
        if (loadingMore == null) loadingMore!,
      ],
    );
  },
);

What advantages would one way have over the other? Is there something one of them cannot do?

Edit

Here is a more full fledged implementation. Again, untested at the time of writing

stargazing-dino avatar Jul 06 '21 19:07 stargazing-dino

Hello @Nolence 👋

Sorry for the very late reply.

It is already possible to replicate the .separated behavior by using the separatorBuilder option.

renancaraujo avatar Oct 03 '22 19:10 renancaraujo

is there a technical reason why someone has not built a wrapper kind of thing around ListView instead of recreating it?

There is! For this particular case (adding a loading thingy on the trailing edge of the scroll view) it would be really hard to accomplish it without sacrificing the optimization of ListView.builder. In your example, on usage, you generate a list of widgets (ListTile).

Having a list of items in a scroll view is not always the case and in some cases, it may cause the crash of the app due to the number of items. (Once I worked on an app that could have 2 million items on a single listview).

renancaraujo avatar Oct 03 '22 19:10 renancaraujo