Ensure the visibility of the first item in a horizontal listview after prepending some items - flutter

I have a horizontal list view where I detect if the user has scrolled to the leftmost or rightmost edge and prepend, or append, new list items respectively.
The code structure is like so:
class WidgetState extends State<SomeWidget> {
// This list is prepended with some items when the user scrolls to the left edge
// (i.e when position.pixels = 0)
var _list;
// I use the scroll controller to detect if the user has hit the leftmost or rightmost edge
// of the list and trigger the logic that updates the above list
var _scrollController;
#override
Widget build(BuildContext context) {
ListView.separated(
itemBuilder: (context, index) {
return _list[ index ];
},
controller: _scrollController
);
}
}
The issue is that the new prepended items will push the old items outside of the visible view. What I would like to do is somehow preserve the scroll position such that the user still has to scroll to the left to see the now newly prepended items.
One idea I had is to use https://pub.dev/packages/scrollable_positioned_list and scroll to the index of the first old item in the list. However, I didn't want to add a whole new list implementation just for this. Is there a better way?

Related

Flutter scroll controller infinite scroll fail on larger screen size

I have a problem with infinite scroll in flutter, I am loading six items per page and loading more as I scroll , this works perfectly with a smaller screen size but the problem comes in when I use an emulator with a larger screen size, when the screen size is larger then the first six items don't fill the screen hence I cannot scroll in order to load the other items.
Does anybody have an idea of how I can get around this?
Thanks
You can use ListView.builder. The ListView.builder constructor takes an IndexedWidgetBuilder, which builds the children on demand. This constructor is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible.
return ListView.builder(
padding: const EdgeInsets.all(10.0),
itemBuilder: (context, i) {
if (i.isOdd) return const Divider();
final index = i ~/ 2;
if (index >= _listItems.length) {
_listItems.addAll(_newItems); //load more items here
}
return yourWidget(_listItems[index]);
},
);
This way no need to use scrollController. You can refer to this codelab for more info.

Custom Event listeners in flutter

I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list.  All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?  
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}

Flutter PopupMenuButton - don't close the menu when selected

