EdsonBueno / infinite_scroll_pagination

Flutter package to help you lazily load and display pages of items as the user scrolls down your screen.
https://pub.dev/packages/infinite_scroll_pagination
MIT License
612 stars 202 forks source link

feat: create PagedSliverMasonryGrid #252

Closed clragon closed 5 months ago

clragon commented 1 year ago

I added PagedSliverMasonryGrid and rewrote PagedMasonryGrid to use it internally.

To facilitate this change, I moved _AppendedSliverGrid into its own file. SliverMasonryGrid is very similar to SliverGrid but not the same, so reusing this felt right. I imagine if we implement other wrappers around the new custom staggered grid view types, this will also come in handy.

All tests pass as usual and I did not add new ones as PagedMasonrySliverGrid is tested through PagedMasonryGridView and I have seen that PagedSliverGrid does not have tests at the moment either.

I have reverted PagedMasonryGridView to a BoxScrollView so that it is consistent with other paged sliver wrappers and can be used with pull_to_refresh.

EdsonBueno commented 1 year ago

Hey @clragon, Have you tried using this as a Sliver? Inside of a CustomScrollView with other sibling Sliver widgets.

clragon commented 1 year ago

Hi @EdsonBueno, I dont remember whether I specifically tested that. Is there an issue with it? I am using this fork in my app, though I am using the PagedMasonryGridView which wraps PagedSliverMasonryGrid. I havent seen any issues yet though.

prologikus commented 1 year ago

I also use this fork in my app, works with Custom Scroll View

prologikus commented 1 year ago

@clragon i found a bug using PagedSliverMasonryGrid: When having long items, after reaching the bottom of the list, the scrolls jumps to the start, (this doens't happend 70% of the time)

Flutter doctor: Flutter (Channel beta, 3.10.0-1.4.pre, on Microsoft Windows [Version 10.0.25309.1000], locale en-US) • Flutter version 3.10.0-1.4.pre on channel beta at C:\src\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision a14a4eac61 (4 days ago), 2023-04-26 12:54:31 +0700 • Engine revision f7ac42e8a2 • Dart version 3.0.0 (build 3.0.0-417.3.beta) • DevTools version 2.23.1

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at C:\Users\G3orG3\AppData\Local\Android\Sdk • Platform android-33, build-tools 31.0.0 • ANDROID_HOME = C:\Users\G3orG3\AppData\Local\Android\Sdk • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694)
• All Android licenses accepted.

[√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Android Studio (version 2022.2) • Android Studio at C:\Program Files\Android\Android Studio • 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.6+0-b2043.56-9586694)

[√] VS Code (version 1.77.3) • VS Code at C:\Users\G3orG3\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.62.0

[√] Connected device (2 available) • Chrome (web) • chrome • web-javascript • Google Chrome 112.0.5615.138 • Edge (web) • edge • web-javascript • Microsoft Edge 112.0.1722.64

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

• No issues found!

Code to test:

class _MyHomePageState extends State<MyHomePage> {
  //create paging controller
  final PagingController<int, String> _pagingController =
      PagingController(firstPageKey: 0);

