Flutter List.generate but in reverse? - flutter

I have a container that I am trying to print out items form a list, minus the last item in the list. I am using list.generate:
child: Container(
width: double.infinity,
color: Colors.white,
child: ListView(
padding: const EdgeInsets.only(top: 10.0),
children: List.generate(
Provider.of<WeekList>(context).listOfWeeks.toList().length -
1,
(index) => ListTileCustom(
index: index,
),
),
),
),
My problem is when it prints out my custom ListTileCustom widget each new item added to the list is added to the bottom and not the top in the view.
In the picture above the order should be:
Red Bar
Red / Green Bar
Green bar
and when I add new items by pressing the button they should be added to the top not the bottom.
I tried adding reverse: true and that gets the order right but moves everything to the bottom and adds a ton of white space above them... Also not sure the scroll will work in the right direction at that point either.

To reverse a list in Dart, you can use ..reversed method. In the code you posted, that would be: children: List.generate(/* omitted */).reversed.toList().
Another (probably better) solution is to use for loop directly in children, for example:
ListView(
children: [
for (int i = 0; i < 5; i++) FlutterLogo(),
],
)
With this approach you can easily invert the list (and omit one element, like you desired) by changing the loop, e.g. for (int i = 10; i > 1; i--) or something.

Related

Reducing Jank in Flutter progressive web app

Woah! I've spent several hours refactoring nested ListViews to a parent CustomScrollView & child Slivers. The errors Slivers produced were opaque and frightening, with nothing illuminating in Logcat; sleuthing devoured much of the time.
Anyway, that's solved. I find I still have jank scrolling a 15-item list. OK, each item can involve further numerous widgets {Padding, Alignment, Elevated button, Row, Text, SizedBox, Icon}. So my 15-item list ends up being multiple more Widgets.
I've now swapped out my SliverChildListDelegate for SliverChildBuilderDelegates, so a Builder builds the Widget List lazily. Having done this, it seems quite inefficient because it's increased the Widgets in the Widget tree. Each of the builders' buildItem() calls needs an extra Column Widget to wrap that sub-chunk of the total list.
It may be a lot of Widgets scrolling but it's only a 15 item list. I wish I knew what to optimise. Any thoughts on how to best reduce jank on Lists for mobile web?
The Flutter team says Flutter works best for computational-centred apps rather than text heavy informational apps. In future would it be better just to use webView Widgets? I always thought embedding Webviews would be clunky and slow but Lists of native Flutter Widgets, even as SliverLists, give jank.
Here is the janky list complete with builder:
Widget buildLocationDescriptionWidgets(LocationDetails presentLocation) {
print(LOG + "buildLocationDescriptionWidgets");
if (presentLocation.descriptionLinkUrls.isEmpty)
return SliverToBoxAdapter(child:
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(presentLocation.description[0])));
int numDescriptionBlocks = presentLocation.description.length;
double paddingBottom = 16;
if (presentLocation.descriptionLinkUrls.length >= numDescriptionBlocks) {
paddingBottom = 0;
}
return SliverPadding(
padding: EdgeInsets.fromLTRB(16, 16, 16, paddingBottom), sliver:
SliverList(
key: Key("descriptionSliverList"),
delegate: SliverChildBuilderDelegate((context, index) =>
buildDescriptionBlock(context, index),
childCount: presentLocation.description.length
),
));
}
Column buildDescriptionBlock(BuildContext context, int index) {
List<Widget> colChildWidget = [];
colChildWidget.add(Text(
widget.presentLocation.description[index],
textAlign: TextAlign.left,
));
if (index < widget.presentLocation.descriptionLinkUrls.length) {
colChildWidget.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Align(
alignment: Alignment.center,
child: index >=
widget.presentLocation.descriptionButtonIcons.length
? ElevatedButton(
child: Text(
widget.presentLocation.descriptionButtonText[index]),
onPressed: () {
_launchURL(
widget.presentLocation.descriptionLinkUrls[index]);
})
: ElevatedButton(
child:
Row(mainAxisSize: MainAxisSize.min, children: [
Text(
widget.presentLocation.descriptionButtonText[index]),
SizedBox(width: 8),
FaIcon(
buttonIconMap[widget.presentLocation
.descriptionButtonIcons[index]],
size: 16)
]),
onPressed: () {
_launchURL(
widget.presentLocation.descriptionLinkUrls[index]);
}))));
}
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: colChildWidget);
}
Should I regress from a builder to a conventional SliverList?
Other things I've tried: I eliminated jank in my app Drawer list by putting const everywhere possible, lots of Dividers were made Const. But when you style text using Theme.of(context).textTheme.bodyText2 etc. it doesn't allow you to set textboxes to const. If you want to use const you can't style the app globally, you'd have to hard code. Is it worth forsaking abstraction for hard coding Text widget styles?
Here is the web app: Love Edinburgh
For the clearest example of jank
Open the App Drawer
Scroll to WONDER
Tap Arthur's Seat
Open the panel to full screen - slide it up
Scroll down the panel.
It doesn't show on a desktop browser which is compiled using Skia / Webkit. It's a bit fiddly to get scroll working on a desktop browser, you need to click amongst the text, then attempt to scroll. It's meant for mobile use so I'm resigned to that.
Not sure how to help you out. Would rather put this on a comment rather than answer but I don't have the points to do a comment.
Anyway, I wish I could replicate your problem. On my personal experience, for a 15 item list with numerous child widgets, it shouldn't be janky unless it has probably big sized images or really too much extra widgets.
On my case, I made sure to "isolate" / "compute" my heavy computations and showed a loading screen while preparing the list.
You may read on:
Isolates : https://dart.dev/guides/language/concurrency
Compute:
https://api.flutter.dev/flutter/foundation/compute-constant.html
Hope that helped!

