DEV Community

Cover image for Flutter pagination ft. Bloc
Md. Mobin
Md. Mobin

Posted on

Flutter pagination ft. Bloc

Welcome back, everyone! Today, we will be learning about implementing pagination in Flutter using Bloc as our state management solution. To fetch products, we will be using a modified version of the fake-store APIs that includes pagination.

Let's begin with the basics before diving into Flutter-specific details.

Do not worry we are going to talk about bloc's basic

Why Pagination?

Pagination refers to the practice of dividing content into multiple pages to improve user experience and page load times. It is commonly used in websites and applications that feature long-form content such as articles, product listings, and search results.

Setup Project

  • Add the following dependency to your Flutter project(pubspec.yaml)

        flutter_bloc: ^8.1.2
        http: ^0.13.5
        dartz: ^0.10.1
        shimmer: ^2.0.0
        cached_network_image: ^3.2.3
    
  • darts: In Flutter, Dartz is a library that provides functional programming tools such as the Either data type. Either data type represents a value that can be one of two possible types, typically used to represent the success or failure of a computation.

  • shimmer: Shimmer library is being used for showing loading indicator link on LinkedIn, Twitter, Facebook etc.

  • cached_network_image: Flutter library, as its name suggests, is used for caching network images.

  • http: Flutter library for calling HTTP requests.

Project file Structure:

  • models: following folders will contain models that will be used in the application.

  • presentation: The following layer has been divided further into blocs,pages, and widgets.

    • widgets: Widgets are the building blocks of the user interface in Flutter. They are reusable UI elements that can be combined to create complex layouts. Widgets can be stateless, meaning they do not change over time, or stateful, meaning they can change based on user interaction or other events.

    • blocs: Blocs are a pattern for managing state in Flutter applications. They stand for Business Logic Components and are responsible for managing the flow of data between the presentation layer and the data layer.

    • pages: Pages are the top-level UI elements in a Flutter application. Each page typically represents a separate screen or view.

  • repo: Stands for the repository which has logic for calling APIs it passes data to models.

Parsing APIs Data to Dart Model-Classes

Don't know how to parse JSON data to Dart class?

  • We are going to use a modified version of FakeStore APIs.
Base URL="https://flutter-pagination-api-djsmk123.vercel.app/api
  • Endpoint /get-products?page=1 by default page=1. How the response will look like?

      {
    
          "products": [
              {
                  "id": 1,
                  "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
                  "price": 109.95,
                  "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
                  "category": "men's clothing",
                  "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
                  "rating": {
                      "rate": 3.9,
                      "count": 120
                  }
              },
              {
                  "id": 2,
                  "title": "Mens Casual Premium Slim Fit T-Shirts ",
                  "price": 22.3,
                  "description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.",
                  "category": "men's clothing",
                  "image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg",
                  "rating": {
                      "rate": 4.1,
                      "count": 259
                  }
              },
              {
                  "id": 3,
                  "title": "Mens Cotton Jacket",
                  "price": 55.99,
                  "description": "great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling, traveling or other outdoors. Good gift choice for you or your family member. A warm hearted love to Father, husband or son in this thanksgiving or Christmas Day.",
                  "category": "men's clothing",
                  "image": "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg",
                  "rating": {
                      "rate": 4.7,
                      "count": 500
                  }
              },
              {
                  "id": 4,
                  "title": "Mens Casual Slim Fit",
                  "price": 15.99,
                  "description": "The color could be slightly different between on the screen and in practice. / Please note that body builds vary by person, therefore, detailed size information should be reviewed below on the product description.",
                  "category": "men's clothing",
                  "image": "https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg",
                  "rating": {
                      "rate": 2.1,
                      "count": 430
                  }
              },
              {
                  "id": 5,
                  "title": "John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet",
                  "price": 695,
                  "description": "From our Legends Collection, the Naga was inspired by the mythical water dragon that protects the ocean's pearl. Wear facing inward to be bestowed with love and abundance, or outward for protection.",
                  "category": "jewelery",
                  "image": "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg",
                  "rating": {
                      "rate": 4.6,
                      "count": 400
                  }
              }
          ],
          "current_page": 1,
          "reach_max": false
    
      }
    

