flex_infinity_view 0.2.0
flex_infinity_view: ^0.2.0 copied to clipboard
Flexible infinite scrolling for Flutter lists, grids, and staggered layouts with loading, empty, and error states.
flex_infinity_view #
Flexible infinite scrolling for Flutter lists, grids, and simple staggered layouts with loading, empty, and error states built in.
Features #
- Standalone and sliver-based infinite scrolling widgets
- Support for
ListView,GridView, and a two-column staggered layout - Vertical and horizontal infinite scrolling for list layouts
- Automatic first-page loading and next-page loading on scroll
- Explicit
hasMoresupport so pagination can stop cleanly - Default initial loading with
CircularProgressIndicator - Custom initial loading with
loadingWidget - Custom widgets for loading, empty, error, and load-more states
- External
ScrollControllersupport
Getting started #
Add the package to your pubspec.yaml:
dependencies:
flex_infinity_view: ^0.2.0
Usage #
import 'package:flex_infinity_view/flex_infinity_view.dart';
import 'package:flutter/material.dart';
class ProductList extends StatefulWidget {
const ProductList({super.key});
@override
State<ProductList> createState() => _ProductListState();
}
class _ProductListState extends State<ProductList> {
final List<String> _items = [];
bool _isLoading = false;
bool _hasError = false;
bool _hasMore = true;
Future<void> _loadMore(int page, int limit) async {
if (_isLoading) return;
setState(() {
_isLoading = true;
_hasError = false;
});
try {
await Future<void>.delayed(const Duration(milliseconds: 600));
final nextItems = List.generate(
limit,
(index) => 'Item ${(page - 1) * limit + index + 1}',
);
setState(() {
_items.addAll(nextItems);
_hasMore = page < 4;
});
} catch (_) {
setState(() => _hasError = true);
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return FlexInfinityView<String>(
items: _items,
limit: 8,
isLoading: _isLoading,
hasError: _hasError,
hasMore: _hasMore,
loadingWidget: const Center(child: CircularProgressIndicator()),
onLoadMore: _loadMore,
itemBuilder: (context, item, index) {
return ListTile(title: Text(item));
},
);
}
}
Layouts #
Use layoutType to switch between supported presentations:
FlexInfinityView<MyItem>(
layoutType: InfiniteLayoutType.grid,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.78,
),
items: items,
limit: 12,
isLoading: isLoading,
hasMore: hasMore,
onLoadMore: fetchItems,
itemBuilder: (context, item, index) => ProductCard(item: item),
)
Available values:
InfiniteLayoutType.listInfiniteLayoutType.gridInfiniteLayoutType.staggered
Horizontal lists #
Set scrollDirection to Axis.horizontal when using the list layout. Give
the view a bounded height and each item a suitable width.
SizedBox(
height: 240,
child: FlexInfinityView<Product>(
scrollDirection: Axis.horizontal,
items: products,
limit: 12,
isLoading: isLoading,
hasMore: hasMore,
onLoadMore: fetchProducts,
itemBuilder: (context, product, index) => SizedBox(
width: 280,
child: ProductCard(item: product),
),
),
)
Horizontal direction is supported only with InfiniteLayoutType.list.
Vertical remains the default, so existing integrations keep their behavior.
Sliver usage #
Use FlexInfinitySliverView when you need to place the package inside an
existing CustomScrollView.
CustomScrollView(
slivers: [
const SliverAppBar(
pinned: true,
title: Text('Products'),
),
FlexInfinitySliverView<Product>(
items: items,
limit: 12,
isLoading: isLoading,
hasError: hasError,
hasMore: hasMore,
layoutType: InfiniteLayoutType.grid,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.78,
),
onLoadMore: fetchProducts,
itemBuilder: (context, item, index) => ProductCard(item: item),
),
],
)
A sliver follows its parent scroll axis. For a horizontal sliver list, set
scrollDirection: Axis.horizontal on CustomScrollView and use
InfiniteLayoutType.list on FlexInfinitySliverView.
SizedBox(
height: 240,
child: CustomScrollView(
scrollDirection: Axis.horizontal,
slivers: [
FlexInfinitySliverView<Product>(
layoutType: InfiniteLayoutType.list,
items: products,
limit: 12,
isLoading: isLoading,
hasMore: hasMore,
onLoadMore: fetchProducts,
itemBuilder: (context, product, index) => SizedBox(
width: 400,
child: ProductCard(item: product),
),
),
],
),
)
Do not set a separate direction on FlexInfinitySliverView: Flutter slivers
inherit their main axis from the parent scroll view. As with standalone
horizontal lists, give the viewport a bounded height and each item a finite
width.
State handling #
The widget supports:
- Initial loading with
loadingWidget - Empty state with
emptyWidget - Error state with
errorWidget - Load-more state with
loadMoreWidget - Pagination stop with
hasMore
Example #
The example/ app demonstrates:
FlexInfinityViewas a standalone scrollableFlexInfinitySliverViewinsideCustomScrollView- Vertical and horizontal standalone lists
- Vertical grid and horizontal list sliver composition
- List, grid, and staggered layouts
- Default loading and custom loading states
- Empty responses
- Initial request errors
- Next-page request errors with retry
Run it with:
flutter run -d chrome example/lib/main.dart
Additional information #
Before publishing, run:
flutter analyze
flutter test
flutter pub publish --dry-run
Package page guidance used for this setup:
- Dart package page recommendations: https://dart.dev/tools/pub/writing-package-pages
- Publishing guidance: https://dart.dev/tools/pub/publishing