paginated_bloc_widget 1.0.1 copy "paginated_bloc_widget: ^1.0.1" to clipboard
paginated_bloc_widget: ^1.0.1 copied to clipboard

A powerful, flexible, and production-ready pagination widget for Flutter using BLoC pattern. Supports ListView, GridView, PageView, CustomScrollView, and Slivers with built-in loading states, error ha [...]

Paginated BLoC Widget #

pub package License: MIT

A powerful, flexible, and production-ready pagination widget for Flutter using the BLoC pattern. This package provides a complete solution for implementing infinite scroll pagination with support for ListView, GridView, PageView, CustomScrollView, and Slivers.

โœจ Features #

  • ๐ŸŽฏ Generic Type Support - Works with any data model
  • ๐Ÿ“ฑ Multiple Layout Types - ListView, GridView, PageView, Slivers
  • ๐Ÿ”„ Built-in States - Loading, error, empty, and success states
  • โ™ป๏ธ Pull-to-Refresh - Native refresh indicator support
  • ๐Ÿ“ Customizable Threshold - Configure when to trigger load more
  • ๐ŸŽจ Fully Customizable - Override any widget state
  • ๐Ÿงช Testable - Includes in-memory repository for testing
  • ๐Ÿ“ฆ Zero Dependencies - Only relies on flutter_bloc and equatable

๐Ÿ“ฆ Installation #

Add this to your pubspec.yaml:

dependencies:
  paginated_bloc_widget: ^1.0.0

Then run:

flutter pub get

๐Ÿš€ Quick Start #

1. Create Your Model #

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'],
    name: json['name'],
    email: json['email'],
  );
}

2. Implement the Repository #

import 'package:paginated_bloc_widget/paginated_bloc_widget.dart';

class UserRepository extends PaginatedDataRepository<User> {
  final ApiClient _client;

  UserRepository(this._client);

  @override
  Future<PaginatedResponse<User>> fetchData({
    required int page,
    int limit = 10,
    Map<String, dynamic>? filters,
  }) async {
    final response = await _client.getUsers(page: page, limit: limit);
    
    return PaginatedResponse(
      data: response.users.map((e) => User.fromJson(e)).toList(),
      hasMore: page < response.totalPages,
      currentPage: page,
      totalPages: response.totalPages,
      totalItems: response.totalCount,
    );
  }
}

3. Use the Widget #

import 'package:paginated_bloc_widget/paginated_bloc_widget.dart';

class UserListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => PaginatedDataBloc<User>(
        repository: UserRepository(context.read<ApiClient>()),
        itemsPerPage: 20,
      )..add(const LoadFirstPage()),
      child: Scaffold(
        appBar: AppBar(title: const Text('Users')),
        body: PaginatedDataWidget<User>(
          enablePullToRefresh: true,
          itemBuilder: (context, user, index) => ListTile(
            leading: CircleAvatar(child: Text(user.name[0])),
            title: Text(user.name),
            subtitle: Text(user.email),
          ),
        ),
      ),
    );
  }
}

๐Ÿ“– Usage Examples #

ListView with Separator #

PaginatedDataWidget<User>(
  layoutType: PaginatedLayoutType.listView,
  separatorWidget: const Divider(height: 1),
  enablePullToRefresh: true,
  itemBuilder: (context, user, index) => ListTile(
    title: Text(user.name),
  ),
)

GridView #

PaginatedDataWidget<Product>(
  layoutType: PaginatedLayoutType.gridView,
  crossAxisCount: 2,
  childAspectRatio: 0.75,
  crossAxisSpacing: 8,
  mainAxisSpacing: 8,
  padding: const EdgeInsets.all(16),
  itemBuilder: (context, product, index) => ProductCard(product: product),
)

Horizontal PageView #

PaginatedDataWidget<Story>(
  layoutType: PaginatedLayoutType.pageView,
  scrollDirection: ScrollDirection.horizontal,
  enablePageSnapping: true,
  itemBuilder: (context, story, index) => StoryPage(story: story),
)

CustomScrollView with Sliver Headers #

PaginatedDataWidget<Item>(
  layoutType: PaginatedLayoutType.sliverList,
  sliverHeaders: [
    SliverAppBar(
      title: const Text('My Items'),
      floating: true,
    ),
    SliverToBoxAdapter(
      child: Container(
        height: 100,
        child: const Text('Header Content'),
      ),
    ),
  ],
  itemBuilder: (context, item, index) => ItemTile(item: item),
)

Custom State Widgets #

