Flutter pagination ft. Bloc
Tags: flutter, dart, pagination, tutorial
Published: 2023-04-22 14:52:16

<p>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 <a target="_blank" href="https://fakestoreapi.com/"><mark>fake-store APIs</mark></a> that includes pagination.</p>
<p>Let's begin with the basics before diving into Flutter-specific details.</p>
<p><img src="https://i.imgflip.com/7j39wp.jpg" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Do not worry we are going to talk about bloc's basic</p>
</blockquote>
<h2 id="heading-why-pagination">Why Pagination?</h2>
<p>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.</p>
<p><img src="https://i.imgflip.com/7j3ad1.jpg" alt class="image--center mx-auto" /></p>
<h2 id="heading-setup-project">Setup Project</h2>
<ul>
<li><p>Add the following dependency to your Flutter project(<code>pubspec.yaml</code>)</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">flutter_bloc:</span> <span class="hljs-string">^8.1.2</span>
    <span class="hljs-attr">http:</span> <span class="hljs-string">^0.13.5</span>
    <span class="hljs-attr">dartz:</span> <span class="hljs-string">^0.10.1</span>
    <span class="hljs-attr">shimmer:</span> <span class="hljs-string">^2.0.0</span>
    <span class="hljs-attr">cached_network_image:</span> <span class="hljs-string">^3.2.3</span>
