I have a custom widget which 90% of the times is only one single widget but depending on some conditions it can be 2 or 3 which should fit vertically.
#override
Widget build(BuildContext context)
{
List<Widget> children = [];
// Main widget
children.add(widget.child);
// Condition
if (condition1)
children.add(otherWidget);
// Condition
if (condition2)
children.add(anotherWidget);
return Column(children: children);
}
Since 90% of the time it is only a single widget, should I remove the column?
return children.length > 1 ? Column(children: children) : children.first;
I haven't seen that logic anywhere. What is more common is keeping the column and using the conditions inside:
#override
Widget build(BuildContext context)
{
return Column(children: [
widget.child,
if (condition1)
otherWidget,
if (condition2)
anotherWidget,
]);
}
I prefer the latter, but should I bother with the fact there will be most likely an unnecessary column build?
(btw this widget is a sample child for a ListView with lots of children and inside it there will be stateful widgets which need to keep their state.)
I'd recommend you to use the Column and widgets within wrapped inside a visibility widget (so you don't need an extra widget if not necessary)
Column(
children: [
child,
Visibility(
visible: condition,
child: anotherWidget,
)
Visibility(
visible: condition,
child: anotherWidget,
)
]
)
Related
I am creating a form (not using the Form Widget) in Flutter where the user can add an arbitrary amount of items (treatments) which are rendered as InputChip widgets list in a Wrap widget.
The form uses a button (AddButton widget) which opens a form dialog which itself returns the newly created item (treatment) that is added to selectedItems:
class TreatmentsWidget extends StatefulWidget {
const TreatmentsWidget({super.key, required this.selectedItems});
final List<Treatment> selectedItems;
#override
State<TreatmentsWidget> createState() => _TreatmentsWidgetState();
}
class _TreatmentsWidgetState extends State<TreatmentsWidget> {
#override
Widget build(BuildContext context) {
var chips = widget.selectedItems.map(
(item) {
return InputChip(
label: Text('${item.name} - ${item.frequency}/${item.frequencyUnit.name})',
);
},
).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
children: chips,
),
AddButton(onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return const TreatmentDialog();
}).then((value) {
if (value != null) {
Treatment item = value;
setState(() {
widget.selectedItems.add(item);
});
}
});
}),
],
);
}
}
For some reason, when a new item is added to selectedItem and that the item overflows the current line, the layout is not recomputed such that the Wrap widget overflows the button:
However, as soon as the user scroll (the whole screen content is inside a SingleChildScrollView), the layout is recomputed and the Wrap takes the right amount of space:
How can I force a redraw when a new item is added to prevent this glitch?
The issue seems to be that the Column does not recompute its size on the current frame when one of his child size changes.
I ended up forcing rebuilding the Column using a ValueKey whenever chips.length changes:
class _TreatmentsWidgetState extends State<TreatmentsWidget> {
#override
Widget build(BuildContext context) {
var chips = ...;
return Column(
key: ValueKey(chips.length),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
children: chips,
),
AddButton(...),
],
);
}
}
I am still interested in a cleaner solution if it exists. Using a provider seems overkill for this case.
I am looking for a way to build a widget that creates a scroll controller and contains several widgets that all depend on that scroll controller.
Instead of rendering the whole widget I want to place the widgets that it contains inside a parent widget in their corresponding places whilst still connected to that scroll controller.
EDIT//
In laymen's terms, I want to declare a widget wherever I happen to declare it but not actually render it there. It should render instead in a targeted location but still maintain any state or controllers it obtained or inherited from the position in which it was actually declare. IF this can be done it is probably far simpler than what I have tried to outline here.
//
// Parent Widget
class ParentReceiver extends StatelessWidget {
const ParentReceiver({super.key});
Widget build(BuildContext context){
return Stack(
children: [
Align(
alignment: Alignment.topCenter,
child: // expects to receive InjectWidget's MyHeaderWidget() here.
),
// expects to receive InjectWidget's MyContentWidget() here.
Align(
alignment: Alignment.bottomCenter,
child: // expects to receive InjectWidget's MyFooterWidget() here.
),
]
);
}
}
class InjectWidget extends StatelessWidget {
const InjectWidget({super.key});
Widget build(BuildContext context){
ScrollController scrollController = useScrollController();
// yes that's a hook but it doesn't matter how I got the scroll controller.
return Stack(
children: [
MyHeaderWidget(scrollController: scrollController);
MyContentWidget(scrollController: scrollController);
MyFooterWidget(scrollController: scrollController);
],
);
}
}
class MyContentWidget extends StatelessWidget {
const MyContentWidget({super.key, required scrollController,});
final ScrollController scrollController;
Widget build(BuildContext context){
return ListView(
controller: scrollController,
children: [
etc...
]
);
}
}
So MyHeaderWidget, MyFooterWidget and MyContentWidget all used the scroll controller that was created by InjectWidget. However, InjectWidget is not to render directly. These three individual widgets with their scroll controller intact should be placed into the corresponding areas of the ParentReceiver Widget.
The basic premise behind this is to build entire widgets that respond to controllers but place various component parts of them into the parent view.
Summing up the situation, I'm making a simple App in Flutter, which displays a List of Items you've added (I won't detail the app, as it would be unnecessary).
The file I created (log.dart) has a Property Class
class LogItem { /* code and stuff inside */ }
And it has a List with items
List<LogItem> itemsList = [test01, test02];
I created a simple Widget to display data for each item in this List
class SpecificItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Text(
" R\$ ${itensList[i].price}",
),
Spacer(),
Text(
"${itensList[i].title}",
),
],
),
);
}
}
Just below in another widget, I created a for loop to make a variable "i" change, to display different items from this list.
class LogGraphical extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
children: [
for (int item = 0; item < itensList.length; item++) SpecificItem(item),
],
),
);
}
}
Can someone explain to me exactly how I do the Widget ACCEPT PARAMETERS and change it? In this way creating multiple items?
I tried in many ways, but I could never get it to work!
(In the code I inserted here, I didn't put the Widget accepting anything.)
I have a method that returns a list of widgets.
List<Widget> _buildDrawerItems() {
return [...];
}
How to include the method inline like this?
#override
Widget build(BuildContext context) {
return Column(
children: [
...,
_buildDrawerItems(),
...
],
);
}
So far I've been using .addAll(). But I feel that's untidy.
#override
Widget build(BuildContext context) {
return Column(
children: []
..addAll([...])
..addAll(_buildDrawerItems())
..addAll([...])
);
}
I think the answer is literally just a small edit of your code. You can use the spread operator (...) like this, and it builds the list of widgets.
#override
Widget build(BuildContext context) {
return Column(
children: [
..._buildDrawerItems(),
],
);
}
I'm still trying to understand how to structure widgets. I have placed a container in the body already so how can I now add another row. I've removed some code to simplify my situation but hopefully this gives an idea of how my project is structured at the moment.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.bottomCenter,
//Stats Button
child: Row(
), //container
//How can I enter a new row here <------- WHERE I WANT TO ENTER A ROW
);
}
}
The short answer is, you cannot. You can take advantage of the children property of a Column, the most common layout widget in all of Flutter. Flutter works on a system of nested widgets, you cannot have many parents as it all starts with one widget.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.bottomCenter,
//Stats Button
child: Column(
children: <Widget>[
Row(
children: <Widget>[
// nested widgets
],
),
],
),
),
);
}
}
Judging by your class name, you just want a button. Not every widget starts with a Scaffold, that's only if you want an entire layout with an app bar or a bottom navigation bar. For simple widgets like a button, you can get rid of Scaffold entirely and just use MaterialButton like this.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(
onPressed: () {}, //empty function
);
}
}
More reading:
https://flutter.dev/docs/development/ui/layout
https://pusher.com/tutorials/flutter-building-layouts
https://medium.com/flutter-community/flutter-layout-cheat-sheet-5363348d037e