Home > Mobile >  Load more datas when scrolling on a GridView Flutter
Load more datas when scrolling on a GridView Flutter

Time:10-18

I am making an application where I can run through items in a GridView on a profile page, like on Instagram when we scroll our posts.

I want to load more items (15 per 15) when I scroll on my GridView. I want an infinite loading.

So I added a ScrollListener to my GridView.

If I put an "initialScrollOffset" to "5.0" in attribute to my ScrollListener, it will load the 15 first items and make one loading, so it's add 15 items (work only 1 time), but if I let the default value, it loads no items.

I would like to have an infinite loading.

My GridView code :


import 'dart:developer';

import 'package:dresskip/model/item_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class ItemSection extends StatefulWidget {
  const ItemSection({Key? key}) : super(key: key);
  @override
  _ItemSectionState createState() => _ItemSectionState();
}

class _ItemSectionState extends State<ItemSection> {
  List<Item> items = [];
  bool isLoading = false;
  int pageCount = 1;
  ScrollController _scrollController = ScrollController(initialScrollOffset: 5.0);

  @override
  void initState() {
    super.initState();

    ///LOADING FIRST  DATA
    addItemsToList(1);

    _scrollController.addListener(_scrollListener);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(),
        GridView.count(
          controller: _scrollController,
          crossAxisCount: 3,
          shrinkWrap: true,
          //physics: const ScrollPhysics() /*AlwaysScrollableScrollPhysics()*/,
          mainAxisSpacing: 0,
          children: items.map((value) {
            return Image.network(value.picture);
          }).toList(),
        )
      ],
    );
  }

  //// ADDING THE SCROLL LISTINER
  _scrollListener() {
    //inspect(_scrollController.offset);
    if (_scrollController.offset >=
            _scrollController.position.maxScrollExtent &&
        !_scrollController.position.outOfRange) {
      setState(() {
        print("comes to bottom $isLoading");
        isLoading = true;

        if (isLoading) {
          print("RUNNING LOAD MORE");

          pageCount = pageCount   1;

          addItemsToList(pageCount);
        }
      });
    }
  }

  addItemsToList(int page) {
    //if (page < 5) {}
    Item myItem = Item(
        name: "test",
        brand: "test",
        color: ["0xFF39BDC8", "0xFFdb8abc", ""],
        picture:
            "https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
        //"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
        solo: false,
        clean: true,
        type: "test");
    for (int i = (pageCount * 15) - 15; i < pageCount * 15; i  ) {
      items.add(myItem);
      isLoading = false;
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
}

Item model class

class Item {
  String name;
  String brand;
  List<String> color;
  String picture;
  bool solo;
  bool clean;
  String type;

  Item({
    required this.name,
    required this.brand,
    required this.color,
    required this.picture,
    required this.solo,
    required this.clean,
    required this.type,
  });
}


The first part (profile section) code

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '/assets/constants.dart' as constants;
import '../../assets/dresskip_icon_icons.dart' as DresskipIcons;

class ProfileSection extends StatelessWidget {
  final List<String> description;
  final VoidCallback onClicked;

  const ProfileSection({
    Key? key,
    required this.description,
    required this.onClicked,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
        padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment
                  .spaceBetween, //pour que chaque colonnes dans la ligne ait leurs propres tailles
              crossAxisAlignment: CrossAxisAlignment
                  .start, //pour tout coller en haut du container
              children: <Widget>[
                const Icon(Icons.local_laundry_service),
                Stack(children: [
                  buildImage(),
                  Positioned(
                      bottom: 0,
                      right: 4,
                      child:
                          buildEditIcon(Color(constants.COLOR_BLUE_DRESSKIP)))
                ]),
                const Icon(Icons.settings),
              ],
            ),
            // Partie description
            Container(
                child: Text(description[0]),
                margin: EdgeInsets.fromLTRB(50, 5, 50, 5)),
            // Partie Instagram
            Container(
                child: Row(
                  children: <Widget>[
                    Container(
                      child: const Icon(DresskipIcons.DresskipIcon.instagram),
                      margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
                    ),
                    Expanded(child: Text(description[1]))
                  ],
                ),
                margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
            // Partie Facebook
            Container(
                child: Row(
                  children: <Widget>[
                    Container(
                      child: const Icon(Icons.facebook),
                      margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
                    ),
                    Expanded(child: Text(description[2]))
                  ],
                ),
                margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
            // Partie Twitter
            Container(
                child: Row(
                  children: <Widget>[
                    Container(
                      child:
                          const Icon(DresskipIcons.DresskipIcon.twitter_square),
                      margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
                    ),
                    Expanded(child: Text(description[3]))
                  ],
                ),
                margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
          ],
        ));
  }

  // Widget pour afficher l'image
  Widget buildImage() {
    // use if is an image on the web with the link : final image = NetworkImage(imagePath);

    return ClipOval(
        child: Material(
            color: Colors.transparent,
            child: Ink.image(
              image: const AssetImage("assets/undraw_female_avatar.png"),
              fit: BoxFit.cover,
              width: 128,
              height: 128,
              child: InkWell(onTap: onClicked),
            )));
  }

  // Widget pour l'ajout de l'icone à coté de l'image
  // Ici, il y a 2 fois buildCircle pour arrondir l'icone et ensuite mettre le trait blanc arrondi entre la photo et l'icône
  Widget buildEditIcon(Color color) => buildCircle(
      color: Colors.white,
      all: 1,
      child: buildCircle(
          color: color,
          all: 8,
          child: Icon(Icons.add_a_photo, color: Colors.white, size: 20)));

  // Widget permettant d'arrondir l'image
  Widget buildCircle({
    required Widget child,
    required double all,
    required Color color,
  }) =>
      ClipOval(
          child: Container(
              padding: EdgeInsets.all(all), color: color, child: child));
}

The parent page code

import 'package:dresskip/model/user_model.dart';
import 'package:flutter/material.dart';

import 'itemSection_widget.dart';
import 'profileSection_widget.dart';
import '/assets/constants.dart' as constants;

import 'dart:convert';

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

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        body: SingleChildScrollView(
            child: Column(
      children: [
        ProfileSection(
            description: getUserInformation().properties,
            onClicked: () async {}),
        const Divider(
          color: Color(constants.COLOR_BLUE_DRESSKIP),
          thickness: 2,
          indent: 50,
          endIndent: 50,
        ),
        ItemSection()
      ],
    )));
  }

  getUserInformation() {
    User myUser = User(username: "test", email: "[email protected]", properties: [
      "22 yo\nFlexeur/Epicurien/Philanthrope\nJ'adore la vie\nEFREI Paris",
      "instagram_test",
      "facebook_test",
      "twitter"
    ]);
    return myUser;
  }
}

