smart_pagination 2.1.1 copy "smart_pagination: ^2.1.1" to clipboard
smart_pagination: ^2.1.1 copied to clipboard

Powerful Flutter pagination library with built-in BLoC state management, 6+ view types (ListView, GridView, PageView, StaggeredGrid, ReorderableListView, Column, Row, Custom), advanced error handling [...]

example/example.md

Smart Pagination Examples #

This document provides practical examples of using the Smart Pagination library.

Table of Contents #


1. Basic ListView Example #

Simple pagination with a REST API:

import 'package:flutter/material.dart';
import 'package:smart_pagination/pagination.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

// Model
class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

  Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
  });

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'].toString(),
      name: json['name'] as String,
      price: (json['price'] as num).toDouble(),
      imageUrl: json['image'] as String,
    );
  }
}

class ProductListPage extends StatelessWidget {
  // Data provider function
  Future<List<Product>> fetchProducts(PaginationRequest request) async {
    final response = await http.get(
      Uri.parse(
        'https://api.example.com/products?page=${request.page}&limit=${request.pageSize}',
      ),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['items'] as List)
          .map((item) => Product.fromJson(item))
          .toList();
    } else {
      throw Exception('Failed to load products');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Products')),
      body: SmartPagination.listViewWithProvider<Product>(
        request: PaginationRequest(page: 1, pageSize: 20),
        provider: PaginationProvider.future(fetchProducts),
        itemBuilder: (context, items, index) {
          final product = items[index];
          return ListTile(
            leading: Image.network(product.imageUrl, width: 50, height: 50),
            title: Text(product.name),
            subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
            trailing: Icon(Icons.arrow_forward_ios),
            onTap: () {
              // Navigate to product details
            },
          );
        },
      ),
    );
  }
}

2. GridView with Custom Styling #

Display items in a grid layout:

class PhotoGalleryPage extends StatelessWidget {
  Future<List<Photo>> fetchPhotos(PaginationRequest request) async {
    // Fetch photos from API
    final response = await http.get(
      Uri.parse('https://api.example.com/photos?page=${request.page}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['photos'] as List)
          .map((item) => Photo.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load photos');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Photo Gallery')),
      body: SmartPagination.gridViewWithCubit(
        cubit: SmartPaginationCubit<Photo>(
          request: PaginationRequest(page: 1, pageSize: 20),
          provider: PaginationProvider.future(fetchPhotos),
        ),
        itemBuilder: (context, items, index) {
          final photo = items[index];
          return Card(
            elevation: 4,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            child: Column(
              children: [
                Expanded(
                  child: ClipRRect(
                    borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
                    child: Image.network(
                      photo.url,
                      fit: BoxFit.cover,
                      width: double.infinity,
                    ),
                  ),
                ),
                Padding(
                  padding: EdgeInsets.all(8),
                  child: Text(
                    photo.title,
                    style: TextStyle(fontWeight: FontWeight.bold),
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ],
            ),
          );
        },
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 12,
          mainAxisSpacing: 12,
          childAspectRatio: 0.75,
        ),
        padding: EdgeInsets.all(16),
      ),
    );
  }
}

3. PageView for Swipeable Content #

Create a swipeable carousel:

class ArticleViewerPage extends StatelessWidget {
  Future<List<Article>> fetchArticles(PaginationRequest request) async {
    // Fetch articles
    final response = await http.get(
      Uri.parse('https://api.example.com/articles?page=${request.page}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['articles'] as List)
          .map((item) => Article.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load articles');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Articles')),
      body: SmartPagination.pageViewWithCubit(
        cubit: SmartPaginationCubit<Article>(
          request: PaginationRequest(page: 1, pageSize: 1), // One article at a time
          provider: PaginationProvider.future(fetchArticles),
        ),
        itemBuilder: (context, items, index) {
          final article = items[index];
          return Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (article.imageUrl != null)
                  Image.network(
                    article.imageUrl!,
                    height: 200,
                    width: double.infinity,
                    fit: BoxFit.cover,
                  ),
                SizedBox(height: 16),
                Text(
                  article.title,
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 8),
                Text(
                  article.author,
                  style: TextStyle(color: Colors.grey),
                ),
                SizedBox(height: 16),
                Expanded(
                  child: SingleChildScrollView(
                    child: Text(article.content),
                  ),
                ),
              ],
            ),
          );
        },
        onPageChanged: (index) {
          print('Viewing article at index: $index');
        },
      ),
    );
  }
}

Add search and filter functionality:

class SearchableProductsPage extends StatefulWidget {
  @override
  State<SearchableProductsPage> createState() => _SearchableProductsPageState();
}

class _SearchableProductsPageState extends State<SearchableProductsPage> {
  final filterListener = SmartPaginationFilterChangeListener<Product>();
  final searchController = TextEditingController();

