I used a package Pagination_view, but it crashed when the data amount was huge. that's why I make a reusable widget for infinite scroll. Here my widget code:
import 'package:flutter/material.dart';
class InfiniteScrollList<T> extends StatefulWidget {
final Future<List<T>> Function(int offset, int limit) fetchData;
final int initialOffset;
final int limit;
final Widget Function(BuildContext context, T item) itemBuilder;
final Key Function(T item)? itemKey;
final Widget? initialLoadingIndicator;
final Widget? emptyState;
final Widget? errorState;
final Widget? customRefreshIndicator;
final void Function()? onRetry;
final bool enablePullToRefresh;
final bool enableInfiniteScroll;
final Widget Function()? separatorBuilder;
final ScrollController? scrollController;
const InfiniteScrollList({
Key? key,
required this.fetchData,
this.initialOffset = 0,
this.limit = 5,
required this.itemBuilder,
this.itemKey,
this.initialLoadingIndicator,
this.emptyState,
this.errorState,
this.customRefreshIndicator,
this.onRetry,
this.enablePullToRefresh = true,
this.enableInfiniteScroll = true,
this.separatorBuilder,
this.scrollController,
}) : super(key: key);
@override
InfiniteScrollListState<T> createState() => InfiniteScrollListState<T>();
}
class InfiniteScrollListState<T> extends State<InfiniteScrollList<T>> {
List<T> items = [];
int offset = 0;
bool isLoading = false;
bool isRefreshing = false;
bool isError = false;
ScrollController? _scrollController;
@override
void initState() {
super.initState();
offset = widget.initialOffset;
_scrollController = widget.scrollController ?? ScrollController();
fetchData();
_scrollController!.addListener(() => onEndScroll(_scrollController!));
}
@override
void dispose() {
_scrollController!.dispose();
super.dispose();
}
Future<void> fetchData() async {
if (isLoading || isError) return;
setState(() {
isLoading = true;
});
try {
final newData = await widget.fetchData(offset, widget.limit);
setState(() {
items.addAll(newData);
offset += widget.limit;
isLoading = false;
isError = false;
});
} catch (e) {
setState(() {
isLoading = false;
isError = true;
});
print('Error fetching data: $e');
}
}
Future<void> onRefresh() async {
if (isRefreshing || isError || !widget.enablePullToRefresh) return;
setState(() {
isRefreshing = true;
});
try {
final refreshedData = await widget.fetchData(0, widget.limit);
setState(() {
items.clear();
items.addAll(refreshedData);
offset = widget.limit;
isRefreshing = false;
isError = false;
});
} catch (e) {
setState(() {
isRefreshing = false;
isError = true;
});
print('Error refreshing data: $e');
}
}
void onEndScroll(ScrollController controller) {
if (!isLoading &&
!isRefreshing &&
widget.enableInfiniteScroll &&
controller.position.pixels >= controller.position.maxScrollExtent) {
fetchData();
}
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: onRefresh,
child: isError
? (widget.onRetry != null
? _buildErrorWidgetWithRetry()
: (widget.errorState ?? _buildErrorWidget()))
: (items.isEmpty && isLoading
? (widget.initialLoadingIndicator ??
Center(child: CircularProgressIndicator()))
: (items.isEmpty
? (widget.emptyState ?? Center(child: Text('No items found.')))
: _buildInfiniteList())),
);
}
Widget _buildInfiniteList() {
return ListView.builder(
shrinkWrap: true,
itemCount: items.length + (widget.enableInfiniteScroll ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
if (index < items.length) {
return Column(
children: <Widget>[
if (widget.separatorBuilder != null && index > 0)
widget.separatorBuilder!(),
widget.itemBuilder(context, items[index]),
],
);
} else {
return widget.customRefreshIndicator ??
Center(child: CircularProgressIndicator());
}
},
controller: _scrollController,
);
}
Widget _buildErrorWidget() {
return Center(
child: Text('Error loading data.'),
);
}
Widget _buildErrorWidgetWithRetry() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error loading data. Tap to retry.'),
SizedBox(height: 16),
ElevatedButton(
onPressed: widget.onRetry,
child: Text('Retry'),
),
],
);
}
}
Now I want to use this widget under another list view, where many other widgets are present. But this custom widget data is not load automatically when the user scroll down at the end, even onEndScroll is not called. where is the problem? How can I solve this ?
I guess this is due to the InfiniteScrollList widget's constraint is 0.0<=h<=Infinity, so the scroll can't receive scroll event. so you can give InfiniteScrollList widget a height constraint. like wrap InfiniteScrollList with SizedBox widget.