</code></pre>
</li>
<li><p><code>darts</code>: 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.</p>
</li>
</ul>
<ul>
<li><p><code>shimmer</code>: Shimmer library is being used for showing loading indicator link on LinkedIn, Twitter, Facebook etc.</p>
</li>
<li><p><code>cached_network_image</code>: Flutter library, as its name suggests, is used for caching network images.</p>
</li>
<li><p><code>http</code>: Flutter library for calling <code>HTTP</code> requests.</p>
</li>
</ul>
<h2 id="heading-project-file-structure">Project file Structure:</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682166877182/d01a0be6-ef11-4a7c-a75c-d6614652cdc5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>models</code>: following folders will contain models that will be used in the application.</p>
</li>
<li><p><code>presentation</code>: The following layer has been divided further into <code>blocs</code>,<code>pages</code>, and <code>widgets</code>.</p>
<ul>
<li><p><code>widgets</code>: 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.</p>
</li>
<li><p><code>blocs</code>: 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.</p>
</li>
<li><p><code>pages</code>: Pages are the top-level UI elements in a Flutter application. Each page typically represents a separate screen or view.</p>
</li>
</ul>
</li>
<li><p><code>repo</code>: Stands for the repository which has logic for calling APIs it passes data to models.</p>
</li>
</ul>
<h2 id="heading-parsing-apis-data-to-dart-model-classes">Parsing APIs Data to Dart Model-Classes</h2>
<p><a target="_blank" href="https://codewithandrea.com/articles/parse-json-dart/">Don't know how to parse JSON data to Dart class?</a></p>
<ul>
<li>We are going to use a modified version of FakeStore APIs.</li>
</ul>
<pre><code class="lang-dart">Base URL=<span class="hljs-string">"https://flutter-pagination-api-djsmk123.vercel.app/api</span>
</code></pre>
<ul>
<li><p>Endpoint <code>/get-products?page=1</code> by default <code>page=1</code>. How the response will look like?</p>
<pre><code class="lang-json">  {

      <span class="hljs-attr">"products"</span>: [
          {
              <span class="hljs-attr">"id"</span>: <span class="hljs-number">1</span>,
              <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops"</span>,
              <span class="hljs-attr">"price"</span>: <span class="hljs-number">109.95</span>,
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday"</span>,
              <span class="hljs-attr">"category"</span>: <span class="hljs-string">"men's clothing"</span>,
              <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"</span>,
              <span class="hljs-attr">"rating"</span>: {
                  <span class="hljs-attr">"rate"</span>: <span class="hljs-number">3.9</span>,
                  <span class="hljs-attr">"count"</span>: <span class="hljs-number">120</span>
              }
          },
          {
              <span class="hljs-attr">"id"</span>: <span class="hljs-number">2</span>,
              <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Mens Casual Premium Slim Fit T-Shirts "</span>,
              <span class="hljs-attr">"price"</span>: <span class="hljs-number">22.3</span>,
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight &amp; 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."</span>,
              <span class="hljs-attr">"category"</span>: <span class="hljs-string">"men's clothing"</span>,
              <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg"</span>,
              <span class="hljs-attr">"rating"</span>: {
                  <span class="hljs-attr">"rate"</span>: <span class="hljs-number">4.1</span>,
                  <span class="hljs-attr">"count"</span>: <span class="hljs-number">259</span>
              }
          },
          {
              <span class="hljs-attr">"id"</span>: <span class="hljs-number">3</span>,
              <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Mens Cotton Jacket"</span>,
              <span class="hljs-attr">"price"</span>: <span class="hljs-number">55.99</span>,
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"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."</span>,
              <span class="hljs-attr">"category"</span>: <span class="hljs-string">"men's clothing"</span>,
              <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg"</span>,
              <span class="hljs-attr">"rating"</span>: {
                  <span class="hljs-attr">"rate"</span>: <span class="hljs-number">4.7</span>,
                  <span class="hljs-attr">"count"</span>: <span class="hljs-number">500</span>
              }
          },
          {
              <span class="hljs-attr">"id"</span>: <span class="hljs-number">4</span>,
              <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Mens Casual Slim Fit"</span>,
              <span class="hljs-attr">"price"</span>: <span class="hljs-number">15.99</span>,
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"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."</span>,
              <span class="hljs-attr">"category"</span>: <span class="hljs-string">"men's clothing"</span>,
              <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg"</span>,
              <span class="hljs-attr">"rating"</span>: {
                  <span class="hljs-attr">"rate"</span>: <span class="hljs-number">2.1</span>,
                  <span class="hljs-attr">"count"</span>: <span class="hljs-number">430</span>
              }
          },
          {
              <span class="hljs-attr">"id"</span>: <span class="hljs-number">5</span>,
              <span class="hljs-attr">"title"</span>: <span class="hljs-string">"John Hardy Women's Legends Naga Gold &amp; Silver Dragon Station Chain Bracelet"</span>,
              <span class="hljs-attr">"price"</span>: <span class="hljs-number">695</span>,
              <span class="hljs-attr">"description"</span>: <span class="hljs-string">"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."</span>,
              <span class="hljs-attr">"category"</span>: <span class="hljs-string">"jewelery"</span>,
              <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg"</span>,
              <span class="hljs-attr">"rating"</span>: {
                  <span class="hljs-attr">"rate"</span>: <span class="hljs-number">4.6</span>,
                  <span class="hljs-attr">"count"</span>: <span class="hljs-number">400</span>
              }
          }
      ],
      <span class="hljs-attr">"current_page"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"reach_max"</span>: <span class="hljs-literal">false</span>

  }
</code></pre>
</li>
</ul>
<h2 id="heading-lets-create-dart-class-model">Let's Create Dart Class Model</h2>
<ul>
<li><p><code>ProductModel</code>: create a new file in models named <code>product_model.dart</code> with the following content.</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/rating_model.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductModel</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> id;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> title;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> description;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> category;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> price;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> image;
    <span class="hljs-keyword">final</span> RatingModel rating;
    ProductModel({
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.id,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.title,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.description,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.category,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.price,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.image,
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.rating,
    });
    <span class="hljs-keyword">factory</span> ProductModel.fromJson(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) {
      <span class="hljs-keyword">return</span> ProductModel(
        id: json[<span class="hljs-string">'id'</span>],
        title: json[<span class="hljs-string">'title'</span>],
        description: json[<span class="hljs-string">'description'</span>],
        category: json[<span class="hljs-string">'category'</span>],
        price: (json[<span class="hljs-string">'price'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">num</span>).toDouble(),
        image: json[<span class="hljs-string">'image'</span>],
        rating: RatingModel.fromJson(json[<span class="hljs-string">'rating'</span>]),
      );
    }
    toJson() {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-string">"id"</span>: id,
        <span class="hljs-string">"title"</span>: title,
        <span class="hljs-string">"description"</span>: description,
        <span class="hljs-string">"category"</span>: category,
        <span class="hljs-string">"price"</span>: price,
        <span class="hljs-string">"image"</span>: image,
        <span class="hljs-string">"rating"</span>: rating.toJson(),
      };
    }
  }
