Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pagination with ListView in flutter

I'm new at flutter and I have been searching for proper result of pagination for 2 days.

I followed this code Flutter ListView lazy loading But didn't achieve what I want. Look at below code.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';

main() => 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() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int pageNum = 1;
  bool isPageLoading = false;
  List<Map<String, dynamic>> arrayOfProducts;

  Future<List<Map<String, dynamic>>> _callAPIToGetListOfData() async {

    isPageLoading = true;
    final responseDic =
        await http.post(urlStr, headers: accessToken, body: paramDic);
    Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
    List<Map<String, dynamic>> temArr = List<Map<String, dynamic>>.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 <= 0 && 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<List<Map<String, dynamic>>>(
              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']}'),
                              );
                            }),
                      );
                    }
                }
              }),
        ));
  }
}

So, When I reach at bottom of the page my _scrollListener method get call and there I have set setState(().... 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. https://github.com/istyle-inc/LoadMoreTableViewController/blob/master/screen.gif

Edited:

Final code: (Guide by @Rémi Rousselet)

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';

main() => 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() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int pageNum = 1;
  bool isPageLoading = false;
  List<Map<String, dynamic>> arrayOfProducts;
  ScrollController controller;
  Future<List<Map<String, dynamic>>> 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: <Widget>[
              ListTile(
                title: Text('$index ${arrayOfProducts[index]['title']}'),
                leading: CircleAvatar(backgroundImage: NetworkImage(arrayOfProducts[index]['thumbnail'] ?? "")),
              ),
              Container(
                color: Colors.black12,
                height: (index == arrayOfProducts.length-1 && totalRecord > 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<List<Map<String, dynamic>>>(
              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<List<Map<String, dynamic>>> _callAPIToGetListOfData() async {

    isPageLoading = true;
    final responseDic =
        await http.post(urlStr, headers: accessToken, body: paramDic);
    Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
    List<Map<String, dynamic>> temArr =
        List<Map<String, dynamic>>.from(dicOfRes["data"]["products"]);
    setState(() {
      if (pageNum == 1) {
        totalRecord = dicOfRes["total_record"];
        print('============>>>>>>> $totalRecord');
        arrayOfProducts = temArr;
      } else {
        arrayOfProducts.addAll(temArr);
      }
      pageNum++;
    });
    return arrayOfProducts;
  }

  _scrollListener() {
    if (totalRecord == arrayOfProducts.length) {
      return;
    }
    print(controller.position.extentAfter);
    if (controller.position.extentAfter <= 0 && isPageLoading == false) {
      _callAPIToGetListOfData();
    }
  }
}

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..

like image 580
Govaadiyo Avatar asked Dec 14 '18 09:12

Govaadiyo


2 Answers

Add this to your listener of controller

if (scrollController.position.maxScrollExtent == scrollController.offset) {

        /***
         * we need to get new data on page scroll end but if the last
         * time when data is returned, its count should be Constants.itemsCount' (10)
         *
         * So we calculate every time
         *
         * productList.length >= (Constants.itemsCount*pageNumber)
         *
         * list must contain the products == Constants.itemsCount if the page number is 1
         * but if page number is increased then we need to calculate the total
         * number of products we have received till now example:
         * first time on page scroll if last count of productList is Constants.itemsCount
         * then increase the page number and get new data.
         * Now page number is 2, now we have to check the count of the productList
         * if it is==Constants.itemsCount*pageNumber (20 in current case) then we have
         * to get data again, if not then we assume, server has not more data then
         * we currently have.
         *
         */
        if (productList.length >= (Constants.itemsCount * pageNumber) &&
            !isLoading) {
          pageNumber++;
          print("PAGE NUMBER $pageNumber");
          print("getting data");
          getProducts(false); // Hit API to get new data
        }
      }
like image 169
Lakhwinder Singh Avatar answered Oct 04 '22 15:10

Lakhwinder Singh


https://pub.dev/packages/pagination_view is the package I found the more intuitive to work with that needed the least changes from a page view and felt the more intuitive:

It's easy to move from a listview to a PaginationView, the class that holds the widget needs to be stateful or it won't work:

      PaginationView<User>(
        preloadedItems: <User>[
          User(faker.person.name(), faker.internet.email()),
          User(faker.person.name(), faker.internet.email()),
        ],
        itemBuilder: (BuildContext context, User user, int index) => ListTile(
          title: Text(user.name),
          subtitle: Text(user.email),
          leading: IconButton(
            icon: Icon(Icons.person),
            onPressed: () => null,
          ),
        ),
        paginationViewType: PaginationViewType.listView // optional
        pageFetch: pageFetch,
        onError: (dynamic error) => Center(
          child: Text('Some error occured'),
        ),
        onEmpty: Center(
          child: Text('Sorry! This is empty'),
        ),
        bottomLoader: Center( // optional
          child: CircularProgressIndicator(),
        ),
        initialLoader: Center( // optional
          child: CircularProgressIndicator(),
        ),
      ),
like image 35
Sanhaji Omar Avatar answered Oct 04 '22 17:10

Sanhaji Omar