Enhanced Paginated View: Simplifying Pagination in Flutter
Tags: flutter, dart, mobile, programming
Published: 2023-07-22 20:58:01

As developers, we often come across scenarios where we need to implement paginated views in our Flutter apps. Pagination is essential when dealing with large datasets to optimize performance and provide a better user experience. However, managing the loading state, handling errors, and efficiently displaying the paginated content can become a cumbersome task.

Introducing [Enhanced Paginated View](https://pub.dev/packages/enhanced_paginated_view/), a Flutter package that streamlines the process of building paginated views, making it easy and efficient for developers to handle paginated content effortlessly.

## Why Enhanced Paginated View?

1. **Simplified Implementation**: EnhancedPaginatedView abstracts away the complexities of pagination, allowing you to focus on the core functionality of your app. With just a few lines of code, you can implement a fully functional paginated view.
2. **Efficient Scrolling**: The package takes advantage of Flutter’s ScrollController to detect when the user reaches the end of the list and automatically loads more data. This ensures smooth and efficient scrolling in your app.
3. **Customizable Loading Widget**: EnhancedPaginatedView provides a customizable loading widget that appears at the bottom of the list when new data is being fetched. You can customize the loading widget to match your app’s design and branding.
4. **Error Handling**: The package handles errors gracefully by displaying an error widget when an error occurs during data loading. This helps in providing a better user experience by informing the user about the error without disrupting the entire view.
5. **Header Support**: You can easily add a header widget at the top of the list using the header parameter. This allows you to display additional information or actions related to the paginated content.
6. **Pagination Control**: EnhancedPaginatedView includes a control mechanism that automatically detects when the end of the paginated list is reached. You can also set a maximum limit to stop loading new data once the limit is reached.

## Getting Started

To start using EnhancedPaginatedView in your Flutter project, follow these simple steps:

1- **Install the Package**: Add `enhanced_paginated_view` as a dependency in your `pubspec.yaml` file and run `flutter pub get` to install it.

```
dependencies:
  enhanced_paginated_view: ^1.0.0
```

2- **Import the Package**: Import the package in your Dart file where you want to implement the paginated view.

```

import 'package:enhanced_paginated_view/enhanced_paginated_view.dart';

```

3- **Create a Paginated View**: Instantiate EnhancedPaginatedView and pass the required parameters, such as `loadingWidget`, `onLoadMore`, `isLoadingState`, `isMaxReached`, and the initial `listOfData`.

4. **Customize Your View**: Customize the view using the `builder` parameter, which allows you to define how the paginated data should be displayed in the list.

## Using the Builder Function

The `builder` parameter in EnhancedPaginatedView allows you to customize the way the paginated data is displayed in the list. It provides you with three parameters:

physics: This parameter represents the 

1. **physics** :that will be used for the widget to control the scrolling behavior. By default, the physics will be set to NeverScrollableScrollPhysics, which prevents the widget from scrolling. However, you can customize this parameter to control how the list responds to user interactions.
2. **items**: This parameter is the list of items that will be shown in the widget. It contains the paginated data that is loaded when the user reaches the end of the list.
3. **shrinkWrap**: This parameter is a boolean that determines whether the scroll view should adjust its size to wrap its content tightly. Setting shrinkWrap to true can be useful when you have a limited number of items and want the list to take up only the required space.

Using the `builder` function, you can build various types of lists, such as `ListView`, `GridView`, or any other custom widget, to suit your app's specific design and requirements.

Here’s an example of how to use the `builder` parameter with a `ListView.separated`:

```
EnhancedPaginatedView<int>(
  // Other required parameters
  // ...

  builder: (physics, items, shrinkWrap) {
    return ListView.separated(
      physics: physics,
      shrinkWrap: shrinkWrap,
      itemCount: items.length,
      separatorBuilder: (BuildContext context, int index) {
        return const Divider(
          height: 16,
        );
      },
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title: Text('Item ${index + 1}'),
          subtitle: Text('Item ${items[index]}'),
        );
      },
    );
  },
)
```

## Using GridView.builder

EnhancedPaginatedView also supports paginated `GridView` through the `builder` parameter. Here's an example of how to use `GridView.builder`:

```
EnhancedPaginatedView<int>(
  // Other required parameters
  // ...

  builder: (physics, items, shrinkWrap) {
    return GridView.builder(
      physics: physics,
      shrinkWrap: shrinkWrap,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2, // Set the number of columns here
      ),
      itemCount: items.length,
      itemBuilder: (BuildContext context, int index) {
        return Card(
          child: Center(
            child: Text('Item ${items[index]}'),
          ),
        );
      },
    );
  },
)
```

By providing the appropriate `physics`, `items`, and `shrinkWrap` parameters to the builder function, you can easily create various types of paginated lists or grids to cater to your app's layout and design.

You can customize the examples further, depending on your preferred layout and widget design. The key takeaway is that the `builder` parameter offers great flexibility and allows you to build a wide variety of paginated views tailored to your app's specific needs.

## Adding a Header Widget

Enhanced Paginated View provides the `header` parameter, which allows you to add a header widget at the top of the paginated list. You can use this feature to display additional information or actions related to the paginated content.

To add a header widget, simply pass the desired widget to the `header` parameter when instantiating the EnhancedPaginatedView widget. If you don't want to display a `header`, you can set the `header` parameter to `null`.

Here's an example of how to add a header widget to your paginated list:

```
EnhancedPaginatedView<int>(
  // Other required parameters
  // ...

  header: Center(
    child: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          Text(
            'Header',
            style: Theme.of(context).textTheme.headlineLarge,
          ),
          const SizedBox(height: 16),
          OutlinedButton(
            onPressed: () {
              // Handle button press here
            },
            child: const Text('Button in Header'),
          ),
        ],
      ),
    ),
  ),
)
```

In this example, we have added a `Column` with a `Text` widget and an `OutlinedButton` to the header. You can customize the header as needed by adding different widgets and styling them according to your app's design.

## Handling Errors Gracefully

EnhancedPaginatedView makes error handling simple by providing the `errorWidget` parameter. This parameter allows you to define a widget that will be displayed when an error occurs during data loading.

To use the `errorWidget`, you need to set the `showErrorWidget` parameter to `true` and pass the `errorWidget` when instantiating the EnhancedPaginatedView widget.

Here's an example of how to handle errors gracefully using the `errorWidget`:

```
EnhancedPaginatedView<int>(
  // Other required parameters
  // ...

  showErrorWidget: true,
  errorWidget: (int page) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.error_outline,
            size: 48,
            color: Colors.red,
          ),
          const SizedBox(height: 16),
          Text(
            'Error Occurred',
            style: Theme.of(context).textTheme.headline6,
          ),
          const SizedBox(height: 8),
          Text(
            'An error occurred while loading data.',
            style: Theme.of(context).textTheme.subtitle1,
          ),
          const SizedBox(height: 8),
          ElevatedButton(
            onPressed: () {
              // Retry button action
            },
            child: const Text('Retry'),
          ),
        ],
      ),
    );
  },
)
```

In this example, we've created an error widget that displays an error icon, a message, and a retry button. When an error occurs, this widget will be shown to the user, allowing them to retry loading the data.

The `errorWidget` function receives an `int page` parameter, which can be used to handle the error based on the current page number if required.

## Full code example

```
class MyPaginatedView extends StatelessWidget {
  final initList = List<int>.generate(10, (int index) => index);
  bool isLoading = false;
  final maxItems = 30;
  bool isMaxReached = false;

  Future<void> loadMore(int page) async {
    // here we simulate that the list reached the end
    // and we set the isMaxReached to true to stop
    // the loading widget from showing
    if (initList.length >= maxItems) {
      setState(() => isMaxReached = true);
      return;
    }
    setState(() => isLoading = true);
    await Future.delayed(const Duration(seconds: 3));
    // here we simulate the loading of new items
    // from the server or any other source
    // we pass the page number to the onLoadMore function
    // that the package provide to load the next page
    setState(() {
      initList.addAll(List<int>.generate(10, (int index) => index));
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // Scaffold contents here
      body: EnhancedPaginatedView<int>(
          listOfData: initList,
          isLoadingState: isLoading,
          isMaxReached: isMaxReached,
          onLoadMore: loadMore,
          loadingWidget: const Center(child: CircularProgressIndicator()),
          showErrorWidget: false,
          errorWidget: (page) => Center(
            child: Column(
              children: [
                const Text('No items found'),
                ElevatedButton(
                  onPressed: () => loadMore(page),
                  child: const Text('Reload'),
                )
              ],
            ),
          ),
          emptyWidget: const Center(child: Text('No items found')),
          header: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: [
                Text(
                  'Header',
                  style: Theme.of(context).textTheme.headlineLarge,
                ),
                const SizedBox(height: 16),
                OutlinedButton(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) => const BlocView(),
                      ),
                    );
                  },
                  child: const Text('Bloc Example'),
                ),
              ],
            ),
          ),
          builder: (physics, items, shrinkWrap) {
            return ListView.separated(
              // here we must pass the physics, items and shrinkWrap
              // that came from the builder function
              physics: physics,
              shrinkWrap: shrinkWrap,
              itemCount: items.length,
              separatorBuilder: (BuildContext context, int index) {
                return const Divider(
                  height: 16,
                );
              },
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text('Item ${index + 1}'),
                  subtitle: Text('Item ${items[index]}'),
                );
              },
            );
          },
        ),
    );
  }
}
```

In this full example, we have created a `MyPaginatedView` widget that uses EnhancedPaginatedView to display a list of numbers. The list starts with 10 items, and when the user scrolls to the end, more items are fetched and added to the list. The header contains a simple "Button in Header" button. If an error occurs during data loading, an error widget is displayed with a retry button.

## Update 1.0.0 - Introducing Reverse List Functionality

In our latest update, we are excited to present a new and useful feature for our chat app - the ability to display lists and their content in reverse order. This enhancement opens up new possibilities for developers seeking to implement a reversed list view in their chat applications for example.

To make use of this functionality, there are two key aspects to consider:

1. New Parameter in the Builder Function: With the introduction of the reverse functionality, we have added a new parameter to the builder function. This parameter is aptly named reverse. For developers utilizing ListView.Builder, there already exists a parameter called reverse. All you need to do is pass this parameter to the provided builder.

2. Optional Parameter in the Package: Additionally, we have introduced a new optional parameter at the package level, also named reverse. By default, this parameter is set to false. To enable the reversed list view, all you need to do is set this parameter to true.

Here's an example of how to use it:

```
EnhancedPaginatedView<int>(
  // Other required parameters
  // ...

  
  /// The `reverse` parameter is a boolean that will be used
  /// to reverse the list and its children
  /// It code be handy when you are building a chat app for example
  /// and you want to reverse the list to show the latest messages
  reverse = false,
  builder = (physics, items, shrinkWrap, reverse) {
    return ListView.separated(
      // here we must pass the physics, items, and shrinkWrap
      // that came from the builder function
      reverse: reverse,
      physics: physics,
      shrinkWrap: shrinkWrap,
      itemCount: items.length,
      separatorBuilder: (BuildContext context, int index) {
        return const Divider(
          height: 16,
        );
      },
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title: Text('Item ${index + 1}'),
          subtitle: Text('Item ${items[index]}'),
        );
      },
    );
  },
),
```
With this simple addition, you can now effortlessly create a chat app where the messages are displayed in reverse chronological order, providing a more intuitive user experience.

Try out the reverse functionality in your chat app today and let us know how it enhances your user interactions! We are committed to continually improving our package to meet your development needs and look forward to bringing you more exciting updates in the future.

## Conclusion

EnhancedPaginatedView makes implementing paginated views in Flutter a breeze. With its streamlined features, you can focus on delivering the best user experience while efficiently handling large datasets. The package abstracts away the complexities of pagination and allows you to create clean, performant, and user-friendly paginated views effortlessly.

So, the next time you need to implement pagination in your Flutter app, give EnhancedPaginatedView a try, and experience the convenience it offers.

Happy coding!

This is my first article on **Dev.to** , I hope it helps. See you next time!!