Let's Create Dart Class Model

  • ProductModel: create a new file in models named product_model.dart with the following content.

      import 'package:pagination_in_flutter/models/rating_model.dart';
    
      class ProductModel {
        final int id;
        final String title;
        final String description;
        final String category;
        final double price;
        final String image;
        final RatingModel rating;
        ProductModel({
          required this.id,
          required this.title,
          required this.description,
          required this.category,
          required this.price,
          required this.image,
          required this.rating,
        });
        factory ProductModel.fromJson(Map<String, dynamic> json) {
          return ProductModel(
            id: json['id'],
            title: json['title'],
            description: json['description'],
            category: json['category'],
            price: (json['price'] as num).toDouble(),
            image: json['image'],
            rating: RatingModel.fromJson(json['rating']),
          );
        }
        toJson() {
          return {
            "id": id,
            "title": title,
            "description": description,
            "category": category,
            "price": price,
            "image": image,
            "rating": rating.toJson(),
          };
        }
      }
    
  • RatingModel: same create a new dart file in the same folder with the named rating_model.dart and the content will be followed as.

  •   class RatingModel {
        final double rate;
        final int count;
        RatingModel({required this.rate, required this.count});
        factory RatingModel.fromJson(Map<String, dynamic> json) {
          return RatingModel(
              rate: (json['rate'] as num).toDouble(), count: json['count']);
        }
        toJson() {
          return {"rate": rate, "count": count};
        }
      }
    
  • ProductsListModel: The following model requires for parsing JSON data.

      import 'package:pagination_in_flutter/models/product_model.dart';
    
      class ProductsListModel {
        final List<ProductModel> products;
        final bool reachMax;
        final int currentPage;
        const ProductsListModel(
            {required this.products,
            required this.currentPage,
            required this.reachMax});
        factory ProductsListModel.fromJson(Map<String, dynamic> json) {
          return ProductsListModel(
              products: parseProducts(json['products']),
              currentPage: json['current_page'],
              reachMax: json['reach_max']);
        }
        Map<String, dynamic> toJson() {
          return {
            'products': products.map((e) => e.toJson()),
            'current_page': currentPage,
            'reach_max': reachMax
          };
        }
    
        static List<ProductModel> parseProducts(List<dynamic> p) {
          return List.generate(p.length, (index) => ProductModel.fromJson(p[index]));
        }
      }
    
  • Failure: The failure model will be used to throw errors in Either and following in models/error_model.dart

      class Failure { 
      final String message;
      const Failure({required this.message}); 
      }
    

Let's Call API

  • Create class ProductRepo for calling APIs which has getProducts static function which will return Either Failure (error) or ProductListModel.

  •   import 'dart:convert';
    
      import 'package:dartz/dartz.dart';
      import 'package:http/http.dart' as http;
      import 'package:pagination_in_flutter/models/error_model.dart';
      import 'package:pagination_in_flutter/models/product_list_model.dart';
    
      class ProductRepo {
        static Future<Either<Failure, ProductsListModel>> getProducts(
            {required int page}) async {
          try {
            Map<String, String> query = {"page": page.toString()};
            var uri = Uri.parse(
                    "https://flutter-pagination-api-djsmk123.vercel.app/api/get-products")
                .replace(queryParameters: query);
            final response =
                await http.get(uri, headers: {"Content-Type": "application/json"});
            if (response.statusCode == 200) {
              var json = jsonDecode(response.body);
              ProductsListModel products = ProductsListModel.fromJson(json);
              return Right(products);
            } else {
              return const Left(Failure(message: 'Failed to parse json response'));
            }
          } catch (e) {
            return const Left(Failure(message: 'Something went wrong'));
          }
        }
      }
    

