juice_paging 0.2.0 copy "juice_paging: ^0.2.0" to clipboard
juice_paging: ^0.2.0 copied to clipboard

Paged / infinite-scroll list state as a generic Juice bloc, behind a cursor-based fetcher seam.

example/lib/main.dart

import 'package:juice/juice.dart';
import 'package:juice_paging/juice_paging.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Fake paged source: 95 items, pages of 20, cursor = next offset.
  BlocScope.register<PagingBloc<int>>(
    () => PagingBloc<int>.withConfig(PagingConfig(
      fetcher: (cursor) async {
        await Future<void>.delayed(const Duration(milliseconds: 600));
        const total = 95, size = 20;
        final offset = (cursor as int?) ?? 0;
        final end = (offset + size).clamp(0, total);
        return PageResult(
          [for (var i = offset; i < end; i++) i],
          nextCursor: end < total ? end : null,
        );
      },
    )),
    lifecycle: BlocLifecycle.permanent,
  );

  runApp(const DemoApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'juice_paging demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
        useMaterial3: true,
      ),
      home: const FeedScreen(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('juice_paging — infinite scroll')),
      body: Feed(),
    );
  }
}

class Feed extends StatelessJuiceWidget<PagingBloc<int>> {
  Feed({super.key}) : super(groups: PagingGroups.all);

  @override
  Widget onBuild(BuildContext context, StreamStatus status) {
    final s = bloc.state;
    if (s.isLoadingFirst && s.isEmpty) {
      return const Center(child: CircularProgressIndicator());
    }
    if (s.status == PagingStatus.error && s.isEmpty) {
      return Center(
        child: Column(mainAxisSize: MainAxisSize.min, children: [
          Text('Error: ${s.error}'),
          TextButton(onPressed: bloc.retry, child: const Text('Retry')),
        ]),
      );
    }

    return RefreshIndicator(
      onRefresh: () async {
        bloc.refresh();
      },
      child: NotificationListener<ScrollNotification>(
        onNotification: (n) {
          if (n.metrics.pixels >= n.metrics.maxScrollExtent - 300) {
            bloc.loadMore();
          }
          return false;
        },
        child: ListView.builder(
          itemCount: s.items.length + 1,
          itemBuilder: (context, i) {
            if (i < s.items.length) {
              return ListTile(
                leading: CircleAvatar(child: Text('${s.items[i]}')),
                title: Text('Item ${s.items[i]}'),
              );
            }
            // Footer: spinner / end / retry.
            return Padding(
              padding: const EdgeInsets.all(16),
              child: Center(
                child: switch (s.status) {
                  PagingStatus.loadingMore =>
                    const CircularProgressIndicator(),
                  PagingStatus.end => const Text('— end —'),
                  PagingStatus.error => TextButton(
                      onPressed: bloc.retry, child: const Text('Retry')),
                  _ => const SizedBox.shrink(),
                },
              ),
            );
          },
        ),
      ),
    );
  }
}
0
likes
150
points
18
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Paged / infinite-scroll list state as a generic Juice bloc, behind a cursor-based fetcher seam.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#pagination #infinite-scroll #list #bloc #state-management

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, juice

More

Packages that depend on juice_paging