i have an api call that upload data by offset, my goal is to load 10 by 10 on user scroll down, the problem is that i can't scroll down and show more data: here is my snippets:

class Body extends StatefulWidget {
  @override
  _BodyState createState() => _BodyState();
}

class _BodyState extends State<Body> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          HomeHeader(),
          ProductsGridViewInfiniteScroll(),
        ],
      ),
    );
  }
}

here the scrollcontroller seems doesn't work:

class ProductsGridViewInfiniteScroll extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ProductsGridViewInfiniteScrollState();
  }
}

class ProductsGridViewInfiniteScrollState
    extends State<ProductsGridViewInfiniteScroll> {
  Future<ProductList> products;
  int offset;
  ScrollController _controller;

  _scrollListener() {
    if (_controller.offset >= _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      setState(() {
        offset += 10;
        products = loadProductsByIdService(1, offset, 10);
      });
    }
  }

  @override
  void initState() {
    offset = 0;
    products = loadProductsByIdService(1, offset, 10);
    _controller = ScrollController();
    _controller.addListener(_scrollListener);
    super.initState();
  }

  Widget build(BuildContext context) {
    return FutureBuilder<ProductList>(
        future: products,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return GridView.builder(
                controller: _controller,
                itemCount: snapshot.data.products.length,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    crossAxisSpacing: 4.0,
                    mainAxisSpacing: 10.0),
                shrinkWrap: true,
                physics: ScrollPhysics(),
                itemBuilder: (BuildContext ctx, index) {
                  return Container(
                    alignment: Alignment.center,
                    child: ProductCard(product: snapshot.data.products[index]),
                  );
                });
          } else {
            return SizedBox();
          }
        });
  }
}
Future<ProductList> loadProductsByIdService(serviceid, offset, limit) async {
  var datamap = {'service_id': serviceid, 'offset': offset, 'limit': limit};
  var data = json.encode(datamap);
  ProductList products;

  final response = await http.post(Uri.parse(PRODUCTS),
      headers: {
        "Accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: data,
      encoding: Encoding.getByName("utf-8"));

  if (response.body.isNotEmpty) {
    if (response.statusCode == 200) {
      products = ProductList.fromJson(json.decode(response.body));
    }
  } else {
    throw Exception('echec de chargement des produits');
  }

  return products;
}

i want to rebuild the build function and update the products variable every time the scroll reaches the bottom of the screeen, any help please;


Solution 1: Eduard Hasanaj

It looks like ClampingScrollPhysics is the default behavior of scroll physics in Android and BouncingScrollPhysics. Most probably you are running on IOS so by default you are using BouncingScrollPhysics. If that is the case, please change your if condition to:

_scrollListener() {
    if (_controller.offset >= _controller.position.maxScrollExtent &&
        _controller.position.outOfRange) {
      _controller.jumpTo(0);
      setState(() {
        offset += 10;
        products = getProducts(offset, limit);
      });
    }
  }

When bouncing physics is used, the scrolling passes max scrolling extend and outOfRange becomes true. You need to reset controller offset back to 0 as it will keep calling the listener multiple times which skips pages by increasing offset.


Solution 2: ELHIT Fatima

if anyone have the same issue this is the final solution that worked for me without the use of FutureBuilder :

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new HomeState();
}

class HomeState extends State<Home> {
  static int offset = 0;
  ScrollController _sc = new ScrollController();
  bool isLoading = false;
  ProductList products = new ProductList(products: []);

  @override
  void initState() {
    this._getMoreData(1, offset, 10);
    super.initState();
    _sc.addListener(() {
      if (_sc.position.pixels == _sc.position.maxScrollExtent) {
        _getMoreData(1, offset += 10, 10);
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return _buildGrid();
  }

  Widget __buildGrid() {
    return GridView.builder(
        controller: _sc,
        itemCount: products.products.length + 1,
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
        ),
        shrinkWrap: true,
        physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
        itemBuilder: (BuildContext ctx, index) {
          if (index == products.products.length) {
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: new Center(
                child: new Opacity(
                  opacity: isLoading ? 1.0 : 00,
                  child: new CircularProgressIndicator(),
                ),
              ),
            );
          } else {
            return Container(
              alignment: Alignment.topLeft,
              // child: Flexible(
              child: ProductCard(product: products.products[index]),
              // ),
            );
          }
        });

  }

  void _getMoreData(serviceid, offset, limit) async {
    if (!isLoading) {
      setState(() {
        isLoading = true;
      });
      var datamap = {'service_id': serviceid, 'offset': offset, 'limit': limit};
      var data = json.encode(datamap);
      ProductList ps;

      final response = await http.post(Uri.parse(PRODUCTS),
          headers: {
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded"
          },
          body: data,
          encoding: Encoding.getByName("utf-8"));

      if (response.statusCode == 200) {
        ps = ProductList.fromJson(json.decode(response.body));
        setState(() {
          isLoading = false;
          products.products.addAll(ps.products);
          // offset += 10;
        });
      }
    }
  }
}