title: Pagination with ListView in flutter
body: <p>I'm new at flutter and I have been searching for proper result of pagination for 2 days.</p>
  
  <p>I followed this code <a href="https://stackoverflow.com/questions/49508322/flutter-listview-lazy-loading">Flutter ListView lazy loading</a>
  But didn't achieve what I want. Look at below code.</p>
  
  <pre><code>import 'package:flutter/material.dart';
  import 'package:http/http.dart' as http;
  import 'dart:async';
  import 'dart:convert';
  
  main() =&gt; runApp(InitialSetupPage());
  
  class InitialSetupPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: "API Pagination",
        debugShowCheckedModeBanner: false,
        theme: ThemeData(primarySwatch: Colors.green),
        home: HomePage(),
      );
    }
  }
  
  class HomePage extends StatefulWidget {
    @override
    _HomePageState createState() =&gt; _HomePageState();
  }
  
  class _HomePageState extends State&lt;HomePage&gt; {
    int pageNum = 1;
    bool isPageLoading = false;
    List&lt;Map&lt;String, dynamic&gt;&gt; arrayOfProducts;
  
    Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; _callAPIToGetListOfData() async {
  
      isPageLoading = true;
      final responseDic =
          await http.post(urlStr, headers: accessToken, body: paramDic);
      Map&lt;String, dynamic&gt; dicOfRes = json.decode(responseDic.body);
      List&lt;Map&lt;String, dynamic&gt;&gt; temArr = List&lt;Map&lt;String, dynamic&gt;&gt;.from(dicOfRes["data"]["products"]);
  
      if (pageNum == 1) {
        arrayOfProducts = temArr;
      } else {
        arrayOfProducts.addAll(temArr);
      }
  
      return arrayOfProducts;
    }
  
    ScrollController controller;
  
    @override
    void initState() {
      controller = new ScrollController()..addListener(_scrollListener);
      super.initState();
    }
  
    _scrollListener() {
      print(controller.position.extentAfter);
      if (controller.position.extentAfter &lt;= 0 &amp;&amp; isPageLoading == false) {
        _callAPIToGetListOfData().then((data){
          setState(() {
  
          });
        });
      }
    }
  
    @override
    void dispose() {
      controller.removeListener(_scrollListener);
      super.dispose();
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(
            title: Text("Online Products"),
            centerTitle: true,
          ),
          body: Container(
            child: FutureBuilder&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt;(
                future: _callAPIToGetListOfData(),
                builder: (BuildContext context, AsyncSnapshot snapshot) {
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                    case ConnectionState.active:
                    case ConnectionState.waiting:
                      return Center(child: CircularProgressIndicator());
                    case ConnectionState.done:
                      if (snapshot.hasError) {
                        Text(
                            'YOu have some error : ${snapshot.hasError.toString()}');
                      } else if (snapshot.data != null) {
                        isPageLoading = false;
                        pageNum++;
  
                        print(arrayOfProducts);
                        return Scrollbar(
                          child: ListView.builder(
                              itemCount: arrayOfProducts.length,
                              controller: controller,
                              physics: AlwaysScrollableScrollPhysics(),
                              itemBuilder: (context, index) {
                                return ListTile(
                                  title: Text(
                                      '$index ${arrayOfProducts[index]['title']}'),
                                );
                              }),
                        );
                      }
                  }
                }),
          ));
    }
  }
  </code></pre>
  
  <p>So, When I reach at bottom of the page my <code>_scrollListener</code> method get call and there I have set <code>setState(()....</code> method to reload widget. Issue is I load my actual position and It starts with top of the list. So where I'm going wrong? Actually I want like. <a href="https://github.com/istyle-inc/LoadMoreTableViewController/blob/master/screen.gif" rel="noreferrer">https://github.com/istyle-inc/LoadMoreTableViewController/blob/master/screen.gif</a></p>
  
  <h1>Edited:</h1>
  
  <blockquote>
    <p><strong>Final code:</strong> <em>(Guide by  <strong>@Rémi Rousselet</strong>)</em></p>
  </blockquote>
  
  <pre><code>import 'package:flutter/material.dart';
  import 'package:http/http.dart' as http;
  import 'dart:async';
  import 'dart:convert';
  
  main() =&gt; runApp(InitialSetupPage());
  
  class InitialSetupPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: "API Pagination",
        debugShowCheckedModeBanner: false,
        theme: ThemeData(primarySwatch: Colors.green),
        home: HomePage(),
      );
    }
  }
  
  class HomePage extends StatefulWidget {
    @override
    _HomePageState createState() =&gt; _HomePageState();
  }
  
  class _HomePageState extends State&lt;HomePage&gt; {
    int pageNum = 1;
    bool isPageLoading = false;
    List&lt;Map&lt;String, dynamic&gt;&gt; arrayOfProducts;
    ScrollController controller;
    Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; future;
    int totalRecord = 0;
  
    @override
    void initState() {
      controller = new ScrollController()..addListener(_scrollListener);
      future = _callAPIToGetListOfData();
  
      super.initState();
    }
  
    @override
    void dispose() {
      controller.removeListener(_scrollListener);
      super.dispose();
    }
  
    @override
    Widget build(BuildContext context) {
      final constListView = ListView.builder(
          itemCount: arrayOfProducts == null ? 0 : arrayOfProducts.length,
          controller: controller,
          physics: AlwaysScrollableScrollPhysics(),
          itemBuilder: (context, index) {
            return Column(
              children: &lt;Widget&gt;[
                ListTile(
                  title: Text('$index ${arrayOfProducts[index]['title']}'),
                  leading: CircleAvatar(backgroundImage: NetworkImage(arrayOfProducts[index]['thumbnail'] ?? "")),
                ),
                Container(
                  color: Colors.black12,
                  height: (index == arrayOfProducts.length-1 &amp;&amp; totalRecord &gt; arrayOfProducts.length) ? 50 : 0,
                  width: MediaQuery.of(context).size.width,
                  child:Center(
                    child: CircularProgressIndicator()
                  ),
                )
              ],
            );
          });
      return Scaffold(
          appBar: AppBar(
            title: Text("Online Products"),
            centerTitle: true,
          ),
          body: Container(
            child: FutureBuilder&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt;(
                future: future,
                builder: (BuildContext context, AsyncSnapshot snapshot) {
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                    case ConnectionState.active:
                    case ConnectionState.waiting:
                      return Center(child: CircularProgressIndicator());
                    case ConnectionState.done:
                      if (snapshot.hasError) {
                        Text(
                            'YOu have some error : ${snapshot.hasError.toString()}');
                      } else if (snapshot.data != null) {
                        isPageLoading = false;
  
                        print(arrayOfProducts);
                        return constListView;
                      }
                  }
                }),
          ));
    }
  
    Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; _callAPIToGetListOfData() async {
  
      isPageLoading = true;
      final responseDic =
          await http.post(urlStr, headers: accessToken, body: paramDic);
      Map&lt;String, dynamic&gt; dicOfRes = json.decode(responseDic.body);
      List&lt;Map&lt;String, dynamic&gt;&gt; temArr =
          List&lt;Map&lt;String, dynamic&gt;&gt;.from(dicOfRes["data"]["products"]);
      setState(() {
        if (pageNum == 1) {
          totalRecord = dicOfRes["total_record"];
          print('============&gt;&gt;&gt;&gt;&gt;&gt;&gt; $totalRecord');
          arrayOfProducts = temArr;
        } else {
          arrayOfProducts.addAll(temArr);
        }
        pageNum++;
      });
      return arrayOfProducts;
    }
  
    _scrollListener() {
      if (totalRecord == arrayOfProducts.length) {
        return;
      }
      print(controller.position.extentAfter);
      if (controller.position.extentAfter &lt;= 0 &amp;&amp; isPageLoading == false) {
        _callAPIToGetListOfData();
      }
    }
  }
  </code></pre>
  
  <p>Thats working but Is it right/good way? little bit confused because if I reach at end of the page and scroll up/down it seems little bit sticky while scrolling..</p>