Before creating Blocs states, event and their implementation, Let's talk about Bloc's Basics.

Bloc's:

Flutter Widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.

Not going to cover Cubit in Flutter Bloc

Bloc has three different components state, event and emit.

  • State: represents the current state of the application. a state is an immutable object that represents a snapshot of the application at a specific moment in time. When the State changes and the UI is rebuilt with the new state.

  • event: an Event is a message or signal that triggers a change in the State of the application. Event objects represent user actions, system events, or any other external change that affects the state of the application.

  • emit: emit method is used to notify the Bloc or Cubit that a new State has been produced. The emit method accepts a single argument, which is the new State object.

    BlocBuilder:

    a Flutter widget that requires a bloc and a builder function. BlocBuilder handles building the widget in response to new states. BlocBuilder is very similar to StreamBuilder has a more simple API to reduce the amount of boilerplate code needed. The builder function will potentially be called many times and should be a pure function that returns a widget in response to the state.

For more info

Let's Back to where were we.

Creating Blocs for product

You can create your bloc classes but it is time-consuming so use plugins

Keeping Bloc class as `ProductsBloc`

Events

We have only one event that is ProductLoadEvent so create a class for that

part of 'products_bloc.dart';

@immutable
abstract class ProductsEvent {}

class ProductsLoadEvent extends ProductsEvent {}

States

we could have multiple states like

  • Initial loading: Initially we have to show a loading indicator while the first page is keep fetching from APIs.
  • Initial Error: While fetching items from API, there could be an error.

  • Empty List: it might happen that we got an empty list in response.

  • Product fetch success: When you have successfully fetched data from API and now load more while keeping earlier data so it has conditional value, isLoading or hasError.

      part of 'products_bloc.dart';
    
      @immutable
      abstract class ProductsState {}
    
      class ProductsInitial extends ProductsState {}
    
      //State for initial Loading when current page will be 1
      class ProductsInitialLoading extends ProductsState {
        final String message;
        ProductsInitialLoading({required this.message});
      }
    
      class ProductInitialError extends ProductsState {
        final String message;
        ProductInitialError({required this.message});
      }
    
      class ProductsEmpty extends ProductsState {}
    
      class ProductsLoaded extends ProductsState {
        final ProductsListModel products;
        final LoadingMore? loading;
        final LoadMoreError? error;
        ProductsLoaded({
          required this.products,
          this.loading,
          this.error,
        });
      }
      // LoadingMore Model
      class LoadingMore {
        final String message;
        LoadingMore({required this.message});
      }
      // LoadingMoreError Model
      class LoadMoreError {
        final String message;
        LoadMoreError({required this.message});
      }
    

Let's implement Event and States

Add the following content in products_bloc.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pagination_in_flutter/models/product_list_model.dart';
import 'package:pagination_in_flutter/repo/products_repo.dart';

part 'products_event.dart';
part 'products_state.dart';

class ProductsBloc extends Bloc<ProductsEvent, ProductsState> {
  ProductsListModel products = const ProductsListModel(
    products: [],
    currentPage: 1,
    reachMax: false,
  );
  ProductsBloc() : super(ProductsInitial()) {
    on<ProductsEvent>((event, emit) async {
      if (event is ProductsLoadEvent) {
        bool isInitial = products.currentPage == 1;
        isInitial
            ? emit(ProductsInitialLoading(message: 'Fetching products....'))
            : emit(ProductsLoaded(
                products: products,
                loading: LoadingMore(message: 'Fetching more products...')));
        final response =
            await ProductRepo.getProducts(page: products.currentPage);
        response.fold(
            (l) => isInitial
                ? emit(ProductInitialError(message: 'Failed to load products'))
                : emit(ProductsLoaded(
                    products: products,
                    error: LoadMoreError(
                        message: 'Failed to load more products'))), (r) {
          if (isInitial) {
            products = ProductsListModel(
                products: r.products,
                currentPage: r.currentPage + 1,
                reachMax: r.reachMax);

            if (products.products.isEmpty) {
              emit(ProductsEmpty());
            }
          } else {
            //Adding products to existing list
            products = ProductsListModel(
                products: products.products + r.products,
                currentPage: r.currentPage + 1,
                reachMax: r.reachMax);
          }
          emit(ProductsLoaded(products: products));
        });
      }
    });
  }
}