  Future<List<Product>> fetchProducts(PaginationRequest request) async {
    final response = await http.get(
      Uri.parse('https://api.example.com/products?page=${request.page}&limit=${request.pageSize}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['items'] as List)
          .map((item) => Product.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load products');
  }

  void _onSearchChanged(String query) {
    if (query.isEmpty) {
      filterListener.searchTerm = null;
    } else {
      filterListener.searchTerm = (product) =>
          product.name.toLowerCase().contains(query.toLowerCase());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Search Products'),
        bottom: PreferredSize(
          preferredSize: Size.fromHeight(60),
          child: Padding(
            padding: EdgeInsets.all(8),
            child: TextField(
              controller: searchController,
              decoration: InputDecoration(
                hintText: 'Search products...',
                prefixIcon: Icon(Icons.search),
                suffixIcon: IconButton(
                  icon: Icon(Icons.clear),
                  onPressed: () {
                    searchController.clear();
                    _onSearchChanged('');
                  },
                ),
                filled: true,
                fillColor: Colors.white,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(8),
                  borderSide: BorderSide.none,
                ),
              ),
              onChanged: _onSearchChanged,
            ),
          ),
        ),
      ),
      body: SmartPagination.listViewWithProvider<Product>(
        request: PaginationRequest(page: 1, pageSize: 20),
        provider: PaginationProvider.future(fetchProducts),
        filterListeners: [filterListener],
        itemBuilder: (context, items, index) {
          final product = items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: ListTile(
              title: Text(product.name),
              subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
              trailing: Icon(Icons.arrow_forward_ios),
            ),
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    filterListener.dispose();
    searchController.dispose();
    super.dispose();
  }
}

5. Pull-to-Refresh #

Implement refresh functionality:

class RefreshableListPage extends StatefulWidget {
  @override
  State<RefreshableListPage> createState() => _RefreshableListPageState();
}

class _RefreshableListPageState extends State<RefreshableListPage> {
  final refreshListener = SmartPaginationRefreshedChangeListener();
  late final SmartPaginationCubit<Product> cubit;

  @override
  void initState() {
    super.initState();
    cubit = SmartPaginationCubit<Product>(
      request: PaginationRequest(page: 1, pageSize: 20),
      provider: PaginationProvider.future(fetchProducts),
    );
  }

  Future<List<Product>> fetchProducts(PaginationRequest request) async {
    // Fetch products from API
    final response = await http.get(
      Uri.parse('https://api.example.com/products?page=${request.page}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['items'] as List)
          .map((item) => Product.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load products');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pull to Refresh'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () {
              refreshListener.refreshed = true;
            },
          ),
        ],
      ),
      body: RefreshIndicator(
        onRefresh: () async {
          refreshListener.refreshed = true;
          // Wait a bit for the refresh to complete
          await Future.delayed(Duration(milliseconds: 500));
        },
        child: SmartPagination.withCubit(
          cubit: cubit,
          itemBuilderType: PaginateBuilderType.listView,
          refreshListener: refreshListener,
          itemBuilder: (context, items, index) {
            final product = items[index];
            return ListTile(
              title: Text(product.name),
              subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
            );
          },
        ),
      ),
    );
  }

  @override
  void dispose() {
    refreshListener.dispose();
    cubit.dispose();
    super.dispose();
  }
}

6. Custom Error Handling #

Handle errors gracefully:

class ErrorHandlingPage extends StatelessWidget {
  Future<List<Product>> fetchProducts(PaginationRequest request) async {
    try {
      final response = await http.get(
        Uri.parse('https://api.example.com/products?page=${request.page}'),
      ).timeout(Duration(seconds: 10));

      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        return (data['items'] as List)
            .map((item) => Product.fromJson(item))
            .toList();
      } else if (response.statusCode == 404) {
        throw Exception('Products not found');
      } else {
        throw Exception('Server error: ${response.statusCode}');
      }
    } on TimeoutException {
      throw Exception('Request timed out. Please check your internet connection.');
    } catch (e) {
      throw Exception('Failed to load products: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Error Handling')),
      body: SmartPagination.listViewWithProvider<Product>(
        request: PaginationRequest(page: 1, pageSize: 20),
        provider: PaginationProvider.future(fetchProducts),
        itemBuilder: (context, items, index) {
          final product = items[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
          );
        },
        onError: (exception) {
          return Center(
            child: Padding(
              padding: EdgeInsets.all(32),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.error_outline, size: 80, color: Colors.red),
                  SizedBox(height: 24),
                  Text(
                    'Oops! Something went wrong',
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 16),
                  Text(
                    exception.toString(),
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.grey[600]),
                  ),
                  SizedBox(height: 24),
                  ElevatedButton.icon(
                    onPressed: () {
                      // Trigger a refresh
                    },
                    icon: Icon(Icons.refresh),
                    label: Text('Try Again'),
                    style: ElevatedButton.styleFrom(
                      padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

7. Stream Updates (Real-time) #

Use streams for real-time updates (e.g., Firebase):

import 'package:cloud_firestore/cloud_firestore.dart';

class RealtimeMessagesPage extends StatelessWidget {
  Future<List<Message>> fetchMessages(PaginationRequest request) async {
    final snapshot = await FirebaseFirestore.instance
        .collection('messages')
        .orderBy('timestamp', descending: true)
        .limit(request.pageSize ?? 20)
        .get();

    return snapshot.docs
        .map((doc) => Message.fromFirestore(doc))
        .toList();
  }

  Stream<List<Message>> streamMessages(PaginationRequest request) {
    return FirebaseFirestore.instance
        .collection('messages')
        .orderBy('timestamp', descending: true)
        .limit(request.pageSize ?? 20)
        .snapshots()
        .map((snapshot) => snapshot.docs
            .map((doc) => Message.fromFirestore(doc))
            .toList());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Real-time Messages')),
      body: SmartPagination.listViewWithProvider<Message>(
        request: PaginationRequest(page: 1, pageSize: 50),
        provider: PaginationProvider.future(fetchMessages),
        provider: PaginationProvider.stream(streamMessages), // Real-time updates
        itemBuilder: (context, items, index) {
          final message = items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: ListTile(
              leading: CircleAvatar(
                child: Text(message.author[0]),
              ),
              title: Text(message.author),
              subtitle: Text(message.content),
              trailing: Text(
                _formatTime(message.timestamp),
                style: TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ),
          );
        },
      ),
    );
  }

  String _formatTime(DateTime timestamp) {
    final now = DateTime.now();
    final difference = now.difference(timestamp);

    if (difference.inMinutes < 1) return 'Just now';
    if (difference.inHours < 1) return '${difference.inMinutes}m ago';
    if (difference.inDays < 1) return '${difference.inHours}h ago';
    return '${difference.inDays}d ago';
  }
}

8. Programmatic Scrolling #

Scroll to specific items programmatically:

class ScrollControlPage extends StatefulWidget {
  @override
  State<ScrollControlPage> createState() => _ScrollControlPageState();
}

class _ScrollControlPageState extends State<ScrollControlPage> {
  late final SmartPaginationController<Product> controller;

  @override
  void initState() {
    super.initState();
    controller = SmartPaginationController.of(
      request: PaginationRequest(page: 1, pageSize: 50),
      provider: PaginationProvider.future(fetchProducts),
    );
  }

  Future<List<Product>> fetchProducts(PaginationRequest request) async {
    // Fetch products
    final response = await http.get(
      Uri.parse('https://api.example.com/products?page=${request.page}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['items'] as List)
          .map((item) => Product.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load products');
  }

  void _scrollToTop() {
    controller.scrollToIndex(0);
  }

  void _scrollToMiddle() {
    controller.scrollToIndex(25);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Scroll Control'),
        actions: [
          IconButton(
            icon: Icon(Icons.arrow_upward),
            onPressed: _scrollToTop,
            tooltip: 'Scroll to top',
          ),
        ],
      ),
      body: SmartPagination.withCubit(
        cubit: controller.cubit,
        itemBuilderType: PaginateBuilderType.listView,
        itemBuilder: (context, items, index) {
          final product = items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: ListTile(
              leading: Text('#${index + 1}'),
              title: Text(product.name),
              subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'top',
            child: Icon(Icons.arrow_upward),
            onPressed: _scrollToTop,
            tooltip: 'Scroll to top',
          ),
          SizedBox(height: 16),
          FloatingActionButton(
            heroTag: 'middle',
            child: Icon(Icons.remove),
            onPressed: _scrollToMiddle,
            tooltip: 'Scroll to middle',
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

9. Memory Management #

Optimize memory usage for large lists:

class MemoryOptimizedPage extends StatelessWidget {
  Future<List<LargeItem>> fetchItems(PaginationRequest request) async {
    // Fetch large items with images
    final response = await http.get(
      Uri.parse('https://api.example.com/large-items?page=${request.page}'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return (data['items'] as List)
          .map((item) => LargeItem.fromJson(item))
          .toList();
    }
    throw Exception('Failed to load items');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Memory Optimized')),
      body: SmartPagination.listViewWithCubit(
        cubit: SmartPaginationCubit<LargeItem>(
          request: PaginationRequest(page: 1, pageSize: 20),
          provider: PaginationProvider.future(fetchItems),
          maxPagesInMemory: 3, // Keep only 3 pages in memory (60 items)
          onClear: () {
            print('Cleared old pages from memory');
          },
          onInsertionCallback: (items) {
            print('Loaded ${items.length} items');
          },
        ),
        itemBuilder: (context, items, index) {
          final item = items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: Column(
              children: [
                Image.network(
                  item.imageUrl,
                  height: 200,
                  width: double.infinity,
                  fit: BoxFit.cover,
                  cacheHeight: 400, // Optimize image memory
                ),
                Padding(
                  padding: EdgeInsets.all(16),
                  child: Text(item.title),
                ),
              ],
            ),
          );
        },
        cacheExtent: 500, // Pre-render items 500 pixels off-screen
      ),
    );
  }
}

10. REST API Integration #

Complete example with a real REST API:

import 'package:http/http.dart' as http;
import 'dart:convert';

// API Service
class ApiService {
  static const String baseUrl = 'https://jsonplaceholder.typicode.com';

  Future<List<Post>> fetchPosts(PaginationRequest request) async {
    try {
      final response = await http.get(
        Uri.parse(
          '$baseUrl/posts?_page=${request.page}&_limit=${request.pageSize}',
        ),
      );

      if (response.statusCode == 200) {
        final List<dynamic> data = json.decode(response.body);
        return data.map((json) => Post.fromJson(json)).toList();
      } else {
        throw Exception('Failed to load posts: ${response.statusCode}');
      }
    } catch (e) {
      throw Exception('Network error: $e');
    }
  }
}

// Model
class Post {
  final int id;
  final int userId;
  final String title;
  final String body;

  Post({
    required this.id,
    required this.userId,
    required this.title,
    required this.body,
  });

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'] as int,
      userId: json['userId'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
  }
}

// Page
class PostsPage extends StatelessWidget {
  final apiService = ApiService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Posts'),
        centerTitle: true,
      ),
      body: SmartPagination.listViewWithProvider<Post>(
        request: PaginationRequest(page: 1, pageSize: 10),
        provider: PaginationProvider.future(apiService.fetchPosts),
        itemBuilder: (context, items, index) {
          final post = items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            elevation: 2,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            child: InkWell(
              onTap: () {
                // Navigate to post details
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (_) => PostDetailPage(post: post),
                  ),
                );
              },
              borderRadius: BorderRadius.circular(12),
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        CircleAvatar(
                          child: Text('${post.userId}'),
                          backgroundColor: Colors.blue[100],
                          foregroundColor: Colors.blue[900],
                        ),
                        SizedBox(width: 12),
                        Expanded(
                          child: Text(
                            post.title,
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                            maxLines: 2,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 12),
                    Text(
                      post.body,
                      style: TextStyle(color: Colors.grey[700]),
                      maxLines: 3,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ],
                ),
              ),
            ),
          );
        },
        loadingWidget: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(),
              SizedBox(height: 16),
              Text('Loading posts...'),
            ],
          ),
        ),
        emptyWidget: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(Icons.inbox, size: 80, color: Colors.grey),
              SizedBox(height: 16),
              Text('No posts available'),
            ],
          ),
        ),
      ),
    );
  }
}