</code></pre>
</li>
<li><p><code>RatingModel</code>: same create a new dart file in the same folder with the named <code>rating_model.dart</code> and the content will be followed as.</p>
</li>
<li><pre><code class="lang-dart">  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RatingModel</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> rate;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> count;
    RatingModel({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.rate, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.count});
    <span class="hljs-keyword">factory</span> RatingModel.fromJson(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) {
      <span class="hljs-keyword">return</span> RatingModel(
          rate: (json[<span class="hljs-string">'rate'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">num</span>).toDouble(), count: json[<span class="hljs-string">'count'</span>]);
    }
    toJson() {
      <span class="hljs-keyword">return</span> {<span class="hljs-string">"rate"</span>: rate, <span class="hljs-string">"count"</span>: count};
    }
  }
</code></pre>
</li>
<li><p><code>ProductsListModel</code>: The following model requires for parsing JSON data.</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/product_model.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsListModel</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;ProductModel&gt; products;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> reachMax;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> currentPage;
    <span class="hljs-keyword">const</span> ProductsListModel(
        {<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.products,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.currentPage,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.reachMax});
    <span class="hljs-keyword">factory</span> ProductsListModel.fromJson(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) {
      <span class="hljs-keyword">return</span> ProductsListModel(
          products: parseProducts(json[<span class="hljs-string">'products'</span>]),
          currentPage: json[<span class="hljs-string">'current_page'</span>],
          reachMax: json[<span class="hljs-string">'reach_max'</span>]);
    }
    <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; toJson() {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'products'</span>: products.map((e) =&gt; e.toJson()),
        <span class="hljs-string">'current_page'</span>: currentPage,
        <span class="hljs-string">'reach_max'</span>: reachMax
      };
    }

    <span class="hljs-keyword">static</span> <span class="hljs-built_in">List</span>&lt;ProductModel&gt; parseProducts(<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">dynamic</span>&gt; p) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">List</span>.generate(p.length, (index) =&gt; ProductModel.fromJson(p[index]));
    }
  }
</code></pre>
</li>
<li><p><code>Failure</code>: The failure model will be used to throw errors in <code>Either</code> and following in <code>models/error_model.dart</code></p>
<pre><code class="lang-dart">  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Failure</span> </span>{ 
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> message;
  <span class="hljs-keyword">const</span> Failure({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message}); 
  }
</code></pre>
</li>
</ul>
<h2 id="heading-lets-call-api">Let's Call API</h2>
<ul>
<li><p>Create class <code>ProductRepo</code> for calling APIs which has <code>getProducts</code> static function which will return Either <code>Failure</code> (error) or <code>ProductListModel</code>.</p>
</li>
<li><pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'dart:convert'</span>;

  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:dartz/dartz.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:http/http.dart'</span> <span class="hljs-keyword">as</span> http;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/error_model.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/product_list_model.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductRepo</span> </span>{
    <span class="hljs-keyword">static</span> Future&lt;Either&lt;Failure, ProductsListModel&gt;&gt; getProducts(
        {<span class="hljs-keyword">required</span> <span class="hljs-built_in">int</span> page}) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt; query = {<span class="hljs-string">"page"</span>: page.toString()};
        <span class="hljs-keyword">var</span> uri = <span class="hljs-built_in">Uri</span>.parse(
                <span class="hljs-string">"https://flutter-pagination-api-djsmk123.vercel.app/api/get-products"</span>)
            .replace(queryParameters: query);
        <span class="hljs-keyword">final</span> response =
            <span class="hljs-keyword">await</span> http.<span class="hljs-keyword">get</span>(uri, headers: {<span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>});
        <span class="hljs-keyword">if</span> (response.statusCode == <span class="hljs-number">200</span>) {
          <span class="hljs-keyword">var</span> json = jsonDecode(response.body);
          ProductsListModel products = ProductsListModel.fromJson(json);
          <span class="hljs-keyword">return</span> Right(products);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Left(Failure(message: <span class="hljs-string">'Failed to parse json response'</span>));
        }
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Left(Failure(message: <span class="hljs-string">'Something went wrong'</span>));
      }
    }
  }
