Scroll Controller not firing inside List View builder - flutter

I am using a ScrollController in the ListView.builder widget.
But when I try to print something after reaching end of the screen it doesn't seem to work.
My main goal id to use it for pagination.
class RestaurantList extends StatefulWidget {
#override
_RestaurantListState createState() => _RestaurantListState();
}
class _RestaurantListState extends State<RestaurantList> {
ScrollController _controller;
void _scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
print('new rests');
}
}
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
// print('restaurant list');
return FutureBuilder<List<Restaurant>>(
future: Provider.of<Restaurants>(context, listen: false)
.fetchAndSetRestaurants(),
builder: (context, snapshot) {
// Items are not available and you need to handle this situation, simple solution is to show a progress indicator
if (!snapshot.hasData) {
//Navigator.of(context).pushNamed('loading');
return Center(child: LoadingList());
}
final List<Restaurant> restaurants = snapshot.data;
print('List: ${restaurants[0].name}');
return ListView.builder(
controller: _controller,
physics: ScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (ctx, i) =>
restaurants[i].isAvailable || restaurants[i].totalQuantity != 0
? Column(
children: [
ChangeNotifierProvider.value(
value: restaurants[i], child: RestaurantInfo()),
Container(
height: 10,
)
],
)
: Container(),
itemCount: restaurants.length,
);
},
);
}
}

Try changing like below when checking reaching end of screen.
void _scrollListener() {
if (_controller.position.atEdge) {
if (_controller.position.pixels == 0) {
// Reaching Top
} else {
// Reaching end of screen
}
}
}

Related

Flutter: I wants to show message to the user only if the user reaches end of the ListView

Below is my code but it is showing the SnackBar frequently when I reach the bottom of ListView. It also shows the SnackBar on the pages also but I wants to show it only one time how to do that.
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},
Because you are assigning new listeners every time item builder calls.
put this code in ititState so it just called once.
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
Remove the listener from the itembuilder, instead use it in initState(),as everytime item is builded on listview, it will call this listener, so it is going on everytime item get builded.
You can use Listener on ScrollController, your issue is that you assign Listener to controller in build method which is wrong, you should do it once in initState. This is a full example of what you want:
class ScrollPageTest extends StatefulWidget {
const ScrollPageTest({Key? key}) : super(key: key);
#override
State<ScrollPageTest> createState() => _ScrollPageTest();
}
class _ScrollPageTest extends State<ScrollPageTest> {
ScrollController controller = ScrollController();
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
if (controller.position.atEdge) {
if (controller.position.pixels != 0) {
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text('data = $index'),
);
},
itemCount: 100,
),
);
}
}
Try this:
bool isSnackBarShown = false;
...
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if ((_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent)
&& !isSnackBarShown) {
isSnackBarShown = true;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},

Pagination with Future Builder and List of snapshots, Flutter