There are 2 screenshots of my App.

The problem here, it's just loading 15 15 items (the first 30) and I can't load more data on scrolling.

My App with my GridView : 1

My App With my GridView at the end of the GridView

EDIT

I find a way to resolv this problem. The attribute "shrinkwrap" block the possibility to scroll more because my widget which contains the gridview is into a Column Widget.

So i removed it, but just the Gridview is scrolling, I would like to do like Instagram's profil, when you scroll on your pictures, all the page scroll and not only the GridView.

Do you have an idea ?

CodePudding user response:

Firstly, I'm not aware of Instagram UI.

The problem is here with parent, while SingleChildScrollView is the parent and you want to scroll the full page and generate GridItem based on it, therefore set we don't need to ScrollController for GridView instead use it on SingleChildScrollView.

The code structure will be

  • parent class generate data for GridView and will be StatefulWidget.
  • while there are two scrollable widgets so that GridView < physics: NeverScrollableScrollPhysics(),>
  • AccountPage: SingleChildScrollView will have that _scrollController
  • ItemSection can be StatelessWidget

Full Code snippet with dummyHeaderWidget

import 'package:flutter/material.dart';

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

  @override
  State<AccountPage> createState() => _AccountPageState();
}

class _AccountPageState extends State<AccountPage> {
  ScrollController _scrollController = ScrollController();
  bool isLoading = true; // 

