Problem:
Initially I have disabled ListView scrolling, and want to enable it after 3 seconds. The moment app launches and you keep scrolling it for like 5 seconds (without lifting your finger off the screen), the ListView doesn't scroll.
However it should have scrolled because I am enabling scrolling at 3rd second, the console confirms ListView enabled but still I am not able to scroll it.
Code:
bool _enabled = false; // scrolling disabled initially
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3), () {
print("Scrolling enabled");
setState(() => _enabled = true); // scrolling enabled after 3 seconds
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: _enabled ? ClampingScrollPhysics() : NeverScrollableScrollPhysics(),
itemBuilder: (_, i) => ListTile(title: Text("Item $i")),
),
);
}
Here is a workaround for this:
final _scrollController = ScrollController();
var _firstScroll = true;
bool _enabled = false;
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3), () {
setState(() => _enabled = true);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onVerticalDragUpdate: (details) {
if (_enabled && _firstScroll) {
_scrollController
.jumpTo(_scrollController.position.pixels - details.delta.dy);
}
},
onVerticalDragEnd: (_) {
if (_enabled) _firstScroll = false;
},
child: AbsorbPointer(
absorbing: !_enabled,
child: ListView.builder(
controller: _scrollController,
physics: ClampingScrollPhysics(),
itemBuilder: (_, i) => ListTile(title: Text("Item $i")),
),
),
),
);
}
try this out...
class _blabla extends State<blabla> {
Timer _timer;
}
#override
void initState() {
super.initState();
bool _enabled = false;
);
}
_blablaState() {
_timer = new Timer(const Duration(seconds: 3), () {
setState(() => _enabled = true);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: _enabled ? ClampingScrollPhysics() : NeverScrollableScrollPhysics(),
itemBuilder: (_, i) => ListTile(title: Text("Item $i")),
),
);
}
#override
void dispose() {
super.dispose();
_timer.cancel();
}
I would also try with physics disabled to see if it makes a difference. There may be a conflict.
Related
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);
},
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;
}
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
}
}
}
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..
I want to implement pagination in GridView I use GridView.builder I want to download 10 by 10 items when the user reaches the last row
You can do this using a NotificationListener. As a simple demonstration it will increase the length of your GridView whenever it reaches end of page :
var items_number = 10 ;
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification){
if(scrollNotification.metrics.pixels == scrollNotification.metrics.maxScrollExtent){
setState(() {
items_number += 10 ;
});
}
},
child: GridView.builder(
itemCount: items_number,
itemBuilder: (context, index) {
//.... the reminder of your code
}
),
);
I also needed this but couldn't find any widget for the gridview pagination, so I tried to make a component based on #Mazin Ibrahim's answer below. It seems to be working but not sure if it is the right way to do this.
typedef Future<bool> OnNextPage(int nextPage);
class GridViewPagination extends StatefulWidget {
final int itemCount;
final double childAspectRatio;
final OnNextPage onNextPage;
final Function(BuildContext context, int position) itemBuilder;
final Widget Function(BuildContext context) progressBuilder;
GridViewPagination({
this.itemCount,
this.childAspectRatio,
this.itemBuilder,
this.onNextPage,
this.progressBuilder,
});
#override
_GridViewPaginationState createState() => _GridViewPaginationState();
}
class _GridViewPaginationState extends State<GridViewPagination> {
int currentPage = 1;
bool isLoading = false;
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification sn) {
if (!isLoading && sn is ScrollUpdateNotification && sn.metrics.pixels == sn.metrics.maxScrollExtent) {
setState(() {
this.isLoading = true;
});
widget.onNextPage?.call(currentPage++)?.then((bool isLoaded) {
setState(() {
this.isLoading = false;
});
});
}
return true;
},
child: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 1,
mainAxisSpacing: 1,
crossAxisCount: 2,
childAspectRatio: widget.childAspectRatio,
),
delegate: SliverChildBuilderDelegate(
widget.itemBuilder,
childCount: widget.itemCount,
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
addSemanticIndexes: true,
),
),
if (isLoading)
SliverToBoxAdapter(
child: widget.progressBuilder?.call(context) ?? _defaultLoading(),
),
],
),
);
}
Widget _defaultLoading() {
return Container(
padding: EdgeInsets.all(15),
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
}
}
Example -
GridViewPagination(
itemCount: 10,
childAspectRatio: 1,
itemBuilder: _buildGridItem,
onNextPage: (int nextPage) {
return fetchData();
},
)
create a Scroll controller
ScrollController _scrollController = new ScrollController();
add a scroll event listener
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// Bottom poistion
}
});
}
Now just need to set the controller in your GridView, ListViewand ...
GridView.builder(
controller: _scrollController,
));
You can use this plugin here: Paging. Wrap your GridView inside of it and tell me if this works!