I want to implement pagination when calling APIs(without any packages) with my FutureBuilder, that calls 2 APIs at the same time since one depends on the other and I'm not sure how to do that. Here is the code below:
The ListView from FutureBuilder:
final ScrollController _scrollController = ScrollController();
#override
void initState() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {}
});
super.initState();
}
FutureBuilder(
future: Future.wait(
[
RepositoryFromAPItoDB().getAllMovies(),
RepositoryFromAPItoDB().getAllGenres()
],
),
builder:
(BuildContext context, AsyncSnapshot<List<dynamic>?> snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data?[0].length,
itemBuilder: (BuildContext context, int index) {
return MoviesListTile();
},
);
}
},
),
API calls for both Lists:
Future<List<Movies?>> getAllMovies() async {
Response response = await Dio().get(Constants().moviesURL);
return (response.data['results'] as List).map((movies) {
return Movies.fromJson(movies);
}).toList();
}
Future<List<Genres?>> getAllGenres() async {
Response response = await Dio().get(Constants().genresURL));
return (response.data['genres'] as List).map((genres) {
return Genres.fromJson(genres);
}).toList();
}
Now that I have call the APIs, my list is populated and everything is working fine, except I can't implement any type of pagination.. And how do I display some sort of CircularProgressIndicator() or anything like that while I scroll the bottom of the list and when it loads again? Thanks in advance for your help!
import 'package:flutter/material.dart';
import 'package:pageview_demo/main.dart';
import 'package:pageview_demo/usersModel.dart';
import 'DataNum.dart';
class School extends StatefulWidget {
const School({Key? key}) : super(key: key);
#override
_SchoolState createState() => _SchoolState();
}
class _SchoolState extends State<School> with AutomaticKeepAliveClientMixin {
ScrollController _scrollController = ScrollController(keepScrollOffset: true);
int pageNo = 1;
bool isLoading = false;
List<UsersModel> _usersModel = [];
List<Datum> data = [];
#override
void initState() {
_scrollController.addListener(() {
final pos = _scrollController.position;
final triggerFetchMoreSize = 0.9 * pos.maxScrollExtent;
if (pos.pixels > triggerFetchMoreSize) {
Future.delayed(const Duration(seconds: 5), () {
_callApi();
});
}
});
_callApi();
super.initState();
}
_callApi() async {
var response = await getHttp();
isLoading = true;
_usersModel.clear();
setState(() {
if (data.isEmpty) {
_usersModel.add(response);
data.addAll(_usersModel[0].data);
isLoading = false;
} else {
_usersModel.add(response);
data.insertAll(data.length, _usersModel[0].data);
isLoading = false;
}
});
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
pageNo = 1;
isLoading = true;
_usersModel.clear();
data.clear();
_callApi();
},
child: ListView.builder(
padding: EdgeInsets.all(10),
shrinkWrap: true,
scrollDirection: Axis.vertical,
controller: _scrollController,
physics: const BouncingScrollPhysics(),
itemCount: isLoading ? 0 : data.length,
itemBuilder: (context, indx) => indx == data.length - 1
? Center(child: LinearProgressIndicator())
: ListTile(
onTap: (){},
leading: Text("${data[indx].id}"),
title: Text("${data[indx].name}"),
subtitle: Text("${data[indx].email}"),
),
),
);
}
#override
bool get wantKeepAlive => true;
}

RefreshIndicator on horizontal ListView.builder

