Search code examples
listflutterdartlistviewnavigation

Is there a way in Flutter to split a long ListView into multiple "pages"?


I'm a beginner in coding and I'm trying to create an app to practice and learning dart and flutter.

My app has a page with a long ListView (700 items) and I want to make some sort of "page navigator" to split the ListView in 7 (100 items per page). I know about the pagination ListView but I don't link the result. I'm sorry for my poor vocabulary on explaining what I want to do, here's an example

Example of "pages navigator"

Ultimately, I want that all my items in this "splitted ListView" could be filterable by a search bar.

Here's my list.dart code so far:

class SongList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _buildListView(context),
    );
  }

  ListView _buildListView(BuildContext context) {
    return ListView.separated(
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      itemCount: 700,
      itemBuilder: (_, index) {
        final count = index + 1;

        return new ListTile(
          leading: new CircleAvatar(
            child: new Text(
              "$count",
              style: TextStyle(color: kBackgroundColor),
            ),
            backgroundColor: kPrimaryColor,
          ),
          title: new Text("Song #$count"),
          trailing: Icon(Icons.navigate_next),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => SongDetail(index),
              ),
            );
          },
        );
      },
      separatorBuilder: (context, index) {
        return Divider();
      },
    );
  }
}

Solution

  • Update: I made it!

    I discovered this package which is amazing! https://pub.dev/packages/number_paginator

    So I updated the code from below and replaced the CupertinoSlidingSegmentedControl with the NumberPaginator widget.

    Here the code:

                  child: NumberPaginator(
                    numberPages: 7,
                    onPageChange: (int index) {
                      setState(() {
                        currentPage = index;
                      });
                    },
                  ),
    

    The packages provide even some properties in order to customize the colors and shape of the page indicators.


    Old solution

    I hope this will be helpful for someone. Please note that I'm still a learner and if somethig in this code is off and might be better comment and add your contribution.

    I didn't find any native "paging widget" in Flutter so I ended up using CupertinoSlidingSegmentedControl for the "paging widget" and CupertinoUserInterfaceLevel for the "body" of the segments/pages.

    My song list is provided by a database helper and some queries, I dediced to split the my 700 items list in 7 queries and call them according to the selected segment/page indicator.

    This is my code with some useful comments.

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart'; // Required in order to use Cupertino Widgets
    
    import '/assets/data/queries.dart'; // My queries are in another dart file
    import '/assets/data/db_tables.dart';
    
    import 'songs_detail.dart';
    
    class SongsBody extends StatefulWidget {
      @override
      _SongsBodyState createState() => _SongsBodyState();
    }
    
    class _SongsBodyState extends State<SongsBody> {
      // Change this in order to edit the segment/paging widget, you can put text and icons
      final Map<int, Widget> children = const <int, Widget>{
        0: Text('1'),
        1: Text('2'),
        2: Text('3'),
        3: Text('4'),
        4: Text('5'),
        5: Text('6'),
        6: Text('7'),
      };
    
      // Change this to set the initial segment/page
      int currentSegment = 0;
    
      // This is required to update the state when you tap on another segment/page
      void onValueChanged(newValue) {
        setState(() {
          currentSegment = newValue;
        });
      }
    
      // My queries from queries.dart are called here and used later in the code
      final List<Future<List?>> _query = [
        QueryCtr().getSongsFrom1To100(),
        QueryCtr().getSongsFrom101To200(),
        QueryCtr().getSongsFrom201To300(),
        QueryCtr().getSongsFrom301To400(),
        QueryCtr().getSongsFrom401To500(),
        QueryCtr().getSongsFrom501To600(),
        QueryCtr().getSongsFrom601To700(),
      ];
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            // This is the segment control widget (our "paging" widget)
            CupertinoSlidingSegmentedControl<int>(
              children: children,
              onValueChanged: onValueChanged,
              groupValue: currentSegment,
            ),
            const Divider(),
            // This is the "body" of our segment/page
            CupertinoUserInterfaceLevel(
              data: CupertinoUserInterfaceLevelData.base,
              // My listview is now called with a future builder because I'm getting the data from my database
              child: Builder(
                builder: (BuildContext context) {
                  return FutureBuilder<List?>(
                    // This is the interesting part: I update only the query according to the selected segment
                    future: _query[currentSegment],
                    initialData: const [],
                    builder: (context, snapshot) {
                      return snapshot.hasData
                      // If there is some data show them in a listview wrapped in expanded
                          ? Expanded(
                              child: ListView.separated(
                                physics: const ScrollPhysics(),
                                shrinkWrap: true,
                                itemCount: snapshot.data!.length,
                                itemBuilder: (context, i) {
                                  // Here I decided to code a separate widget to build the rows of my listview
                                  return _buildRow(snapshot.data![i]);
                                },
                                separatorBuilder: (context, index) {
                                  return const Divider();
                                },
                              ),
                            ) 
                      // If there is no data return a progress indicator
                          : const Center(
                            child: CircularProgressIndicator(),
                          );
                    },
                  );
                },
              ),
            ),
          ],
        );
      }
    
      Widget _buildRow(Raccolta get) {
        return ListTile(
          leading: CircleAvatar(
            child: Text(
              get.songId.toString(),
            ),
          ),
          title: Text(get.songTitle),
          trailing: const Icon(
            Icons.navigate_next,
            color: somecolor,
          ),
          onTap: () {
            FocusScope.of(context).unfocus();
            int songId = get.songId;
            String songTitle = get.songTitle;
            String songText = get.songText;
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) {
                  return SongsDetail(songId, songTitle, songText);
                },
              ),
            );
          },
        );
      }
    }