</code></pre>
</li>
</ul>
<p><img src="https://couchto5krunandblog.files.wordpress.com/2015/05/finally-half-way.jpg" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Before creating Blocs states, event and their implementation, Let's talk about Bloc's Basics.</p>
</blockquote>
<h2 id="heading-blocs">Bloc's:</h2>
<p>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.</p>
<blockquote>
<p>Not going to cover Cubit in Flutter Bloc</p>
</blockquote>
<p>Bloc has three different components <code>state</code>, <code>event</code> and <code>emit</code>.</p>
<ul>
<li><p><code>State</code>: represents the current state of the application. a <code>state</code> is an immutable object that represents a snapshot of the application at a specific moment in time. When the <code>State</code> changes and the UI is rebuilt with the new state.</p>
</li>
<li><p><code>event</code>: 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.</p>
</li>
<li><p><code>emit</code>: 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.</p>
<h2 id="heading-blocbuilder"><strong>BlocBuilder:</strong></h2>
<p>  a Flutter widget that requires a <code>bloc</code> and a <code>builder</code> function. <code>BlocBuilder</code> handles building the widget in response to new states. <code>BlocBuilder</code> is very similar to <code>StreamBuilder</code> has a more simple API to reduce the amount of boilerplate code needed. The <code>builder</code> function will potentially be called many times and should be a <a target="_blank" href="https://en.wikipedia.org/wiki/Pure_function">pure function</a> that returns a widget in response to the state.</p>
</li>
</ul>
<p><a target="_blank" href="https://pub.dev/packages/flutter_bloc">For more info</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682170093654/227cf309-166d-4ce3-a0b6-0bd184cc2de8.png" alt class="image--center mx-auto" /></p>
<blockquote>
<h2 id="heading-lets-back-to-where-were-we">Let's Back to where were we.</h2>
</blockquote>
<h2 id="heading-creating-blocs-for-product">Creating Blocs for product</h2>
<p>You can create your bloc classes but it is time-consuming so use plugins</p>
<ul>
<li><p>Android Studio: <a target="_blank" href="https://plugins.jetbrains.com/plugin/12129-bloc">https://plugins.jetbrains.com/plugin/12129-bloc</a></p>
</li>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc"><strong>Vs code</strong></a></p>
</li>
</ul>
<blockquote>
<p>Keeping Bloc class as `<code>ProductsBloc` </code></p>
</blockquote>
<h2 id="heading-events">Events</h2>
<p>We have only one event that is <code>ProductLoadEvent</code> so create a class for that</p>
<pre><code class="lang-dart"><span class="hljs-keyword">part</span> of <span class="hljs-string">'products_bloc.dart'</span>;

<span class="hljs-meta">@immutable</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsEvent</span> </span>{}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsLoadEvent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsEvent</span> </span>{}
</code></pre>
<h2 id="heading-states">States</h2>
<p>we could have multiple states like</p>
<ul>
<li><strong>Initial loading:</strong> Initially we have to show a loading indicator while the first page is keep fetching from APIs.</li>
</ul>
<ul>
<li><p><strong>Initial Error:</strong> While fetching items from API, there could be <strong>an</strong> error.</p>
</li>
<li><p><strong>Empty List:</strong> it might happen that we got an empty list in response.</p>
</li>
<li><p><strong>Product fetch success:</strong> When you have successfully fetched data from API and now load more while keeping earlier data so it has conditional value, isLoading or hasError.</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">part</span> of <span class="hljs-string">'products_bloc.dart'</span>;

  <span class="hljs-meta">@immutable</span>
  <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsState</span> </span>{}

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsInitial</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsState</span> </span>{}

  <span class="hljs-comment">//State for initial Loading when current page will be 1</span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsInitialLoading</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsState</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> message;
    ProductsInitialLoading({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message});
  }

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductInitialError</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsState</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> message;
    ProductInitialError({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message});
  }

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsEmpty</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsState</span> </span>{}

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsLoaded</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ProductsState</span> </span>{
    <span class="hljs-keyword">final</span> ProductsListModel products;
    <span class="hljs-keyword">final</span> LoadingMore? loading;
    <span class="hljs-keyword">final</span> LoadMoreError? error;
    ProductsLoaded({
      <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.products,
      <span class="hljs-keyword">this</span>.loading,
      <span class="hljs-keyword">this</span>.error,
    });
  }
  <span class="hljs-comment">// LoadingMore Model</span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoadingMore</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> message;
    LoadingMore({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message});
  }
  <span class="hljs-comment">// LoadingMoreError Model</span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoadMoreError</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> message;
    LoadMoreError({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message});
  }