11. Reorderable List #

Drag and drop to reorder items:

class ReorderableListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Reorderable List')),
      body: SmartPagination.reorderableListViewWithProvider<Product>(
        request: PaginationRequest(page: 1, pageSize: 20),
        provider: PaginationProvider.future(fetchProducts),
        itemBuilder: (context, items, index) {
          final product = items[index];
          return ListTile(
            key: ValueKey(product.id),
            leading: Icon(Icons.drag_handle),
            title: Text(product.name),
            subtitle: Text('\$${product.price}'),
          );
        },
        onReorder: (oldIndex, newIndex) {
          // Handle reordering logic
          if (newIndex > oldIndex) {
            newIndex -= 1;
          }
          // Note: This only updates the UI list.
          // You should also update your backend/database.
          // In a real app, you would access the list from your state management solution
        },
      ),
    );
  }
}

Running the Examples #

To run these examples:

  1. Add the required dependencies to your pubspec.yaml:
dependencies:
  flutter:
    sdk: flutter
  smart_pagination: ^0.0.1
  http: ^1.1.0
  cloud_firestore: ^4.13.0  # For real-time example
  1. Import the package:
import 'package:smart_pagination/pagination.dart';
  1. Copy any example code and customize it for your needs!

For more information, see the README.md or visit the GitHub repository.

2
likes
140
points
333
downloads

Publisher

unverified uploader

Weekly Downloads

Powerful Flutter pagination library with built-in BLoC state management, 6+ view types (ListView, GridView, PageView, StaggeredGrid, ReorderableListView, Column, Row, Custom), advanced error handling (6 pre-built error styles), smart preloading, real-time stream support, automatic retry with exponential backoff, SmartSearchBox with auto-positioning overlay dropdown, and production-ready UI components. Zero boilerplate, highly customizable, type-safe, and well-tested.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_bloc, flutter_staggered_grid_view, logger, provider, scrollview_observer

More

Packages that depend on smart_pagination