Done with Flutter Bloc

Let's Implement UI

  • create a new stateful class named ProductPage in /presentation/pages/ with the following content.

      import 'package:flutter/material.dart';
      import 'package:flutter_bloc/flutter_bloc.dart';
      import 'package:pagination_in_flutter/colors.dart';
      import 'package:pagination_in_flutter/models/product_model.dart';
      import 'package:pagination_in_flutter/presentation/blocs/products_bloc.dart';
    
      import '../widgets/widgets.dart';
    
      class ProductsPage extends StatefulWidget {
        const ProductsPage({Key? key}) : super(key: key);
    
        @override
        State<ProductsPage> createState() => _ProductsPageState();
      }
    
      class _ProductsPageState extends State<ProductsPage> {
        @override
        void initState() {
          BlocProvider.of<ProductsBloc>(context).add(ProductsLoadEvent());
          super.initState();
        }
    
        @override
        Widget build(BuildContext context) {
          return Scaffold(
              appBar: AppBar(
                leading: Icon(
                  Icons.menu,
                  color: primaryColor,
                  size: 30,
                ),
                title: Text(
                  "Flutter Pagination",
                  style: TextStyle(
                    color: primaryColor,
                    fontSize: 20,
                  ),
                ),
                centerTitle: true,
              ),
              body: PaginationWidget<ProductModel>(
                loadMore: () {
                  BlocProvider.of<ProductsBloc>(context).add(ProductsLoadEvent());
                },
                initialEmpty: const EmptyWidget(),
                initialLoading: const LoadingWidget(),
                initialError: const CustomErrorWidget(),
                child: (ProductModel productModel) {
                  return ProductCard(product: productModel);
                },
              ));
        }
      }
    
    • Create PaginationWidget stateless class in widgets directory.

        class PaginationWidget<t> extends StatelessWidget {
          final Function() loadMore;
          final Widget initialError;
          final Widget initialLoading;
          final Widget initialEmpty;
          final Widget Function(t p) child;
          final Widget? onLoadMoreError;
          final Widget? onLoadMoreLoading;
          const PaginationWidget(
              {Key? key,
              required this.loadMore,
              required this.initialError,
              required this.initialLoading,
              required this.initialEmpty,
              this.onLoadMoreError,
              this.onLoadMoreLoading,
              required this.child})
              : super(key: key);
      
          @override
          Widget build(BuildContext context) {
            return BlocBuilder<ProductsBloc, ProductsState>(
              builder: (context, state) {
                if (state is ProductsLoaded) {
                  List<ProductModel> products = state.products.products;
                  return NotificationListener<ScrollEndNotification>(
                      onNotification: (scrollInfo) {
                        scrollInfo.metrics.pixels ==
                                    scrollInfo.metrics.maxScrollExtent &&
                                !state.products.reachMax
                            ? loadMore()
                            : null;
                        return true;
                      },
                      child: Column(
                        children: [
                          Expanded(
                              child: ListView.builder(
                                  itemCount: products.length,
                                  itemBuilder: (context, index) =>
                                      child(products[index] as t))),
                          const SizedBox(
                            height: 20,
                          ),
                          //if error occured while loading more
                          if (state.error != null)
                            Expanded(child: onLoadMoreError ?? initialError),
                          if (state.loading != null)
                            Expanded(child: onLoadMoreLoading ?? initialLoading),
                        ],
                      ));
                }
                if (state is ProductsInitialLoading) {
                  return initialLoading;
                }
                if (state is ProductsEmpty) {
                  return initialEmpty;
                }
                if (state is ProductInitialError) {
                  return initialError;
                }
                return const SizedBox.shrink();
              },
            );
          }
        }
      
      • The PaginationWidget class is a Flutter stateless widget that renders a list of items with pagination support. It takes in several parameters through its constructor to configure its behavior and appearance.

      • The constructor for the PaginationWidget class has the following parameters:

        • loadMore: a function that is called when the user reaches the end of the list and needs to load more items.

        • initialError: a widget to display when an error occurs while loading the initial list of items.

        • initialLoading: a widget to display while the initial list of items is being loaded.

        • initialEmpty: a widget to display when the initial list of items is empty.

        • child: a function that takes an item of type t and returns a widget that represents that item in the list.

        • onLoadMoreError: a widget to display when an error occurs while loading more items.

        • onLoadMoreLoading: a widget to display while more items are being loaded.

  • Empty List item Widget: create a widget in widgets/empty_widget

      import 'package:cached_network_image/cached_network_image.dart';
      import 'package:flutter/material.dart';
    
      class EmptyWidget extends StatelessWidget {
        const EmptyWidget({Key? key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CachedNetworkImage(
                  imageUrl:
                      "https://static.vecteezy.com/system/resources/previews/005/073/073/original/no-item-in-the-shopping-cart-add-product-click-to-shop-now-concept-illustration-flat-design-eps10-modern-graphic-element-for-landing-page-empty-state-ui-infographic-icon-vector.jpg",
                  height: 200,
                ),
                const SizedBox(
                  height: 30,
                ),
                Text("No products available",
                    style: TextStyle(color: Colors.grey.shade500, fontSize: 24)),
              ],
            ),
          );
        }
      }
    
  • CustomErrorWidget: do the same with this, name error_widget.dart.

      import 'package:cached_network_image/cached_network_image.dart';
      import 'package:flutter/material.dart';
    
      class CustomErrorWidget extends StatelessWidget {
        const CustomErrorWidget({Key? key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CachedNetworkImage(
                  imageUrl: "https://gcdnb.pbrd.co/images/5Rz9dEYkdYdm.png?o=1",
                  height: 200,
                ),
                const SizedBox(
                  height: 30,
                ),
                Text("Error Occured,try again",
                    style: TextStyle(color: Colors.grey.shade500, fontSize: 24)),
              ],
            ),
          );
        }
      }
    
  • LoadingWidget: For showing Loading, We are using shimmer widget.

