Flutter reverse listview refresh from bottom (after version >= 3) - flutter

After Flutter 3.0 the ListView(reverse: true) in my project has changed behaviour. In older version it can be refreshed from bottom pull but now it doesn't, it can just refresh from top.
I know pull_to_refresh package but I am looking for a core solution like old days.. Github issues indicate that It is intended but I don't wanna reverse flutter version.
Does anybody have idea on flutter 3 and without extra packages?
RefreshIndicator(
key: refreshKey,
onRefresh: () async { onRefresh(); },
child: buildMessagesView(messages),
),
ListView buildMessagesView(List<MessageModel> messages) {
return ListView.builder(
scrollDirection: Axis.vertical,
reverse: true,
itemCount: messages.length,
itemBuilder: (_, index) {
return Message(message: messages.elementAt(index));
},
);
}
while ListView reverse:true I expected the Refresh Indicator work by pull from down but it doesn't..
UPDATE
Solved. The ScrollController answer was very useful for me and I developed a simple widget that wraps the ListView to integrate it into the project. You can find the code here.
https://github.com/bnurd/reversed_listview_refresh

I saw the github issue. Actually I realized this behaviour without refresh indicator widget. You can use ScrollController. Create ScrollController instance with listener, attach it to ListView. Write code inside listener that check whether scroll ended. This is listener function sample code:
void _onScroll() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll == 0) {'YOUR REFRESH CODE'}
}
This is how you attach this listener to ScrollController:
late final _scrollController = ScrollController()..addListener(_onScroll);
IMPORTANT: Dont' forget to attach scrollController to ListView.

Related

Is it OK to add a listener in the build method in a Stateful widget?

I have a scrollcontroller which I need to add a listener to do some pagination functions on when the user scrolls down on a listview.
Currently, I create the scrollcontroller and a listener in the initState. I'm doing it there, because the scroll controller is actually a PrimaryScrollController and it needs context
var _scrollController = PrimaryScrollController.of(context);
Now I've run into a problem where when my page gets rebuilt for one reason or another the listview will jump to the top.
From what I understand its because on a rebuild, everything is getting rebuild however the initState isn't being run.
SO my solution is to move the scrollcontroller creation into the build method, which seems to be working just fine. However, the listener does not work, unless I also move it into the build method.
Is this ok? Or am I creating potentially many parallel listeners which can increase each time the page gets rebuilt?
If you are looking to listen to the scroll position and do some operations based on the scroll offset you can try a builder named valueListenableBuilder
ValueListenableBuilder<int>(
builder: (BuildContext context, int value, Widget? child) {
return Row(
children: <Widget>[
Text('$value'),
child!,
],
);
},
valueListenable: scrollController.offset,
child: Container(),
)

FLUTTER: PageView.builder onPage property - can i change so the index is triggered upon a FULL page swipe instead of 0.50