</code></pre>
</li>
</ul>
<h2 id="heading-lets-implement-event-and-states">Let's implement Event and States</h2>
<p>Add the following content in <code>products_bloc.dart</code></p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/product_list_model.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/repo/products_repo.dart'</span>;

<span class="hljs-keyword">part</span> <span class="hljs-string">'products_event.dart'</span>;
<span class="hljs-keyword">part</span> <span class="hljs-string">'products_state.dart'</span>;

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

            <span class="hljs-keyword">if</span> (products.products.isEmpty) {
              emit(ProductsEmpty());
            }
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">//Adding products to existing list</span>
            products = ProductsListModel(
                products: products.products + r.products,
                currentPage: r.currentPage + <span class="hljs-number">1</span>,
                reachMax: r.reachMax);
          }
          emit(ProductsLoaded(products: products));
        });
      }
    });
  }
}
</code></pre>
<blockquote>
<p>Done with Flutter Bloc</p>
</blockquote>
<h2 id="heading-lets-implement-ui">Let's Implement UI</h2>
<ul>
<li><p>create a new <code>stateful</code> class named <code>ProductPage</code> in <code>/presentation/pages/</code> with the following content.</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/colors.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/product_model.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/presentation/blocs/products_bloc.dart'</span>;

  <span class="hljs-keyword">import</span> <span class="hljs-string">'../widgets/widgets.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
    <span class="hljs-keyword">const</span> ProductsPage({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

    <span class="hljs-meta">@override</span>
    State&lt;ProductsPage&gt; createState() =&gt; _ProductsPageState();
  }

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ProductsPageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ProductsPage</span>&gt; </span>{
    <span class="hljs-meta">@override</span>
    <span class="hljs-keyword">void</span> initState() {
      BlocProvider.of&lt;ProductsBloc&gt;(context).add(ProductsLoadEvent());
      <span class="hljs-keyword">super</span>.initState();
    }

    <span class="hljs-meta">@override</span>
    Widget build(BuildContext context) {
      <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(
            leading: Icon(
              Icons.menu,
              color: primaryColor,
              size: <span class="hljs-number">30</span>,
            ),
            title: Text(
              <span class="hljs-string">"Flutter Pagination"</span>,
              style: TextStyle(
                color: primaryColor,
                fontSize: <span class="hljs-number">20</span>,
              ),
            ),
            centerTitle: <span class="hljs-keyword">true</span>,
          ),
          body: PaginationWidget&lt;ProductModel&gt;(
            loadMore: () {
              BlocProvider.of&lt;ProductsBloc&gt;(context).add(ProductsLoadEvent());
            },
            initialEmpty: <span class="hljs-keyword">const</span> EmptyWidget(),
            initialLoading: <span class="hljs-keyword">const</span> LoadingWidget(),
            initialError: <span class="hljs-keyword">const</span> CustomErrorWidget(),
            child: (ProductModel productModel) {
              <span class="hljs-keyword">return</span> ProductCard(product: productModel);
            },
          ));
    }
  }
