mo_infinite_scroll 2.0.0 copy "mo_infinite_scroll: ^2.0.0" to clipboard
mo_infinite_scroll: ^2.0.0 copied to clipboard

Infinite scroll for Flutter with pull-to-refresh, pre-fetch, horizontal lists, error recovery, a Sliver variant, and customisable placeholders.

example/example.dart

// ignore_for_file: public_member_api_docs

/// This file is the pub.dev example tab for mo_infinite_scroll.
///
/// It shows the three most common use cases:
///   1. [MoInfiniteScroll]        – standalone vertical list with built-in
///      pull-to-refresh.
///   2. [MoInfiniteScroll]        – horizontal list (carousel style).
///   3. [MoInfiniteScrollSliver]  – sliver variant inside a [CustomScrollView],
///      with pull-to-refresh delegated to the parent [RefreshIndicator].
library;

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

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

// ── Fake model & API ──────────────────────────────────────────────────────────

class Post {
  const Post({required this.id, required this.title});

  final int id;
  final String title;
}

/// Simulates a remote API: 47 posts total, 800 ms latency.
Future<List<Post>> _fetchPosts(int page, int limit) async {
  await Future.delayed(const Duration(milliseconds: 800));

  const total = 47;
  final start = (page - 1) * limit;
  if (start >= total) return [];

  return List.generate(
    (start + limit).clamp(0, total) - start,
    (i) => Post(id: start + i + 1, title: 'Post #${start + i + 1}'),
  );
}

// ── App shell ─────────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'mo_infinite_scroll example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const _HomePage(),
    );
  }
}

class _HomePage extends StatelessWidget {
  const _HomePage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('mo_infinite_scroll')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            FilledButton(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const _StandalonePage()),
              ),
              child: const Text('MoInfiniteScroll (vertical)'),
            ),
            const SizedBox(height: 12),
            FilledButton(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const _HorizontalPage()),
              ),
              child: const Text('MoInfiniteScroll (horizontal)'),
            ),
            const SizedBox(height: 12),
            FilledButton.tonal(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const _SliverPage()),
              ),
              child: const Text('MoInfiniteScrollSliver'),
            ),
          ],
        ),
      ),
    );
  }
}

// ── Example 1 — MoInfiniteScroll (vertical) ──────────────────────────────────
//
// The simplest usage: only [fetcher] and [itemBuilder] are required.
// Pull-to-refresh is built in. An external [MoInfiniteScrollController] is
// passed so the AppBar action can also trigger a refresh.

class _StandalonePage extends StatefulWidget {
  const _StandalonePage();

  @override
  State<_StandalonePage> createState() => _StandalonePageState();
}

class _StandalonePageState extends State<_StandalonePage> {
  final _controller = MoInfiniteScrollController<Post>();

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Vertical'),
        actions: [
          // Refresh from outside the widget via the external controller.
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'Refresh',
            onPressed: _controller.refresh,
          ),
        ],
      ),
      body: MoInfiniteScroll<Post>(
        controller: _controller,
        fetcher: _fetchPosts,
        limit: 10,
        prefetchOffset: 3,
        itemBuilder: (context, post) => _PostTile(post: post),
        separatorBuilder: (_, __) => const Divider(height: 1),
      ),
    );
  }
}

// ── Example 2 — MoInfiniteScroll (horizontal) ────────────────────────────────
//
// Set [scrollDirection] to Axis.horizontal for carousel-style lists.
// Pull-to-refresh is automatically disabled on the horizontal axis.

class _HorizontalPage extends StatelessWidget {
  const _HorizontalPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Horizontal')),
      body: Center(
        child: SizedBox(
          height: 160,
          child: MoInfiniteScroll<Post>(
            fetcher: _fetchPosts,
            limit: 10,
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            itemBuilder: (context, post) => Card(
              child: SizedBox(
                width: 140,
                child: Center(child: Text(post.title)),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// ── Example 3 — MoInfiniteScrollSliver ───────────────────────────────────────
//
// Drop the sliver into any CustomScrollView. Pull-to-refresh is handled by
// the wrapping RefreshIndicator; the controller is passed in so it can be
// called from onRefresh.

class _SliverPage extends StatefulWidget {
  const _SliverPage();

  @override
  State<_SliverPage> createState() => _SliverPageState();
}

class _SliverPageState extends State<_SliverPage> {
  final _controller = MoInfiniteScrollController<Post>();

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: _controller.refresh,
        child: CustomScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          slivers: [
            const SliverAppBar(
              title: Text('Sliver'),
              floating: true,
              snap: true,
            ),
            MoInfiniteScrollSliver<Post>(
              controller: _controller,
              fetcher: _fetchPosts,
              limit: 10,
              prefetchOffset: 3,
              itemBuilder: (context, post) => _PostTile(post: post),
              separatorBuilder: (_, __) =>
                  const Divider(height: 1, indent: 16, endIndent: 16),
            ),
          ],
        ),
      ),
    );
  }
}

// ── Shared item tile ──────────────────────────────────────────────────────────

class _PostTile extends StatelessWidget {
  const _PostTile({required this.post});

  final Post post;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: CircleAvatar(child: Text('${post.id}')),
      title: Text(post.title),
      subtitle: Text('Page ${((post.id - 1) ~/ 10) + 1}'),
    );
  }
}
2
likes
160
points
35
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Infinite scroll for Flutter with pull-to-refresh, pre-fetch, horizontal lists, error recovery, a Sliver variant, and customisable placeholders.

Homepage
Repository (GitHub)
View/report issues

Topics

#infinite-scroll #pagination #listview #lazy-loading #pull-to-refresh

License

MIT (license)

Dependencies

flutter

More

Packages that depend on mo_infinite_scroll