skip to Main Content

I am new to Flutter/Dart. I am trying to add pagination to my list. I get my Data through RestAPI. All the example I have been seeing online on pagination does not use Future Builder. Here is my code:

I have been able to detect when the scroll gets to the bottom of the page but I am finding it difficult to load more transactions to the old lists.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:xsocial/utils/my_drawer.dart';
import 'package:xsocial/utils/transaction_history_tile.dart';
import 'package:http/http.dart' as http;

class TransactionHistory extends StatefulWidget {
  const TransactionHistory({super.key});

  @override
  State<TransactionHistory> createState() => _TransactionHistoryState();
}

class _TransactionHistoryState extends State<TransactionHistory> {
  final _controller = ScrollController();
  int page = 1;

  Future<List> getListData() async{
    final res = await http.get(Uri.parse('http://194.143.45.195/myappi/cash-transaction-history'));
      var resBody = jsonDecode(res.body); 
      return resBody;
  }

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

    //Setup the listener.
    _controller.addListener(() {
      if (_controller.position.atEdge) {
        bool isTop = _controller.position.pixels == 0;
        if (isTop) {
          print('At the top');
        } else {
          print('At the bottom');
          //Load more data
          page = page + 1;
          getData(int page);
        }
      }
   });
  }

  @override
  Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(
        'Transaction History',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
    ),
    drawer: MyDrawer(),

    body: Column(
      mainAxisSize: MainAxisSize.max,
      children: [

        //Search bar
        SizedBox(height: 15.0),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 15.0),
          child: Container(
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(7),
            ),
            child: TextField(
              decoration: InputDecoration(
                prefixIcon: Icon(Icons.search),
                border: InputBorder.none,
                hintText: 'Search transactions by ID',
              ),
            ),
          ),
        ),
        SizedBox(height: 15.0),

        FutureBuilder<List>(
            //future: getListData(),
            future: getListData(),
            builder: (ctx, snapshot) {
                // Checking if future is resolved or not
                if (snapshot.connectionState == ConnectionState.done) {
                  // If we got an error
                  if (snapshot.hasError) {
                    return Center(
                      child: Text(
                        '${snapshot.error} occurred',
                        style: TextStyle(fontSize: 18),
                      ),
                    );
        
                    // if we got our data
                  } else if (snapshot.hasData) {
                    // Extracting data from snapshot object
                    final data = snapshot.data as List;
                    return TransactionHistoryTile(list: data, scrollController: _controller);
                  }
                }
        
                // Displaying LoadingSpinner to indicate waiting state
                return Center(
                  child: CircularProgressIndicator(),
                );
            }
          ),
       ],
     ),
   );
 }
}

2

Answers


  1. Use StreamBuilder rather then FutureBuilderthe reason being you add new items in a stream and it will not affect the existing items in list and only add new items but it you use FutureBuilder then everytime you add new items in list a new future need to call to reflect changes and that will cause the whole FutureBuilder to rebuild and it will show loader again. So it is better to use StreamBuilder with StreamController or BehaviorSubject to implement pagination in listview or you can use ChangeNotifier but in that case you need to handle intial loading, error everything manually. So check out this article that uses StreamBuilder to implement pagination.

    Login or Signup to reply.
  2. Don’t call your API with a FutureBuilder inside your build function because it will call your endpoint only once. (BTW your loading function doesn’t have a page parameter)

    Also please add types to all your variables

    • It will increase performance
    • Let the analyzer tell you when you try to assign something where it shouldn’t be assigned.
    • and help anyone trying to understand your code

    Dart is a strongly typed language for a good reason.

    Keep the List of your items as a property of your State, so that it will stay the same between rebuilds, and add your newly received items to that list inside a setState() call to make the UI update with the latest content of that list.

    You don’t even need a ScrollController for this if you let your ListView.builder call a getItemAtIndex(int index) function like this (pseudo code)

    // in your state
    Future<List<Item>> getItems(int pageNum);
    
    
    List<Item> items = [];
    int nextPage = 0;
    VaulueNotifier<int> itemCount = ValueNotifier<int>(0);
    
    void loadNextPage(){
        getItems(nextPage++).then((newItems){
          items.allAdd(newItems);
          itemCount.value=items.length;
        }
    }
    
    
    void initState(){
      // load initalPage 
      loadNextPage();
    }
    
    Item getItemAtIndex(int index)
    {
      if (index > items.length-5)
      {
        // this means we are close to the end of the List
        // so we load the next page asynchronously
        loadNextPage();
      }
      return list[index]
    }
    

    Then wrap your ListView.builder inside a ValueListenableBuilder that observes the itemCountproperty. UseitemCount.valueasitemCount:of yourListView.builder`

    By doing this, every time you get to the end of the items you already have in your list, the next page will be loaded, and itemCount will get updated which will then trigger a rebuild of your ListView.

    By using a ValueNotifier and a ValueNotifier builder you don’t need to call setState and you could move this Logic easily into a separate data layer and access it with a state management solution like watch_it or riverpod.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search