I have a list of checkbox items (PopupMenuItem) inside my popup menu, which is triggered by a popupMenuButton. I want the user to be able to select a number of checkboxes, but as soon as one item is selected it closes the window.
Is there any way to prevent this? I need it to stay open, or alternatively force it open again immediately.
(I tried creating my own PopupItem class to override the "handleTap()", but I need to update the state of the parent menu view, which I can't call from another class. So Ive removed that again.)
class TopicsNotificationMenu extends StatefulWidget {
List<Topic> topics = [];
TopicsNotificationMenu(this.topics);
#override
_TopicsNotificationMenuState createState() =>
_TopicsNotificationMenuState();
}
class _TopicsNotificationMenuState extends State<TopicsNotificationMenu> {
_TopicsNotificationMenuState();
_updateTopics(_tp){
setState(() {
if(_tp.value == true){
_tp.value = false;
}else{
_tp.value = true;
_registerTopic(_tp.name);
}
});
}
#override
Widget build(BuildContext context) {
return PopupMenuButton(
onSelected: (value) {
_updateTopics(value);
},
itemBuilder: (BuildContext context) {
return widget.topics.map((var tp) {
var _icon = (tp.value == true) ? Icons.check_box : Icons.check_box_outline_blank;
return PopupMenuItem(
value: tp,
child: ListTile(
leading: Icon(_icon),
title: Text(tp.name),
),
);
}).toList();
});
}
You can try to reopen it each time after the user's selection. I made an example here
Alternatively, I would advise creating your own widget with the desired behaviour.
I had to create my own widget for this. In summary, I wanted a floating list in the top-right of my screen with a list of checkboxed items. When I press the items they become checked/unchecked but the window remains open until I click off it. I used the following widget tree:
An OverlayEntry widget so that I could place it anywhere floating above the app
-> added the SafeArea widget so that my padding would include the notification bar at the top of the phone
-> a Gesture Detector, so that on tapping off it I could close it
-> a column with CrossAxisAlignment.end so that it was placed in the top-right
-> a container widget with some padding
-> a Material for elevation shading and to contain a list
-> The Listview
-> The List Tiles with icon and Text for each item. The icon was either the ticked or unticked graphic, depending on it's value (which is stored as a boolean in an array)
onTap of a ListTile it updated the state of the widget and dispayed it again. There is no visual blinking, it is instant.
onTap of the Gesture Detector it simply removes the widget.

How to know if list view is scrollable programatically

I am copying my question from here as it is the same question but for flutter
How do you know if your ListView has enough number of items so that it
can scroll?
For instance, If I have 5 items on my ListView all of it will be
displayed on a single screen. But if I have 7 or more, my ListView
begins to scroll. How do I know if my List can scroll
programmatically?
Thank you
I am adding the code I tried, in which I test if the controller is attached, to be able to get the position. I couldn't get the position because the controller is not attached until you actually scroll
Widget build(BuildContext context) {
_afterBuild();
ListView.builder(
controller: controller,
// ...
)
}
Future<void> _afterBuild () async {
if (controller.hasClients) {
print("controller.hasClients");
// here I would be able to get the position
} else {
print("controller.has no Clients");
}
}
Edit: For anyone coming here: The controller was not being attached because I had a condition under which to build the ListView
So I combined the comments with the accepted answer (which is actually the answer for the question) and solved it like this (with some pseudocode):
Widget build(BuildContext context) {
if (not loaded results from api) {
return Something()
} else {
Future(_afterBuild);
return ListView.builder(
controller: controller,
// ...
)
}
}
Future<void> _afterBuild () async {
if (controller.hasClients) {
if(controller.position.maxScrollExtent > 0){
print('it can be scrolled');
}
} else {
print("controller has no client");
}
}
Actually it's quite easy to do in Flutter. You should have a ScrollController attached to your ListView and then you can check the maxScrollExtent. If it's bigger than zero then your ListView can be scrolled. It also works for any scrolling view which uses ScrollController.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if(controller.position.maxScrollExtent > 0){
print('it can be scrolled');
}
});
}
Step 1 - Assign a GlobalKey
GlobalKey myKey= GlobalKey();
Step 2 - Assign key to ListView
ListView(
key: myKey,
...
)
Step 3 - In your function that checks if the ListView is scrollable, use the following code-
final RenderBox renderBoxRed = myKey.currentContext.findRenderObject();
final height = renderBoxRed.size.height; // find height of ListView
if (height > MediaQuery.of(context).size.height) { // checking if listview height is greater than page height
print("ListView is SCROLLABLE !!!");
}
I like where most of the rest of the answers are going, but they aren't getting the data the most succinctly or reliably. What you want to do is, yes, attach a ScrollController, and then look for the .position on that (which may throw if there's no single attached position). Then, ask the position for extentAfter and extentBefore, and you'll discover how much virtual view (in pixels) there is after and before the current visible portion. If those are 0, no scrolling. No need to figure out the size of the screen or the containers. Flutter already knows all that when it laid everything out!

Animations in List with async data provided by a Stream

I have a Firestore collection with 4 different documents. In my app I have a list that shows every document in a ListView. I get the documents through a Stream. Users can delete, move and add items. When a user moves an item I want it to animate the way that the item moves above/below other items (the moving system is made through up votes and down votes, the most upvoted items being at the top, while the most downvoted being at the bottom). When an item gets 1 upvote more that the item on top of it, it should animate the switch of the items. I know that animated list does not have any native support for this. How can I get to tell my list to do an animation when I get new data? Right now everything looks confusing and everything is jumping instantly because the normal ListView does not provide any animations for moving/inserting/removing data. Given that I work with async data and streams that process becomes harder. Any ideas on how I can detect changes in the stream and animate the current list to the new state of the list? Whether that means removing/inserting/modifying the positions of some items.
Use implicitly animated list
https://pub.dev/packages/implicitly_animated_reorderable_list
// Specify the generic type of the data in the list.
ImplicitlyAnimatedList<MyGenericType>(
// The current items in the list.
items: items,
// Called by the DiffUtil to decide whether two object represent the same item.
// For example, if your items have unique ids, this method should check their id equality.
areItemsTheSame: (a, b) => a.id == b.id,
// Called, as needed, to build list item widgets.
// List items are only built when they're scrolled into view.
itemBuilder: (context, animation, item, index) {
// Specifiy a transition to be used by the ImplicitlyAnimatedList.
// See the Transitions section on how to import this transition.
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: Text(item.name),
);
},
// An optional builder when an item was removed from the list.
// If not specified, the List uses the itemBuilder with
// the animation reversed.
removeItemBuilder: (context, animation, oldItem) {
return FadeTransition(
opacity: animation,
child: Text(oldItem.name),
);
},
);