import 'package:flutter/material.dart';
import 'package:pagination_in_flutter/colors.dart';
import 'package:shimmer/shimmer.dart';

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

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (c, i) => Padding(
        padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
        child: Card(
          elevation: 5,
          surfaceTintColor: Colors.grey,
          color: Colors.grey.shade300,
          shape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Stack(
              children: [
                Column(
                  children: [
                    Row(
                      children: [
                        Flexible(
                            child: shimmerBuilder(
                          const CircleAvatar(
                            radius: 40,
                            backgroundColor: Colors.grey,
                          ),
                        )),
                        const SizedBox(
                          width: 20,
                        ),
                        Flexible(
                          child: shimmerBuilder(
                            Container(
                              height: 20,
                              width: 150,
                              color: Colors.black,
                            ),
                          ),
                        )
                      ],
                    ),
                    const SizedBox(
                      height: 20,
                    ),
                    Row(
                      children: [
                        shimmerBuilder(
                          const Icon(
                            Icons.favorite_border,
                            color: Colors.grey,
                          ),
                        ),
                        const SizedBox(
                          width: 10,
                        ),
                        shimmerBuilder(
                          Container(
                            height: 10,
                            width: 100,
                            color: Colors.black,
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
                Positioned(
                    right: 0,
                    bottom: 0,
                    child: shimmerBuilder(
                      Container(
                        width: 125,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 20, vertical: 5),
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(8),
                          border: Border.all(color: primaryColor),
                        ),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: const [
                            Icon(
                              Icons.shopping_cart_outlined,
                              color: Colors.grey,
                            ),
                          ],
                        ),
                      ),
                    ))
              ],
            ),
          ),
        ),
      ),
      itemCount: 5,
    );
  }

  Shimmer shimmerBuilder(child) {
    return Shimmer(
      gradient: LinearGradient(
        colors: [
          Colors.grey.shade300,
          Colors.grey.shade100,
          Colors.grey.shade50,
        ],
      ),
      child: child,
    );
  }
}