PaginatedDataWidget<User>(
  firstPageLoadingWidget: const ShimmerList(),
  loadMoreLoadingWidget: const SmallLoader(),
  emptyWidget: const EmptyState(
    icon: Icons.people_outline,
    message: 'No users found',
  ),
  firstPageErrorWidget: (error, retry) => ErrorWidget(
    message: error,
    onRetry: retry,
  ),
  loadMoreErrorWidget: (error, retry) => TextButton(
    onPressed: retry,
    child: Text('Error: $error. Tap to retry'),
  ),
  itemBuilder: (context, user, index) => UserTile(user: user),
)

With Filters #

PaginatedDataBloc<User>(
  repository: userRepository,
  filters: {
    'status': 'active',
    'role': 'admin',
    'sortBy': 'createdAt',
  },
)..add(const LoadFirstPage())

๐Ÿ”ง BLoC Events #

Event Description
LoadFirstPage() Load the first page of data
LoadMoreData() Load the next page
RefreshData() Refresh and reload first page
ResetPagination() Reset to initial state
UpdateItem<T>(item, matcher) Update an existing item
RemoveItem<T>(item, matcher) Remove an item from the list
AddItem<T>(item, insertAtStart) Add a new item

Updating Items #

// Update a user in the list
context.read<PaginatedDataBloc<User>>().add(
  UpdateItem<User>(
    updatedUser,
    matcher: (oldItem, newItem) => oldItem.id == newItem.id,
  ),
);

Removing Items #

// Remove a user by ID
context.read<PaginatedDataBloc<User>>().add(
  RemoveItem<User>(
    matcher: (item) => item.id == deletedUserId,
  ),
);

Adding Items #

// Add a new user at the start
context.read<PaginatedDataBloc<User>>().add(
  AddItem<User>(newUser, insertAtStart: true),
);

๐Ÿ“Š State Properties #

Access state properties in your UI:

BlocBuilder<PaginatedDataBloc<User>, PaginatedDataState<User>>(
  builder: (context, state) {
    // Helper getters
    state.isInitial          // Initial state
    state.isFirstPageLoading // Loading first page
    state.isLoadingMore      // Loading more items
    state.isRefreshing       // Refreshing data
    state.hasError           // Any error occurred
    state.isEmpty            // No items loaded
    state.isSuccess          // Data loaded successfully
    
    // Data access
    state.items              // List of loaded items
    state.itemCount          // Number of items
    state.currentPage        // Current page number
    state.hasReachedMax      // All pages loaded
    state.totalItems         // Total items (if known)
    state.totalPages         // Total pages (if known)
    state.loadProgress       // Loading progress (0.0 - 1.0)
    state.error              // Error message
    
    return YourWidget();
  },
)

๐Ÿงช Testing #

Use the included InMemoryPaginatedRepository for testing:

final testRepository = InMemoryPaginatedRepository<User>(
  items: List.generate(100, (i) => User(id: i, name: 'User $i')),
  simulatedDelay: const Duration(milliseconds: 500),
);

final bloc = PaginatedDataBloc<User>(
  repository: testRepository,
  itemsPerPage: 10,
);

โš™๏ธ Configuration #

PaginationConfig #

class PaginationConfig {
  static const int defaultItemsPerPage = 10;
  static const double defaultLoadMoreThreshold = 0.8;
  static const int defaultPageViewLoadMoreOffset = 3;
}

Widget Properties #

Property Type Default Description
layoutType PaginatedLayoutType listView Layout type to use
scrollDirection ScrollDirection vertical Scroll direction
loadMoreThreshold double 0.8 Scroll threshold to trigger load more
enablePullToRefresh bool false Enable pull-to-refresh
shrinkWrap bool false Shrink wrap content
crossAxisCount int? 2 Grid columns
childAspectRatio double? 1.0 Grid item aspect ratio

๐Ÿ“ License #

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿค Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

๐Ÿ“ฃ Support #

If you find this package helpful, please give it a โญ on GitHub!

For bugs and feature requests, please open an issue.

3
likes
0
points
68
downloads

Publisher

unverified uploader

Weekly Downloads

A powerful, flexible, and production-ready pagination widget for Flutter using BLoC pattern. Supports ListView, GridView, PageView, CustomScrollView, and Slivers with built-in loading states, error handling, and pull-to-refresh.

Repository (GitHub)
View/report issues

Topics

#pagination #bloc #infinite-scroll #listview #gridview

Documentation

Documentation

License

unknown (license)

Dependencies

bloc, equatable, flutter, flutter_bloc

More

Packages that depend on paginated_bloc_widget