I have a horizontal ListView.builder widget that I want to refresh with a RefreshIndicator when pulling it to the left.
FutureBuilder(
future: _initGetTopX(),
builder: (context, wikiSnapshot) {
if (ConnectionState.active != null &&
!wikiSnapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
if (ConnectionState.done != null &&
wikiSnapshot.hasError) {
return Center(child: Text(wikiSnapshot.error));
}
return Container(
height: 220,
child: RefreshIndicator(
onRefresh: _refreshInitGetTopX,
child: ListView.builder(
scrollDirection: Axis.horizontal,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: _getTopXList.length,
itemBuilder: (context, index) {
return Row(
children: [
MainImageThumb(
myTitle: _getTopXList[index].title +
" (" +
The List is loading initally but the RefreshIndicator doesn't show up and it doesn't reload neither...
How can I reload this list?
You can use ScrollController to listen for scrollExtents
ScrollController _controller;
We instantiate it within our initState method, in the following way:
#override
void initState() {
_controller = ScrollController();
super.initState();
}
Then we assign this _controller to our ListView.
controller: _controller,
With this we have our ListView connected with our ScrollController, we just have to listen to the events to determine what we want.
Listening to events
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
Now we are listening to the Scroll events, but how we can know if the scroll reach the top or bottom.
We just have to add these validations:
_scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
setState(() {
message = "reach the end right";
});
}
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
setState(() {
message = "reach the end left";
});
}
}
for indepth you can read more here

Scroll Controller is not listening to scroll in Flutter

Im trying to listen to scroll for lazy loading but im not getting any Listen values,I have used ListView.builder widget and have attached a scroll contoller (_controller) and have instantiated controller in initState() method, need help with the issue
class _FeedsState extends State<Feeds> {
ScrollController _controller;
int pageNumber = 1;
#override
void initState() {
super.initState();
_controller = new ScrollController();
_controller.addListener(scrollListener);
SchedulerBinding.instance.addPostFrameCallback((_) {
_controller.animateTo(
0.0,
duration: const Duration(milliseconds: 10),
curve: Curves.easeOut,
);
});
}
#override
Widget build(BuildContext context) {
print(widget.feedDetails);
return widget.feedDetails.length == 0
? PostSomething(
isAdmin: widget.isAdmin,
)
: ListView.builder(
shrinkWrap: true,
controller: _controller,
itemBuilder: (context, index) {
return Column(
children: [
index == 0
? PostSomething(
isAdmin: widget.isAdmin,
profilePic: widget.feedDetails[0]["profile_pic"])
: Container(),
(Posts(
index: index,
feedDetails: widget.feedDetails[index],
displayProfileNavigation: widget.displayProfileNavigation,
)),
],
);
},
itemCount: widget.feedDetails.length,
);
}
void scrollListener() {
print("Scrolling");
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
print("Coooool");
}
}
}
make: shrinkWrap: false,, this will enable your scrolling, if this show unbounded height exception, then try
return Scaffold(
body: Expanded(
ListView.builder(....your code..

Best practices for implementing an infinite scroll GridView in Flutter?

I want to create a GridView that displays items which will be fetching from server by offset. I load only 10 items in GridView, and after user scroll reached to 10, I will load more 10 items. What are the best practices for implementing an infinite scroll GridView in Flutter?
You need to add the ScrollController for the scrolling detection at the bottom for the ListView and GridView. As you need the GridView i have created the ScrollController listner and added to the GridView's contollerfor the detection of the scroll. I have created the demo of it , please check it once. At first time it load the 10 items and when list comes to the bottom then it add more 10 items in it.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomeState();
}
}
class HomeState extends State<HomeScreen> {
List dataList = new List<int>();
bool isLoading = false;
int pageCount = 1;
ScrollController _scrollController;
#override
void initState() {
super.initState();
////LOADING FIRST DATA
addItemIntoLisT(1);
_scrollController = new ScrollController(initialScrollOffset: 5.0)
..addListener(_scrollListener);
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gridview',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.red,
accentColor: Color(0xFFFEF9EB),
),
home: Scaffold(
appBar: new AppBar(),
body: GridView.count(
controller: _scrollController,
scrollDirection: Axis.vertical,
crossAxisCount: 2,
mainAxisSpacing: 10.0,
physics: const AlwaysScrollableScrollPhysics(),
children: dataList.map((value) {
return Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height * 0.2,
margin: EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: Text("Item ${value}"),
);
}).toList(),
)));
}
//// ADDING THE SCROLL LISTINER
_scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount + 1;
addItemIntoLisT(pageCount);
}
});
}
}
////ADDING DATA INTO ARRAYLIST
void addItemIntoLisT(var pageCount) {
for (int i = (pageCount * 10) - 10; i < pageCount * 10; i++) {
dataList.add(i);
isLoading = false;
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
And output of the above program as follow
class AllOrdersPage extends StatefulWidget {
#override
_AllOrdersPageState createState() => _AllOrdersPageState();
}
class _AllOrdersPageState extends State<AllOrdersPage> {
List<OrderDatum> ordersList;
ScrollController _scrollController = ScrollController();
int skip = 0;
bool shouldLoadMore = true;
Future<OrdersResponse> future;
#override
void initState() {
super.initState();
ordersList = [];
future = getAllOrders(skip); //load data for first time
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) { //Check whether user scrolled to last position
if (shouldLoadMore) {
setState(() {
skip += ordersList.length;
future = getAllOrders(skip); //load more data
});
}
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<OrdersResponse>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasError)
return ErrorText('${snapshot.error.toString()}');
if (snapshot.hasData) {
skip = snapshot.data.skip;
if (snapshot.data.limit + snapshot.data.skip >=
snapshot.data.total) {
shouldLoadMore = false;
}
snapshot.data.data.forEach((element) {
if (!ordersList.contains(element)) ordersList.add(element);
});
if (skip == 0 && ordersList.isEmpty) {
return ErrorText('No orders.');
}
return Scrollbar(
controller: _scrollController,
isAlwaysShown: true,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 2.5,
mainAxisSpacing: 8),
controller: _scrollController,
padding: const EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
if (index == ordersList.length) {
return shouldLoadMore
? Center(child: CircularProgressIndicator())
: Container();
}
return Container(
width: MediaQuery.of(context).size.width,
child: OrderCard(ordersList[index]));
},
itemCount: ordersList.length + 1,
),
);
}
return Loader();
});
}
}
Thanks