Using PageView.builder in Flutter, I have made a basic quiz with random repopulating questions, and some animation that I want to trigger every time a new page comes into view.
Also, of relevance, the user can only proceed to the next page once they have answered a question on each page. (Hence to control swiping, I created a ternary operator on the physics property where the onScroll boolean is set to false when the onPage method is triggered, (and changed back later by the user to true when they answer a question by pressing a button)).
So, everything is working well, except one annoying sideeffect:
The onPageChange property is "Called whenever the page in the center of the viewport changes." and the animation set for the new page, will play on the previous page as it dissapears from view, as the onState method needed for the onScroll boolean is triggered half way during the scroll causing a rebuild.
A simple fix would be if I could change the onPageChange property to trigger only once a FULL page swipe has been completed...but I cant work out how to achieve this..?
Thank you!
#override
Widget build(BuildContext context) {
return Material(
child: PageView.builder(
onPageChanged: (int i) {
print('page changed! + i');
setState(() {_canScroll = false;});
},
physics: _canScroll
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
pageSnapping: true,
itemBuilder: (context, index) {
_index = index;
return WordCardWidget(
word1: _word1,
word2: _word2,
answer: answer,
animationType: _cardAnimType,
buttonSelected: buttonSelected,
);
},
));
}
}
Try using mounted property before triggering setState().
This way it will ensure widget is completely mounted before changing the state.
Leaving this up, in case it helps any future readers.
I got this to work by using itemCount property and incrementing with a local variable, hence I dont need to use the setState method on the onPage property. The users can progress through each page only after attemping to answer a question which increases the itemCount by one.
(I have amended my original post where I wrongly thought this didnt work).
Here is the relevant working code snippet for reference :)
child: PageView.builder(
itemCount: itemCount,
onPageChanged: (int i) {
print('page changed! + i');
},

Jumping to a new page in PageView.builder after the additon of a new page doesn't seem to work as expected [Flutter]

I have a simple layout that consists of a PageView.builder widget that contains several pages. The contents of each page are simple, they contain a Container and a Text widget inside of it.
cards is a List of type String and the Pageview.builder widget has an itemCount that's based on the length of this List. The value of the Text in each page is assigned using this List.
List<String> cards = [];
Now, whenever I add a new value to cards List, a variable newPage is used to store the last index position in the List after that element has been added.
After doing this, setState(() {}); is called so that the UI along with the PageView update to reflect the changes made in the List.
The PageView widget does reflect the changes and a new page does get added to it.
However, the problem arises when I try to jump to newly added page right after calling setState.
The error indicates that the index value that jumpToPage is trying to use is out of range in the PageView
cards.add("New card");
newPage = cards.length - 1;
setState(() {});
card_PageController.jumpToPage(newPage);
So, after trying to figure something out, I added a Timer after the setState, so that the framework get's some time to properly update the UI elements.
I'm using a small value of 50 milliseconds in the Timer function and jumping to the new page after the timer gets over.
cards.add("New card");
newPage = cards.length - 1;
setState(() {});
Timer(Duration(milliseconds: 50), () {
card_PageController.jumpToPage(newPage);
})
The addition of a Timer seems to solve the problem and there were no errors after it's addition. However, I'm not sure if this is the right way of tackling this problem.
I'd like to know as to why is this happening, as shouldn't calling jumpToPage directly after setState work without the use of a Timer?
Also, does setState infact take some time to finish updating the UI, even though it isn't async, and that due to this reason the referenced index is invalid? And could this problem have been tackled in a better way?
From the code you shared I can't detect an issue. Especially because I've reproduced what you said you wanted to achieve and it works without issue. Take a look at the code below:
class PageCards extends StatefulWidget {
#override
_PageCardsState createState() => _PageCardsState();
}
class _PageCardsState extends State<PageCards> {
PageController _pageController = PageController();
List<String> cards = [
'page 0',
'page 1',
'page 2',
];
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: cards.length,
itemBuilder: (context, index){
return Center(
child: Text(cards[index]),
);
}
),
),
RaisedButton(
child: Text('Add page'),
onPressed: () => addCard(),
),
],
);
}
void addCard(){
setState(() {
cards.add('page ${cards.length}');
});
_pageController.jumpToPage(cards.length - 1);
}
}

How to scroll to an index by its index number in flutter listview?

I have a listview . I want to go to a certain widget by it's corresponding index number.
I've tried with ScrollController, but it need offset value to jumpTo the index. But I want to jumpTo a widget by using its index number.
Please, help me to fix it
Thanks in advance.
Unfortunately, ListView has no built-in approach to a scrollToIndex() function. You’ll have to develop your own way to measure to that element’s offset for animateTo() or jumpTo(), or you can search through suggested solutions/plugins from other posts such as:
flutter ListView scroll to index not available
Flutter: Scrolling to a widget in ListView
(the general scrollToIndex issue is discussed at flutter/issues/12319 since 2017, but still with no current plans)
But there is a different kind of ListView that does support scrollToIndex:
ScrollablePositionedList
scrollable_positioned_list
dependency: flutter_widgets
You set it up exactly like ListView and works the same, except you now have access to a ItemScrollController that does:
jumpTo({index, alignment})
scrollTo({index, alignment, duration, curve})
Simplified example:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
(note that this library is developed by Google but not by the core Flutter team.)

Why Flutter ListTile onTap seems cached?

I have two listview screen on my app. User can navigate between them using BottomNavigationBar control.
On listview.builder function, I return something like this
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('...'),
subtitle: Text('...'),
onTap: () {
...
}
)
});
I found onTap handler seems mixed between those 2 listviews.
When I open first list view, flutter serve the correct onTap,but when I switch to second listview, flutter still serving the first listview onTap.
Seems the onTap is cached by flutter (title & subtitle seems okay). Any idea?
Sample source code: https://github.com/jazarja/flutter_app
The problem is in your compararing transition values. Because you are first reversing the animation transition of current view.
In your botton_nav.dart, change this:
return aValue.compareTo(bValue);
to this:
return bValue.compareTo(aValue);
Yes the problem is that the animations haven't completed by the time the comparison is being done so the widget at the top of the stack is always 1 tab selection behind. You actually don't need to build a stack at all, just replace Center(child: _buildTransitionStack()) here with _navigationViews[_currentIndex].transition(_type, context) and it should work.