  int pageCount = 1;
  List items = [];
  //// ADDING THE SCROLL LISTINER
  void _scrollListener() {
    print(
        "current ${_scrollController.offset}  max: ${_scrollController.position.maxScrollExtent}");

    if (_scrollController.offset >=
            _scrollController.position.maxScrollExtent &&
        !_scrollController.position.outOfRange) {
      setState(() {
        print("comes to bottom $isLoading");
        isLoading = true;

        if (isLoading) {
          print("RUNNING LOAD MORE");

          pageCount = pageCount   1;
          addItemsToList(pageCount);
        }
      });
    }
  }

  addItemsToList(int page) {
    //if (page < 5) {}
    Item myItem = Item(
        name: "test",
        brand: "test",
        color: ["0xFF39BDC8", "0xFFdb8abc", ""],
        picture:
            "https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
        //"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
        solo: false,
        clean: true,
        type: "test");
    for (int i = (pageCount * 15) - 15; i < pageCount * 15; i  ) {
      items.add(myItem);
      isLoading = false;
    }
  }

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_scrollListener);
    addItemsToList(1);
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        controller: _scrollController,
        child: Column(
          children: [
            Container(
              height: 300,
              color: Colors.green,
            ),
            const Divider(
              thickness: 2,
              indent: 50,
              endIndent: 50,
            ),
            ItemSection(
              items: items,
            ),
          ],
        ),
      ),
    );
  }
}

class ItemSection extends StatelessWidget {
  final List items;
  const ItemSection({
    Key? key,
    required this.items,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 3,
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      mainAxisSpacing: 0,
      children: items.map((value) {
        return Image.network(value.picture);
      }).toList(),
    );
  }
}

class Item {
  String name;
  String brand;
  List<String> color;
  String picture;
  bool solo;
  bool clean;
  String type;

  Item({
    required this.name,
    required this.brand,
    required this.color,
    required this.picture,
    required this.solo,
    required this.clean,
    required this.type,
  });
}

Read the code, hope you will get concept and make changes on your project.

CodePudding user response:

Endless / Infinite Scroll GridView

This example uses a GridView.builder & doesn't need a ScrollController.

When the end of the current dataset is reached, it will request more data and rebuild the GridView.

We can pad the end of the dataset with a special item. When this special item is built by GridView.builder, it will:

  1. show a loading indicator
  2. request more data from datasource
  3. rebuild the GridView when data arrives
import 'package:flutter/material.dart';

class InfiniteScrollPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Infinite Scroll'),
      ),
      body: EndlessGrid());
  }
}

class EndlessGrid extends StatefulWidget {
  @override
  _EndlessGridState createState() => _EndlessGridState();
}

class _EndlessGridState extends State<EndlessGrid> {
  NumberGenerator _numGen = NumberGenerator();
  List<int> _data = [];

  @override
  void initState() {
    super.initState();
    _data = _numGen.nextPage();
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: _data.length   1, // pad data with an extra item at end
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisSpacing: 10, mainAxisSpacing: 10, crossAxisCount: 2),
      itemBuilder: (context, i) {
        if (i < _data.length) {
          return gridItem(i);
        }
        else { // extra item will request next page & rebuild widget
          getNextPage();
          return CircularProgressIndicator();
        }
      },
    );
  }

  Widget gridItem(int i) {
    return Container(
      alignment: Alignment.center,
      decoration: BoxDecoration(
        color: Colors.lightBlue
      ),
      padding: EdgeInsets.all(5),
      child: Text('$i'),
    );
  }

  /// Request next page of data and call setState to rebuild GridView with
  /// new data.
  Future<void> getNextPage() async {
    var _nextPage = await Future.delayed(Duration(seconds: 2), _numGen.addPage);
    setState(() {
      _data = _nextPage;
    });

  }
}

/// Mock data to fill GridView
class NumberGenerator {
  static const PAGESIZE = 15;
  List<int> dataset = [];
  final int start;

  NumberGenerator({this.start = 0});

  List<int> addItem() {
    dataset.add(lastItem   1);
    return dataset;
  }

  List<int> addPage() {
    dataset.addAll(nextPage());
    return dataset;
  }

  int get lastItem => dataset.isNotEmpty ? dataset.last : start;

  List<int> nextPage({int start, int size = PAGESIZE}) {
    start ??= lastItem;
    return List<int>.generate(size, (i) => start   i   1);
  }
}
  • Related