Card widget:

create a dart file named product_card.dart with the following content:

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:pagination_in_flutter/colors.dart';
import 'package:pagination_in_flutter/models/product_model.dart';

class ProductCard extends StatelessWidget {
  final ProductModel product;
  const ProductCard({Key? key, required this.product}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
      child: Card(
        elevation: 5,
        surfaceTintColor: primaryColor,
        color: Colors.blueAccent,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Stack(
            //crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Column(
                children: [
                  Row(
                    children: [
                      Flexible(
                        child: CircleAvatar(
                          radius: 40,
                          backgroundColor: Colors.white,
                          child: ClipOval(
                            child: CachedNetworkImage(
                              width: double.maxFinite,
                              height: 130.0,
                              fit: BoxFit.scaleDown,
                              imageUrl: product.image,
                              placeholder: (context, url) => const SizedBox(
                                width: 10,
                                height: 10,
                              ),
                              errorWidget: (context, url, error) => Image.asset(
                                'assets/images/sinimagen.png',
                                height: 30,
                                fit: BoxFit.cover,
                              ),
                            ),
                          ),
                        ),
                      ),
                      const SizedBox(
                        width: 20,
                      ),
                      Flexible(
                          child: Text(
                        product.title,
                        maxLines: 2,
                        style:
                            const TextStyle(color: Colors.white, fontSize: 14),
                      ))
                    ],
                  ),
                  const SizedBox(
                    height: 20,
                  ),
                  Row(
                    children: [
                      const Icon(
                        Icons.favorite_border,
                        color: Colors.white,
                      ),
                      const SizedBox(
                        width: 10,
                      ),
                      Text(
                        product.rating.count.toString(),
                        maxLines: 2,
                        style:
                            const TextStyle(color: Colors.white, fontSize: 14),
                      ),
                    ],
                  ),
                ],
              ),
              Positioned(
                right: 0,
                bottom: 0,
                child: Container(
                  width: 125,
                  padding:
                      const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: primaryColor),
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(product.price.toString()),
                      const SizedBox(
                        width: 10,
                      ),
                      const Icon(Icons.shopping_cart_outlined),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
  • Add the following content in main.dart
  •   import 'package:flutter/material.dart';
      import 'package:flutter_bloc/flutter_bloc.dart';
      import 'package:pagination_in_flutter/presentation/blocs/products_bloc.dart';
      import 'package:pagination_in_flutter/presentation/pages/products_page.dart';
    
      void main() {
        runApp(const MyApp());
      }
    
      class MyApp extends StatelessWidget {
        const MyApp({Key? key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return MultiBlocProvider(
              providers: [BlocProvider(create: (context) => ProductsBloc())],
              child: MaterialApp(
                title: 'Pagination Example with Flutter',
                debugShowCheckedModeBanner: false,
                theme: ThemeData(useMaterial3: true),
                home: const ProductsPage(),
              ));
        }
      }
    
  • Create colors.dart

      import 'package:flutter/material.dart';
    
      Color primaryColor = Colors.blueAccent;
    

OUTPUT

Fetching Items when No Error Occurred

Fetching items when the error occurred while loading more:

Hurry we successfully implemented Pagination in Flutter using Blocs.

Source code:

Follow me:

Top comments (2)

Collapse
 
cometmotion profile image
Элмар Орозбаев

Hello. Maybe after emit(ProductsEmpty()); will you set return, if you don't set return after emit ProductsEmpty, it will be never state. It will return always emit(ProductsLoaded(products: products));

Collapse
 
djsmk123 profile image
Md. Mobin

Good Catch!!, My Bad will update this.