infinite_scroll_plus 2.0.1 copy "infinite_scroll_plus: ^2.0.1" to clipboard
infinite_scroll_plus: ^2.0.1 copied to clipboard

Lightweight Flutter package for infinite scrolling, lazy loading, search, and sort in ListView & GridView.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:infinite_scroll_plus/infinite_scroll_plus.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.indigo,
      ),
      home: const MyPage(),
    );
  }
}

class MyPage extends StatefulWidget {
  const MyPage({super.key});

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  final List<String> _items = [];
  bool _hasMore = true;
  bool _isGridView = false;
  String _searchQuery = "";
  bool _sortEnabled = false;
  bool _initialLoading = true;

  @override
  void initState() {
    super.initState();
    _loadInitial();
  }

  Future<void> _loadInitial() async {
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _items.addAll(List.generate(20, (i) => 'Item $i'));
      _initialLoading = false;
    });
  }

  // ✅ Updated to use LoadMoreRequest
  Future<void> _loadMore(LoadMoreRequest request) async {
    await Future.delayed(const Duration(seconds: 2));

    // Simulate max limit
    if (_items.length >= 100) {
      setState(() => _hasMore = false);
      return;
    }

    setState(() {
      _items.addAll(
        List.generate(
          10,
          (i) => 'Item ${request.currentItemCount + i}',
        ),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Infinite Scroll Plus',
          style: TextStyle(fontWeight: FontWeight.w600),
        ),
        centerTitle: true,
        actions: [
          IconButton(
            tooltip: _isGridView ? 'List view' : 'Grid view',
            icon: Icon(_isGridView ? Icons.view_list : Icons.grid_view_rounded),
            onPressed: () => setState(() => _isGridView = !_isGridView),
          ),
          IconButton(
            tooltip: 'Sort A → Z',
            icon: Icon(
              Icons.sort_by_alpha_rounded,
              color: _sortEnabled ? cs.primary : cs.onSurfaceVariant,
            ),
            onPressed: () => setState(() => _sortEnabled = !_sortEnabled),
          ),
        ],
      ),
      body: Column(
        children: [
          // 🔍 Search Bar
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
            child: TextField(
              onChanged: (v) => setState(() => _searchQuery = v),
              decoration: InputDecoration(
                hintText: 'Search items...',
                prefixIcon: const Icon(Icons.search),
                filled: true,
                fillColor: cs.surfaceContainerHighest.withValues(alpha: 0.4),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(14),
                  borderSide: BorderSide.none,
                ),
              ),
            ),
          ),

          // 📦 Content
          Expanded(
            child: AnimatedSwitcher(
              duration: const Duration(milliseconds: 300),
              child: Padding(
                key: ValueKey(_isGridView),
                padding: const EdgeInsets.all(12),
                child: _isGridView
                    ? InfiniteScrollGrid<String>(
                        items: _items,
                        onLoadMore: _loadMore,
                        hasMore: _hasMore,
                        enableSkeletonLoader: _initialLoading,
                        searchQuery: _searchQuery,
                        onSearch: (items, q) => items
                            .where((e) =>
                                e.toLowerCase().contains(q.toLowerCase()))
                            .toList(),
                        applySort: _sortEnabled,
                        onSort: (items) {
                          items.sort((a, b) => a.compareTo(b));
                          return items;
                        },
                        emptyWidget: const _EmptyState(),
                        gridDelegate:
                            const SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 2,
                          crossAxisSpacing: 12,
                          mainAxisSpacing: 12,
                          childAspectRatio: 1.1,
                        ),
                        itemBuilder: (context, item, index) => _GridCard(
                          title: item,
                          index: index,
                        ),
                      )
                    : InfiniteScrollList<String>(
                        items: _items,
                        onLoadMore: _loadMore,
                        hasMore: _hasMore,
                        enableSkeletonLoader: _initialLoading,
                        searchQuery: _searchQuery,
                        onSearch: (items, q) => items
                            .where((e) =>
                                e.toLowerCase().contains(q.toLowerCase()))
                            .toList(),
                        applySort: _sortEnabled,
                        onSort: (items) {
                          items.sort((a, b) => a.compareTo(b));
                          return items;
                        },
                        emptyWidget: const _EmptyState(),
                        itemBuilder: (context, item, index) => Card(
                          elevation: 0.5,
                          margin: const EdgeInsets.symmetric(vertical: 6),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(14),
                          ),
                          child: ListTile(
                            leading: CircleAvatar(
                              backgroundColor: cs.primaryContainer,
                              child: Text(
                                '${index + 1}',
                                style: TextStyle(color: cs.onPrimaryContainer),
                              ),
                            ),
                            title: Text(
                              item,
                              style: const TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            subtitle: const Text('Tap to view details'),
                            trailing: const Icon(
                              Icons.chevron_right_rounded,
                            ),
                          ),
                        ),
                      ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// 🧩 Grid Card Widget
class _GridCard extends StatelessWidget {
  final String title;
  final int index;

  const _GridCard({required this.title, required this.index});

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;

    return Card(
      elevation: 0.5,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      color: cs.primaryContainer.withValues(alpha: 0.9),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.inventory_2_rounded,
              size: 36,
              color: cs.onPrimaryContainer,
            ),
            const SizedBox(height: 8),
            Text(
              title,
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w600,
                color: cs.onPrimaryContainer,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              '#${index + 1}',
              style: TextStyle(
                fontSize: 12,
                color: cs.onPrimaryContainer.withValues(alpha: 0.7),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// 📭 Empty State
class _EmptyState extends StatelessWidget {
  const _EmptyState();

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.search_off_rounded, size: 56, color: Colors.grey),
          SizedBox(height: 12),
          Text(
            'No items found',
            style: TextStyle(fontSize: 16, color: Colors.grey),
          ),
        ],
      ),
    );
  }
}
1
likes
160
points
348
downloads

Publisher

unverified uploader

Weekly Downloads

Lightweight Flutter package for infinite scrolling, lazy loading, search, and sort in ListView & GridView.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on infinite_scroll_plus