</code></pre>
<ul>
<li><p>Create <code>PaginationWidget</code> stateless class in <code>widgets</code> directory.</p>
<pre><code class="lang-dart">  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaginationWidget</span>&lt;<span class="hljs-title">t</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">Function</span>() loadMore;
    <span class="hljs-keyword">final</span> Widget initialError;
    <span class="hljs-keyword">final</span> Widget initialLoading;
    <span class="hljs-keyword">final</span> Widget initialEmpty;
    <span class="hljs-keyword">final</span> Widget <span class="hljs-built_in">Function</span>(t p) child;
    <span class="hljs-keyword">final</span> Widget? onLoadMoreError;
    <span class="hljs-keyword">final</span> Widget? onLoadMoreLoading;
    <span class="hljs-keyword">const</span> PaginationWidget(
        {Key? key,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.loadMore,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.initialError,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.initialLoading,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.initialEmpty,
        <span class="hljs-keyword">this</span>.onLoadMoreError,
        <span class="hljs-keyword">this</span>.onLoadMoreLoading,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.child})
        : <span class="hljs-keyword">super</span>(key: key);

    <span class="hljs-meta">@override</span>
    Widget build(BuildContext context) {
      <span class="hljs-keyword">return</span> BlocBuilder&lt;ProductsBloc, ProductsState&gt;(
        builder: (context, state) {
          <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> ProductsLoaded) {
            <span class="hljs-built_in">List</span>&lt;ProductModel&gt; products = state.products.products;
            <span class="hljs-keyword">return</span> NotificationListener&lt;ScrollEndNotification&gt;(
                onNotification: (scrollInfo) {
                  scrollInfo.metrics.pixels ==
                              scrollInfo.metrics.maxScrollExtent &amp;&amp;
                          !state.products.reachMax
                      ? loadMore()
                      : <span class="hljs-keyword">null</span>;
                  <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
                },
                child: Column(
                  children: [
                    Expanded(
                        child: ListView.builder(
                            itemCount: products.length,
                            itemBuilder: (context, index) =&gt;
                                child(products[index] <span class="hljs-keyword">as</span> t))),
                    <span class="hljs-keyword">const</span> SizedBox(
                      height: <span class="hljs-number">20</span>,
                    ),
                    <span class="hljs-comment">//if error occured while loading more</span>
                    <span class="hljs-keyword">if</span> (state.error != <span class="hljs-keyword">null</span>)
                      Expanded(child: onLoadMoreError ?? initialError),
                    <span class="hljs-keyword">if</span> (state.loading != <span class="hljs-keyword">null</span>)
                      Expanded(child: onLoadMoreLoading ?? initialLoading),
                  ],
                ));
          }
          <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> ProductsInitialLoading) {
            <span class="hljs-keyword">return</span> initialLoading;
          }
          <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> ProductsEmpty) {
            <span class="hljs-keyword">return</span> initialEmpty;
          }
          <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> ProductInitialError) {
            <span class="hljs-keyword">return</span> initialError;
          }
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> SizedBox.shrink();
        },
      );
    }
  }
</code></pre>
<ul>
<li><p>The <strong>PaginationWidget</strong> 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.</p>
</li>
<li><p>The constructor for the <strong>PaginationWidget</strong> class has the following parameters:</p>
<ul>
<li><p><strong>loadMore</strong>: a function that is called when the user reaches the end of the list and needs to load more items.</p>
</li>
<li><p><strong>initialError</strong>: a widget to display when an error occurs while loading the initial list of items.</p>
</li>
<li><p><strong>initialLoading</strong>: a widget to display while the initial list of items is being loaded.</p>
</li>
<li><p><strong>initialEmpty</strong>: a widget to display when the initial list of items is empty.</p>
</li>
<li><p><strong>child</strong>: a function that takes an item of type t and returns a widget that represents that item in the list.</p>
</li>
<li><p><strong>onLoadMoreError</strong>: a widget to display when an error occurs while loading more items.</p>
</li>
<li><p><strong>onLoadMoreLoading</strong>: a widget to display while more items are being loaded.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Empty List item Widget:</strong> create a widget in <code>widgets/empty_widget</code></p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:cached_network_image/cached_network_image.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EmptyWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
    <span class="hljs-keyword">const</span> EmptyWidget({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

    <span class="hljs-meta">@override</span>
    Widget build(BuildContext context) {
      <span class="hljs-keyword">return</span> Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CachedNetworkImage(
              imageUrl:
                  <span class="hljs-string">"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"</span>,
              height: <span class="hljs-number">200</span>,
            ),
            <span class="hljs-keyword">const</span> SizedBox(
              height: <span class="hljs-number">30</span>,
            ),
            Text(<span class="hljs-string">"No products available"</span>,
                style: TextStyle(color: Colors.grey.shade500, fontSize: <span class="hljs-number">24</span>)),
          ],
        ),
      );
    }
  }