Flexible and fixed size. Make children pass size to parent?

I am trying to make an interface that tries to do something very simple:
Prints a series of things in line and the last one is an scrollable (listview) that expands across all screen. It's making my life hell, however.
My problem is that the things it needs to print in line occupy don't adapt, I want them to "calculate and pass their size to the parent" so that the listview can be make to expand as much as possible WITHOUT eating the first elements on the screen or leaving them with unnecessary space.
Furthermore, the size of the firsts elements can change, they have a button that "dissects" into two.
This is more like my setup of widgets (and in parenthesis the class they are in):
Column(W1):
|Flexible(W1):
||Column(W2) <= Actually, I created a listview now because some problems.
|||conditional column(WX*) (1 children or 2)
|Flexible(W1)
||Divider(W3)
||Flexible
|||Layoutparamsbuilder(W3)
||||listviewbuilder(W4)
*This was in another widget, but there is no reason for it. There are 3 elems to show in screen, the first option shows 1, and the other option disables that one and shows the other 2, they are part of a state(changes while looking at it). I created a new column for simplicity (adding them programatically to the W2 column could be kind of a mess, but if doing so solves the issue i will do it)
I think the mistake is in between W1 and W2. But removing the "flexible" out of W1 makes a mess. It doesn't print anything. I need W2 to show all his shit and then back off, but instead it occupies half the screen.
Now, being in a single line is not a necessity, and I tried a Grid, thinking that I would solve all my problems. Is seemed perfect, but not at all! That's the only reason why W2 is a column.
Sorry for the long post. I made a shitty painting in pain showing ""my interface"":
Also, I have another tiny, unimportant question: The listview is meant for showing some sort of a "table" with several fields. An example: It shows (name, age, email). If the window expands horizontally, each field grows. Is there any widget that calculates this and, instead, adds a different field (name, age, email, phone) ? Tables not only don't do this, but they also don't scroll on their own, and my second option was to make a listview of listviews, making my real problem(the question) even worse xdddd.
Thank you very much, you are amazing people :).
I add the basic structure of the code code:
Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible( child : Widget1()),
Expanded (child: Widget2())
)],),
Widget1:
Container(
decoration: BoxDecoration( border: Border.all(color: Colors.green)),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Column(
children: [
Container(
alignment: Alignment.topLeft,
child: Text( ),
),
SizedBox(height: 5),
Consumer>> (ListView(children: textfiel, row, row)
])
Widger2:
Column(
children : [
Divider(), Row(), Divider(), Text(), Sizedbox(), Flexible(LayoutBuilder(Sizedbox(height: contrains.height, child: Listview(rows....)
if I understand you correctly, you need to use Expanded Widget and pass the ListView or whatever your Scrollable widget as a child.
Check out the links below:
https://api.flutter.dev/flutter/widgets/Expanded-class.html
https://www.youtube.com/watch?v=_rnZaagadyo
If you still have this problem, you can share your code to make the problem more clear.

Separate and show priority items on top of a ListView? (Pinned Item functionality)

How to add the 'pinned item' functionality, similar to Google Keep, on a ListView?
I tried doing 2 list view, and putting the pinned one on top of a Column. But the problem is they scrolled separately, and if I wrapped the column in a Singlechildscrollview, it just gives error.
Am I on the right track of doing it or there is a way by just using a single list view?
Implement if you want:
add an extra field to your object: bool pinned -> then you sort the list to make sure the pinned items are on top of the list
put a GestureDetector around the item and use the onTap to set the pinned value -> pinned = !pinned
Or try this package I just found:
https://pub.dev/packages/pinnable_listview
I think I got the Singlechildscrollview worked the way I want it to. I just have to set the ListView shrinkWrap: true, and physics: NeverScrollableScrollPhysics(). I'm not sure if this is a good idea tho, since most people say that shrinkWrap is quite a heavy operation to use.
SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Pinned'),
PinnedListItemHere(), //ListView of Pinned Items
Divider(
height: 25.0,
thickness: 2.0,
),
Text('Others'),
UnpinnedListItemHere(), //ListView of Unpinned Items
],
),
),

Flutter: How do I stretch the last Element in a Wrap Widget?

I think this doesn't need much explaining so I didn't include any code or screenshots.
As the title, suggests, I have a Wrap Widget and I want the last Element of it to take up all the available horizontal space to the right, (the same way as if you put an Expanded Widget in a Row).
I this case though, I can not use Expanded or Flexible, as Wrap doesn't allow that.
Edit, added a bit of (simplified) code and screenshots:
_buildTileDragTarget is just a function that returns a DragTarget.
void _buildAnswerWidgetsFromStrings() {
_answerWidgets.clear(); //start building from 0
_answerWidgets.add(_buildTileDragTarget(index: 0)); //add initial DragTarget
//for each word in _answerStrings, add Draggable Chip & DragTarget
for (int i = 0; i < _answerStrings.length; i++) {
WordTile wordTile = _answerStrings[i];
_answerWidgets.add(
Draggable<WordTile>(
key: UniqueKey(),
data: wordTile,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Chip(
label: Text(wordTile.word),
padding: EdgeInsets.all(0),
),
onTap: () {
//seems to work: tap from top to bottom (5)
setState(() {
_options[wordTile.word] = true;
_answerStrings.remove(wordTile);
});
},
),
feedback: Material(child: Chip(label: Text(wordTile.word))),
childWhenDragging: Chip(label: Text(' ' * wordTile.word.length)),
),
);
_answerWidgets.add(_buildTileDragTarget(index: i + 1)); //the position AFTER the current word (last)
}
//remove last DragTarget and add one wrapped in Expanded
_answerWidgets.removeLast();
_answerWidgets.add(
SizedBox(
// width: double.infinity,
child: _buildTileDragTarget(index: _answerStrings.length),
),
); //TODO try wrapping the whole thing in 1 big DragTarget (for last index)
// _answerWidgets.add(_buildTileDragTarget(index: _answerStrings.length));
}
Screenshots:
I wrapped the Wrap widget in a purple Container, each DragTarget is wrapped in a green Container. (you can ignore the blue part)
(1) This is what I have (without SizedBox)
(2) This is what I get when I try using SizedBox(width:double.infinity...)
(3) This is what I want
Try SizedBox(width: double.infinity)
There's also expand constructor you might use
SizedBox.Expand(
child: //your widget
)

flutter scollable.ensurevisible not working in reverse direction

Please check the video / gif:
I have a pageview that will make the current tab active. I need to ensure the active tab is always visible even if the user swipes the screen multiple times. It is working from the left to right. But when we try back from right to left it's not behaving as expected.
PageView file
PageView(
controller: pageController,
onPageChanged: (int page) {
_duaWidgetState.currentState.updateBtn(page + 1);
Scrollable.ensureVisible(
_duaWidgetState.currentState.activeBtn.currentContext);
},
children: loaded
TabBarWidget with scroll view file
GlobalKey activeBtn = GlobalKey();
var _selectedTab = 1;
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(widget.numberOfTab,
(index) => tabBarItem(index + 1, widget.numberOfTab, activeBtn)),
),
),
);
}
Container that uses the Key
Container(
key: activeBtn,
margin: EdgeInsets.only(left: 20),
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 24,
),
No One answered my Question. But, after a lot of effort and time. I found a workaround to fix this problem. Hope this is a bug and will fix by the flutter team in the coming updates.
Scrollable.ensureVisible(_duaWidgetState.currentState.activeBtn.currentContext);
This function has an optional argument called alignment which can be adjusted to make sure the selected element/button is in the center of the viewport. (In my case horizontally center).
My widgets are horizontally scrollable, so I need to update the page number according to the result from PageView(), Here when I used
alignment: 0
Its working fine from left to right swipes of page view. But with this alignment when i swipe page from right to left to go to previous page in the PageView widget, the ensureVisible is not working like expected. The selected element/button is out of the view. I experimented and found that when I used
aligment: 1
The ensureVisible is working fine from the swipes from right to left but at the same time. when I scroll left to right the same problem occured.
Finally, so I managed to do some workaround to fix this behavior. The solution is to store last page index in a variable and check whether the last page is greater than the new page then alignment: 0 and if the last page less than the new page aligment:1 like that.
PageView(
onPageChanged: (int page) {
if (lastPage < page) {
Scrollable.ensureVisible(
_duaWidgetState.currentState.activeBtn.currentContext,
alignment: -0.0100,
);
} else {
Scrollable.ensureVisible(
_duaWidgetState.currentState.activeBtn.currentContext,
(Perfect For font size 24)
alignment: 1.005,
);
}}
Hope my answer will help someone in the future.
I modified the original ensureVisible() a bit and this works in both directions in my case:
// `contentAnchor` is the global key of the widget you want to scroll to
final context = contentAnchor.currentContext!;
final ScrollableState scrollable = Scrollable.of(context)!;
scrollable.position.ensureVisible(
context.findRenderObject()!,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);