Flutter List View Builder Scroll Controller is not working - flutter

I have a Instagram like app with stories on the top and then feed data. I want my feed list view to scroll to the top in a function (i.e. button click or whatever).
ScrollController _feedController;
#override
void initState() {
_feedController = new ScrollController();
_feedController.addListener(() {
print("scrolling");
});
_fetchData();
super.initState();
}
// This is in the build function.
return ListView.builder(
padding: EdgeInsets.all(0.0),
shrinkWrap: true,
controller: _feedController,
physics: ClampingScrollPhysics(),
itemCount: _listFeed.length,
itemBuilder: (BuildContext context, int index) {
return ProductWidget(
product: _listFeed[index],
navigateOnImage: true,
);
},
);
// This function should scroll the list view to the top.
refresh() {
_fetchData();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_feedController.animateTo(
0.0,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
print("home screen refreshed");
}
I am initializing my controller on initState() and added a listener. neither the listener nor the controller's animateTo() function is working. I have also tried using WidgetsBinding.instance.addPostFrameCallback(). What am I missing.. ??

You need to call your fetchData function inside the addListener of ScrollController. Here is a proper example of the use of ScrollController:
#override
void initState() {
_feedController = new ScrollController()..addListener(function);
super.initState();
}
void function(){
print("scrolling");
_fetchData();
}

Related

Flutter Riverpod: Using ref.watch Outside of Build Function

In my Flutter app, I have a function gotoPage() that animates a PageView widget to a new page. I'd like this function to be called whenever newPageProvider is updated. How do I "activate" the ref.watch() inside the function gotoPage() when gotoPage() is not part of build() function?
providers.dart
final newPageProvider = StateProvider<int>((ref) => 0);
widget.dart
class _WidgetState extends ConsumerState<WidgetState> {
final pageController = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
return PageView.builder(
controller: pageController,
itemCount: data.length,
itemBuilder: (context, index) {
return WidgetCard(poi: data[index], itemIndex: index);
},
);
}
void gotoPage() {
final index = ref.watch(newPageProvider);
pageController.animateToPage(
index,
);
}
}
I believe the answer is use ref.listen, not ref.watch (as shown below).
From the RiverPod user guide:
Similarly to ref.watch, it is possible to use ref.listen to observe a
provider.
The main difference between them is that, rather than
rebuilding the widget/provider if the listened provider changes, using
ref.listen will instead call a custom function.
The listen method should not be called asynchronously, like inside onPressed or
an ElevatedButton. Nor should it be used inside initState and other State life-cycles.
providers.dart
final newPageProvider = StateProvider<int>((ref) => 0);
widget.dart
class _WidgetState extends ConsumerState<WidgetState> {
final pageController = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
ref.listen<int>(newPageProvider, (int previousIndex, int newIndex) {
_gotoPage(newIndex);
});
return PageView.builder(
controller: pageController,
itemCount: data.length,
itemBuilder: (context, index) {
return WidgetCard(poi: data[index], itemIndex: index);
},
);
}
void gotoPage(index) {
pageController.animateToPage(
index,
);
}
}
EDIT: Use ref.listen in build() instead.
Like so:
#override
Widget build(BuildContext context){
ref.listen(
newPageProvider,
(oldIndex, newIndex){
pageController.animateToPage(newIndex);
}
);
return Scaffold(...);
}

scrollcontroller not attached to any scroll views (Swiper)

I'm using a Swiper package to achieve a carousel effect on my images.
I'm trying to update the current index of my Swiper by passing a callback function to it's child.
but when I try to call the function, it returns this " scrollcontroller not attached " error.
I've added a SwiperController but still the same.
Here is my code:
SwiperController swiperController;
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.black,
child: Swiper(
controller: swiperController,
index: _index,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext c, int i) {
return StoriesPerUser(
storiesList: widget.storiesList,
selectedIndex: i,
updateFunction: callBack,
);
},
itemCount: widget.storiesList.length,
loop: false,
duration: 1000,
));
}
callBack() {
setState(() {
_index++;
});
}
Please help.
ANSWER:
If any of you guys want to use this package, and if you want a feature similar to mine, instead of updating the index, just use the one of the methods of SwiperController which is next().
this solved my problem:
callBack() {
setState(() {
swiperController.next();
});
}
Update:
It seems that SwiperController was not instantiated and initialised. You can do it by overriding the initState method:
#override
void initState() {
controller = SwiperController();
controller.length = 10
//controller.fillRange(0, 10, SwiperController());
super.initState();
}

Create a new element once a user scrolled to the end of the list FLUTTER