  @override
  void initState() {
    //setup paging controller
    _pagingController.addPageRequestListener((pageKey) {
      //fetch data from server
      //here we are using random data
      final random = Random();
      final nextPageKey = pageKey + random.nextInt(3) + 1;
      final items = List.generate(3, (index) => '');
      if ((_pagingController.itemList?.length ?? 0) >= 3) {
        _pagingController.appendLastPage(items);
        return;
      }
      Future.delayed(const Duration(seconds: 1), () {
        _pagingController.appendPage(items, nextPageKey);
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(slivers: [
        SliverPadding(
          padding: const EdgeInsets.all(10),
          sliver: SliverList(
              delegate: SliverChildListDelegate([
            const Text(
              "TESTTT",
            ),
          ])),
        ),
        PagedMasonrySliverGrid.extent(
          maxCrossAxisExtent: 500,
          pagingController: _pagingController,
          builderDelegate: PagedChildBuilderDelegate<String>(
            itemBuilder: (c, item, index) {
              return Container(
                margin: const EdgeInsets.all(20),
                width: double.infinity,
                color: Colors.primaries[index % Colors.primaries.length],
                child: //create a list with random items count
                    ListView.builder(
                  shrinkWrap: true,
                  primary: false,
                  itemCount: Random().nextInt(40) + 40,
                  itemBuilder: (context, index2) {
                    return ListTile(
                      title: Text('Parent $index: Child $index2'),
                    );
                  },
                ),
              );
            },
          ),
        )
      ]),
    );
  }
}
prologikus commented 11 months ago

when will this be implemented ? @EdsonBueno

clragon commented 11 months ago

@prologikus thank you for your report. I have also noticed this same issue. I am not sure if this issue is related to the staggered grid package or if it is an issue of my implementation. I will take some time to investigate this, as well as rebase my branch.

clragon commented 11 months ago

@EdsonBueno I have fixed the issue. It was related to the appendix builder changing the widget tree and resetting the state.

Could you take a look at this PR?

EdsonBueno commented 11 months ago

@prologikus can you help us validate this?

prologikus commented 11 months ago

I still use this commit inside my production code and it works. But, the problem is still present.

When there are 2 columns, and the last item is big, the scroll resets here is the full code to test and reproduce:

import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  //create paging controller
  final PagingController<int, String> _pagingController =
      PagingController(firstPageKey: 0);

  @override
  void initState() {
    //setup paging controller
    _pagingController.addPageRequestListener((pageKey) {
      final nextPageKey = pageKey + 1;
      final items = List.generate(3, (index) => '');

      if (nextPageKey > 1) {
        _pagingController.appendLastPage(items);
        return;
      }
      _pagingController.appendPage(items, nextPageKey);
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          PagedMasonrySliverGrid.extent(
            maxCrossAxisExtent: 500,
            pagingController: _pagingController,
            builderDelegate: PagedChildBuilderDelegate<String>(
              itemBuilder: (c, item, index) {
                return Container(
                  width: double.infinity,
                  color: Colors.primaries[index % Colors.primaries.length],
                  child: //create a list with random items count
                      ListView.builder(
                    shrinkWrap: true,
                    primary: false,
                    itemCount: index > 4 ? 80 : 30,
                    itemBuilder: (context, index2) {
                      return ListTile(
                        title: Text('Parent $index: Child $index2'),
                      );
                    },
                  ),
                );
              },
            ),
          )
        ],
      ),
    );
  }
}
clragon commented 11 months ago

Thank you for the example.

When we modify the code to exclude pagination all together:

import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: const MyHomePage(),
      );
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> items = List.generate(6, (index) => '');

  @override
  Widget build(BuildContext context) => Scaffold(
        body: CustomScrollView(
          slivers: [
            SliverMasonryGrid.extent(
              childCount: items.length,
              maxCrossAxisExtent: 500,
              itemBuilder: (context, index) => Container(
                width: double.infinity,
                color: Colors.primaries[index % Colors.primaries.length],
                child: ListView.builder(
                  shrinkWrap: true,
                  primary: false,
                  itemCount: index > 4 ? 80 : 30,
                  itemBuilder: (context, index2) => ListTile(
                    title: Text('Parent $index: Child $index2'),
                  ),
                ),
              ),
            ),
          ],
        ),
      );
}

the same issue still occurs. This is therefore an upstream issue and we should notify the developers of flutter_staggered_grid_view about this behaviour.

clragon commented 10 months ago

@EdsonBueno From my testing, the PR code should work fine, besides the upstream bug. I think we could merge it.

prologikus commented 8 months ago

@EdsonBueno everything works here (y)

junixapp commented 4 months ago

@clragon