</code></pre>
</li>
<li><p><code>CustomErrorWidget</code>: do the same with this, name <code>error_widget.dart.</code></p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:cached_network_image/cached_network_image.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomErrorWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
    <span class="hljs-keyword">const</span> CustomErrorWidget({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

    <span class="hljs-meta">@override</span>
    Widget build(BuildContext context) {
      <span class="hljs-keyword">return</span> Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CachedNetworkImage(
              imageUrl: <span class="hljs-string">"https://gcdnb.pbrd.co/images/5Rz9dEYkdYdm.png?o=1"</span>,
              height: <span class="hljs-number">200</span>,
            ),
            <span class="hljs-keyword">const</span> SizedBox(
              height: <span class="hljs-number">30</span>,
            ),
            Text(<span class="hljs-string">"Error Occured,try again"</span>,
                style: TextStyle(color: Colors.grey.shade500, fontSize: <span class="hljs-number">24</span>)),
          ],
        ),
      );
    }
  }
</code></pre>
</li>
<li><p><code>LoadingWidget</code>: For showing Loading, We are using <code>shimmer</code> widget.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/colors.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shimmer/shimmer.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoadingWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> LoadingWidget({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

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

  Shimmer shimmerBuilder(child) {
    <span class="hljs-keyword">return</span> Shimmer(
      gradient: LinearGradient(
        colors: [
          Colors.grey.shade300,
          Colors.grey.shade100,
          Colors.grey.shade50,
        ],
      ),
      child: child,
    );
  }
}
</code></pre>
<h2 id="heading-card-widget">Card widget:</h2>
<p>create a dart file named <code>product_card.dart</code> with the following content:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cached_network_image/cached_network_image.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/colors.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/models/product_model.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductCard</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> ProductModel product;
  <span class="hljs-keyword">const</span> ProductCard({Key? key, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.product}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Padding(
      padding: <span class="hljs-keyword">const</span> EdgeInsets.symmetric(horizontal: <span class="hljs-number">10</span>, vertical: <span class="hljs-number">5</span>),
      child: Card(
        elevation: <span class="hljs-number">5</span>,
        surfaceTintColor: primaryColor,
        color: Colors.blueAccent,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(<span class="hljs-number">16</span>)),
        child: Padding(
          padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">20</span>),
          child: Stack(
            <span class="hljs-comment">//crossAxisAlignment: CrossAxisAlignment.start,</span>
            children: [
              Column(
                children: [
                  Row(
                    children: [
                      Flexible(
                        child: CircleAvatar(
                          radius: <span class="hljs-number">40</span>,
                          backgroundColor: Colors.white,
                          child: ClipOval(
                            child: CachedNetworkImage(
                              width: <span class="hljs-built_in">double</span>.maxFinite,
                              height: <span class="hljs-number">130.0</span>,
                              fit: BoxFit.scaleDown,
                              imageUrl: product.image,
                              placeholder: (context, url) =&gt; <span class="hljs-keyword">const</span> SizedBox(
                                width: <span class="hljs-number">10</span>,
                                height: <span class="hljs-number">10</span>,
                              ),
                              errorWidget: (context, url, error) =&gt; Image.asset(
                                <span class="hljs-string">'assets/images/sinimagen.png'</span>,
                                height: <span class="hljs-number">30</span>,
                                fit: BoxFit.cover,
                              ),
                            ),
                          ),
                        ),
                      ),
                      <span class="hljs-keyword">const</span> SizedBox(
                        width: <span class="hljs-number">20</span>,
                      ),
                      Flexible(
                          child: Text(
                        product.title,
                        maxLines: <span class="hljs-number">2</span>,
                        style:
                            <span class="hljs-keyword">const</span> TextStyle(color: Colors.white, fontSize: <span class="hljs-number">14</span>),
                      ))
                    ],
                  ),
                  <span class="hljs-keyword">const</span> SizedBox(
                    height: <span class="hljs-number">20</span>,
                  ),
                  Row(
                    children: [
                      <span class="hljs-keyword">const</span> Icon(
                        Icons.favorite_border,
                        color: Colors.white,
                      ),
                      <span class="hljs-keyword">const</span> SizedBox(
                        width: <span class="hljs-number">10</span>,
                      ),
                      Text(
                        product.rating.count.toString(),
                        maxLines: <span class="hljs-number">2</span>,
                        style:
                            <span class="hljs-keyword">const</span> TextStyle(color: Colors.white, fontSize: <span class="hljs-number">14</span>),
                      ),
                    ],
                  ),
                ],
              ),
              Positioned(
                right: <span class="hljs-number">0</span>,
                bottom: <span class="hljs-number">0</span>,
                child: Container(
                  width: <span class="hljs-number">125</span>,
                  padding:
                      <span class="hljs-keyword">const</span> EdgeInsets.symmetric(horizontal: <span class="hljs-number">20</span>, vertical: <span class="hljs-number">5</span>),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(<span class="hljs-number">8</span>),
                    border: Border.all(color: primaryColor),
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(product.price.toString()),
                      <span class="hljs-keyword">const</span> SizedBox(
                        width: <span class="hljs-number">10</span>,
                      ),
                      <span class="hljs-keyword">const</span> Icon(Icons.shopping_cart_outlined),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
</code></pre>
<ul>
<li>Add the following content in <code>main.dart</code></li>
</ul>
<ul>
<li><pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/presentation/blocs/products_bloc.dart'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:pagination_in_flutter/presentation/pages/products_page.dart'</span>;

  <span class="hljs-keyword">void</span> main() {
    runApp(<span class="hljs-keyword">const</span> MyApp());
  }

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
    <span class="hljs-keyword">const</span> MyApp({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

    <span class="hljs-meta">@override</span>
    Widget build(BuildContext context) {
      <span class="hljs-keyword">return</span> MultiBlocProvider(
          providers: [BlocProvider(create: (context) =&gt; ProductsBloc())],
          child: MaterialApp(
            title: <span class="hljs-string">'Pagination Example with Flutter'</span>,
            debugShowCheckedModeBanner: <span class="hljs-keyword">false</span>,
            theme: ThemeData(useMaterial3: <span class="hljs-keyword">true</span>),
            home: <span class="hljs-keyword">const</span> ProductsPage(),
          ));
    }
  }
</code></pre>
</li>
<li><p>Create <code>colors.dart</code></p>
<pre><code class="lang-dart">  <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

  Color primaryColor = Colors.blueAccent;
</code></pre>
</li>
</ul>
<h2 id="heading-output">OUTPUT</h2>
<p><strong>Fetching Items when No Error Occurred</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682172979909/db2e3e03-defa-4004-be9a-f8fec8326b60.gif" alt class="image--center mx-auto" /></p>
<p><strong>Fetching items when the error occurred while loading more:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682173085976/63abcd77-5489-47eb-9b68-d61ea1fc73ed.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-hurry-we-successfully-implemented-pagination-in-flutter-using-blocs">Hurry we successfully implemented Pagination in Flutter using Blocs.</h3>
<h1 id="heading-source-code">Source code:</h1>
<ul>
<li><p>Flutter Github Repo: <a target="_blank" href="https://github.com/Djsmk123/flutter_pagination">https://github.com/Djsmk123/flutter_pagination</a></p>
</li>
<li><p>Backend Repo: <a target="_blank" href="https://github.com/Djsmk123/flutter_pagination_api">https://github.com/Djsmk123/flutter_pagination_api</a></p>
</li>
</ul>
<blockquote>
<p>Follow me:</p>
</blockquote>
<ul>
<li><p><a target="_blank" href="https://github.com//djsmk123">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/md-mobin-bb928820b">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/smk_winner">Twitter</a></p>
</li>
<li><p>dev.to: <a target="_blank" href="https://dev.to/djsmk123/">https://dev.to/djsmk123/</a></p>
</li>
</ul>