I have a listview with some TextFields inside, I think that it will be cool to add the textfield once a user scrolls down,
For example:
We have 5 Elements in ListView after a user reaches the fifth element and continued scrolling down a new element is created.
I couldn't find a solution for this task, I tried to check scrollposition for elements but unfortunately, I got an error
flutter: Another exception was thrown: NoSuchMethodError: The getter
'position' was called on null.
Here is the code..
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
}
FlatButton(
onPressed: () {
print(_controller.position.minScrollExtent);
},
child: Text('ds'),
),
ListView
- controller: _controller
Here is the one solution if you want to add the new element on the end of the list view
ScrollController _controller;
ListView.builder(
controller: _controller,
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(title: Text("Index : $index"));
},
)
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
_scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
setState(() {
message = "reach the bottom";
});
}
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
setState(() {
message = "reach the top";
});
}
}
in the maxScrollExtent you need to add the element into the listview. I hope it will helps you.
For more information visit here
For creating a ListView with a dynamic number of children, you might want to check out the ListView.builder factory constructor.
ListView.builder(
itemBuilder: (context, i) => Text('$i'),
)
Check out the "Working with long lists" tutorial for more information.

Flutter how to get last PageView scroll direction

I'm trying to get which direction(left or right) user swiped by using PageView.
I was able to get direction like so.
Code:
PageController _controller;
#override
void initState() {
_controller = new PageController()..addListener(_listener);
super.initState();
}
_listener() {
if (_controller.position.userScrollDirection == ScrollDirection.reverse) {
print('swiped to right');
} else {
print('swiped to left');
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: PageView.builder(
itemCount: 10,
controller: _controller,
itemBuilder: (context, index) {
return new Center(child: Text('item ${++index}'));
}),
);
}
However since it's not getting end of scrolling, print method
return this many times.
Is there way I can get this after current page switched to next page completely?
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
flutter: swiped to right
A better solution is as follows, using the PageView widget's built in function.
PageView(
onPageChanged: (int page) {
// this page variable is the new page and will change before the pageController fully reaches the full, rounded int value
var swipingRight = page > pageController.page;
print(swipingRight);
},
Compare the current _controller.page.round() with the value from the previous listener invocation (store the previous value in the State).
If the current value is greater than the previous value, the user swiped to the right.
If the current value is lower than the previous value, the user swiped to the left.
None of the solutions here worked for me, figuring this out was a huge headache.
I ended up using a gesture detector and disabling the PageView gestures entirely.
The NeverScrollableScrollPhysics() is how you disable the PageView's built-in gestures.
class MyPageView extends StatefulWidget {
#override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
PageController _pageController;
Duration pageTurnDuration = Duration(milliseconds: 500);
Curve pageTurnCurve = Curves.ease;
#override
void initState() {
super.initState();
// The PageController allows us to instruct the PageView to change pages.
_pageController = PageController();
}
void _goForward() {
_pageController.nextPage(duration: pageTurnDuration, curve: pageTurnCurve);
}
void _goBack() {
_pageController.previousPage(
duration: pageTurnDuration, curve: pageTurnCurve);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
// Using the DragEndDetails allows us to only fire once per swipe.
onHorizontalDragEnd: (dragEndDetails) {
if (dragEndDetails.primaryVelocity < 0) {
// Page forwards
print('Move page forwards');
_goForward();
} else if (dragEndDetails.primaryVelocity > 0) {
// Page backwards
print('Move page backwards');
_goBack();
}
},
child: PageView.builder(
itemCount: 10,
controller: _pageController,
// NeverScrollableScrollPhysics disables PageView built-in gestures.
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return new Center(child: Text('item ${++index}'));
}),
),
);
}
}
Your code is okay just add a setState
like this
PageController _controller;
#override
void initState() {
_controller = new PageController()..addListener(_listener);
super.initState();
}
_listener() {
setState(() {
if (_controller.position.userScrollDirection == ScrollDirection.reverse) {
print('swiped to right');
} else {
print('swiped to left');
}
});
}

Current scroll offset inside a Flutter ListView, SliverList,etc

How do I get the current scroll offset inside a Flutter ListView, GridView, SliverList`, etc?
If you're inside the scroll view use Scrollable.of(context).position.pixels.
If you're outside, you probably want to hand a ScrollController in as the controller argument of the scroll view, then you can read controller.offset.
Alternatively, use scroll notifications with NotificationListener.
This was asked on Flutter Gitter, and answered:
https://gitter.im/flutter/flutter?at=591243f18a05641b1167be0e
For someone else, looking for code implementation, you can use ScrollController like this:
Using NotificationListener:
NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
print(scrollNotification.metrics.pixels); // <-- This is it.
return false;
},
child: ListView.builder(
itemCount: 200,
itemBuilder: (c, i) => Text('Item $i'),
),
)
Using ScrollController.offset
final ScrollController _controller = ScrollController();
#override
void initState() {
super.initState();
_controller.addListener(() {
print(_controller.offset); // <-- This is it.
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _controller,
itemCount: 200,
itemBuilder: (c, i) => Text('Item $i'),
),
);
}
Another approach is with NotificationListener
NotificationListener(
child: ListView(
controller: _scrollController,
children: ...
),
onNotification: (notification) {
if (notification is ScrollEndNotification) {
print(_scrollController.position.pixels);
}
return false;
},
)