PagedMasonryGridView.count(crossAxisCount: 2,
              mainAxisSpacing: 10.w, crossAxisSpacing: 10.w,
              showNoMoreItemsIndicatorAsGridChild: false,
              showNewPageProgressIndicatorAsGridChild: false,
              showNewPageErrorIndicatorAsGridChild: false,
              pagingController: controller.pagingController,
              padding: EdgeInsets.symmetric(vertical: 5.w),
              ...

)

mainAxisSpacing: 10.w, crossAxisSpacing: 10.w, not work, there is no space between child; I read the source of PagedMasonryGridView, I think you forget to pass mainAxisSpacing and crossAxisSpacing to PagedMasonrySliverGrid

@override
  Widget buildChildLayout(BuildContext context) =>
      PagedMasonrySliverGrid<PageKeyType, ItemType>(
        builderDelegate: builderDelegate,
        pagingController: pagingController,
        gridDelegateBuilder: gridDelegateBuilder,
        addAutomaticKeepAlives: addAutomaticKeepAlives,
        addRepaintBoundaries: addRepaintBoundaries,
        addSemanticIndexes: addSemanticIndexes,
        showNewPageProgressIndicatorAsGridChild:
        showNewPageProgressIndicatorAsGridChild,
        showNewPageErrorIndicatorAsGridChild:
        showNewPageErrorIndicatorAsGridChild,
        showNoMoreItemsIndicatorAsGridChild:
        showNoMoreItemsIndicatorAsGridChild,
        shrinkWrapFirstPageIndicators: _shrinkWrapFirstPageIndicators,
        mainAxisSpacing: mainAxisSpacing,  //I add this, problem solved
        crossAxisSpacing: crossAxisSpacing, //I add this, problem solved
      );
clragon commented 4 months ago

Thank you for your report. However, please file an issue next time. This helps us keep track of things better.

rafaelmaia8384 commented 2 months ago

I still haven't managed to use the option to center the items in the PagedMasonryGridView. Can someone help out?

return PagedMasonryGridView<int, T>(
      gridDelegateBuilder: (childCount) {
        return SliverSimpleGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: count,
        );
      },
      showNoMoreItemsIndicatorAsGridChild: false,  <------------------------ this line
      padding: EdgeInsets.symmetric(
        horizontal: width > AppConfig.maxCardWidth
            ? ((width - AppConfig.maxCardWidth) / count)
            : count * 2,
        vertical: count * 2,
      ),
      crossAxisSpacing: count * 2,
      mainAxisSpacing: count * 2,
      reverse: widget.reverse == true,
      physics: widget.scrollPhysics ?? const AlwaysScrollableScrollPhysics(),
      scrollController: widget.scrollController ??
          ScrollController(
            initialScrollOffset: widget.moduleController.scrollOffset,
          ),
      keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
      pagingController: widget.moduleController.pagingController,
      builderDelegate: PagedChildBuilderDelegate<T>(
        firstPageProgressIndicatorBuilder: (context) => widget.firstLoadingPage,
        firstPageErrorIndicatorBuilder: (context) => AppWidgetLoadingErrorPage(
          error: widget.moduleController.pagingController.error,
          details: _errorDetails ?? 'Contate um administrador do sistema.',
          onTryAgain: () => widget.moduleController.pagingController.refresh(),
        ),
        newPageErrorIndicatorBuilder: (context) => AppWidgetLoadingErrorItem(
          error: widget.moduleController.pagingController.error,
          onTryAgain: () =>
              widget.moduleController.pagingController.retryLastFailedRequest(),
        ),
        newPageProgressIndicatorBuilder: (context) =>
            const AppWidgetLoadingItemGrid(),
        noItemsFoundIndicatorBuilder: (context) => widget.emptyItemsPage,
        noMoreItemsIndicatorBuilder: (context) =>
            widget.noMoreItemsIndicator ?? const SizedBox(height: 128),
        itemBuilder: (context, item, index) =>
            widget.itemWidget(item, index) as Widget,
      ),
    );