In Flutter, the most common and reliable way to detect when the user has scrolled to the Bottom of a ScrollView (like ListView, GridView, or SingleChildScrollView) is by using a ScrollController and listening to its positions and attach your Action.
Here are the best approaches, from simplest to most recommended:
Method 1: Using ScrollController (Recommended)
import 'package:flutter/material.dart';
class ScrollToBottomExample extends StatefulWidget {
@override
_ScrollToBottomExampleState createState() => _ScrollToBottomExampleState();
}
class _ScrollToBottomExampleState extends State<ScrollToBottomExample> {
final ScrollController _scrollController = ScrollController();
bool _isAtBottom = false;
@override
void initState() {
super.initState();
_scrollController.addListener(() {
// Check if we've reached the bottom
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 50) { // 50px tolerance
if (!_isAtBottom) {
setState(() {
_isAtBottom = true;
});
_onReachedBottom();
}
} else {
if (_isAtBottom) {
setState(() {
_isAtBottom = false;
});
}
}
});
}
void _onReachedBottom() {
print("User scrolled to the bottom!");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("You've reached the bottom!")),
);
// Load more data, trigger animation, etc.
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Scroll to Bottom Detection")),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(title: Text("Item $index"));
},
),
);
}
}
Method 2: For Pagination / “Load More” (Most Common Use Case)
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent * 0.95) { // Trigger when 95% down
if (!isLoading) {
loadMoreData(); // Your pagination function
}
}
});
Method 3: Using NotificationListener (Alternative)
NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification is ScrollUpdateNotification) {
if (notification.metrics.pixels >=
notification.metrics.maxScrollExtent - 100) {
print("Near bottom!");
}
}
return false;
},
child: ListView.builder(...),
)
Bonus: Reverse ListView (Chat-style, newest at bottom)
If you’re building a chat where new messages appear at the bottom:
ListView.builder(
controller: _scrollController,
reverse: true, // Important!
itemCount: messages.length,
itemBuilder: ...,
)
// To detect when user scrolls up (away from bottom)
if (_scrollController.position.pixels <
_scrollController.position.maxScrollExtent - 200) {
// User scrolled up
}
Summary – Best Practices
- Always use
ScrollControllerfor reliable detection - Use a small threshold (e.g.,
-50or-100) to trigger slightly before actual bottom - For pagination: trigger at ~90-95% scroll
- Always dispose the controller
- Works with
ListView,GridView,CustomScrollView, etc.
This method works perfectly on both iOS and Android in Flutter as of 2025.
