How to add the widgets dynamically to column in Flutter? - flutter

I have added a few widgets inside the ListView. So that I can scroll all widgets. Now I required to add one more widget to the ListView to load the list of comments. I can not add the ListView inside the ListView. And moreover, I do not require the separate scroll for my comments. It should be scroll along with the widgets inside the ListView. So I planned to add the Column instead of ListView. Could any help to add my comments dynamically in the Columns?
new Expanded(
child:
new ListView(
shrinkWrap: true,
children: <Widget>[
// Title
new Padding(padding: const EdgeInsets.only(
top: 10.00, left: 10.00),
child: new Text(
_feed.title, textAlign: TextAlign.start,),
),
// content
new Container(
child: new Text(
_feed.content, textAlign: TextAlign.start,),
),
// Comments List will go here
],
),
),

If you have the comments data already, simply create a List, then pass it to the children property of the Column. Something like:
var commentWidgets = List<Widget>();
for (var comment in comments) {
commentWidgets.Add(Text(comment.text)); // TODO: Whatever layout you need for each widget.
}
…
new Expanded(
child:
new ListView(
shrinkWrap: true,
children: <Widget>[
// Title
new Padding(padding: const EdgeInsets.only(
top: 10.00, left: 10.00),
child: new Text(
_feed.title, textAlign: TextAlign.start,),
),
// content
new Container(
child: new Text(
_feed.content, textAlign: TextAlign.start,),
),
// Comments List will go here
Column(children: commentWidgets,),
],
),
),
If you don't have the comments data already and need to fetch it, use a FutureBuilder to build the UI once the future completes.

Another way:
return Column(
children: [
for(String item in list) Text(item);
]);
You can also mix static and dynamic fields eaisly in this case:
return Column(
children: [
for(String item in list) Text(item),
Text("Static text control"),
]);

By maintaining a reference to the Column object, the .children field/property can be referenced after the Column object has been declared - like so:
Column someColumn = Column(
children: [],
);
someColumn.children.add(Text('Hello 123'));

Currently(2021-06), Flutter 2.x implemented null-safe.
The answer from #Gene Bo should work but needs little modification.
This should work.
var pwdWidgets = <Widget>[];
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: this.pwdWidgets,
),
ElevatedButton(
onPressed: (){
setState((){
this.pwdWidgets.add(Text("Hello World"),);
});
},
child: Text("click me"),
),

In Addition to #Derek Lakin's Answer which worked for me too, it is also required to call setState(), if you need to update the comments and reload.
var commentWidgets = List<Widget>();
for (var comment in comments) {
commentWidgets.Add(Text(comment.text)); // TODO: Whatever layout you need foreach widget.
}
setState(() {
});

import 'package:flutter/material.dart';
class LoginRegisterPage extends StatefulWidget {
#override
_LoginRegisterPageState createState() => _LoginRegisterPageState();
}
class _LoginRegisterPageState extends State<LoginRegisterPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("App"),
),
body: new Container(
margin: EdgeInsets.all(15.0),
child: new Form(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: createInputs() + createButtons(),
),
),
),
);
}
List<Widget> createInputs(){
return{
SizedBox(height: 10.0),
logo(),
SizedBox(height: 20.0),
new TextFormField(
decoration: new InputDecoration(labelText: 'Email'),
),
SizedBox(height: 10.0),
new TextFormField(
decoration: new InputDecoration(labelText: 'Passwors')
),
SizedBox(height: 20.0),
};
}
Widget logo(){
return new Hero(
tag:'hero',
child: new CircleAvatar(
backgroundColor:Colors.transparent,
radius: 110.0,
child: Image.asset("images\logo.PNG"),
),
);
}
List<Widget> createButtons(){
return{
new RaisedButton(
child: new Text("Login", style: new TextStyle(fontSize: 20.0),),
textColor: Colors.pink,
onPressed: () {
},
),
new FlatButton(
child: new Text("Already not have an account?", style: new TextStyle(fontSize: 14.0),),
textColor: Colors.white,
onPressed: () {
},
)
};
}
}

Yes you can add dynamic child on column widgets.
class CategoriesItemWidget extends StatelessWidget {
final List<String> list;
const CategoriesItemWidget({Key? key, required this.list})
: super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: list
.map((string) => Text(string))
.toList(),
);
}
}

Related

How to fix "`RenderFlex children have non-zero flex but incoming height constraints are unbounded." in Flutter?

I'm very new to Flutter and frontend design in general. I've tried looking for the answer online and have tried some of the suggestions on other posts, but they don't match my situation exactly and I keep getting confused. If anyone could offer some guidance I would really appreciate it!
I'm trying to make a custom table widget composed of a title, ListView, and a row of IconButtons. I'm having trouble wrapping my head around how to limit to and fit to containers. I keep getting a error stating RenderFlex children have non-zero flex but incoming height constraints are unbounded. I know it has something to do with the boundaries and I need to use either Flexible or Expanded to fix it, but I've been at it for a while and am not getting anywhere.
#override
Widget build(BuildContext context) {
return Focus(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.tertiary)),
padding: const EdgeInsets.fromLTRB(0, 0, 0, 2),
margin: const EdgeInsets.fromLTRB(13, 2, 13, 2),
clipBehavior: Clip.antiAlias,
child: Column(children: [
Text(
widget.title,
style: Theme.of(context).textTheme.bodyLarge,
),
ListView(
shrinkWrap: true,
children: widget.children,
),
Flexible(
child: Row(
children: [
//PLUS BUTTON
Expanded(
child: IconButton(
onPressed: () {
setState(() {
// updating the state
widget.children.add(ReportInputTableRow(
rowIndex: widget.children.isNotEmpty
? widget.children.length - 1
: 0,
onFocus: (row, column) {
updateCurrent(row, column);
},
));
});
},
icon: Icon(
Icons.plus_one_sharp,
color: Theme.of(context).colorScheme.secondary,
),
splashRadius: 15.0,
),
)
//PLUS BUTTON
],
))
]),
));
}
EDIT:
As requested, here is the code for ReportTableInputRow
class _ReportInputTableRowState extends State<ReportInputTableRow> {
#override
Widget build(BuildContext context) {
return Focus(
child: Row(
children: [
Focus(
child: const Expanded(
child: TextInputField(
text: "Original",
size: 13,
padded: false,
),
),
onFocusChange: (hasFocus) {
if (hasFocus) widget.columnIndex = 0;
}),
Focus(
child: const Expanded(
child: TextInputField(
text: "Note",
size: 13,
padded: false,
)),
onFocusChange: (hasFocus) {
if (hasFocus) widget.columnIndex = 1;
}),
],
),
onFocusChange: (hasFocus) {
widget.onFocus != null
? widget.onFocus!(widget.rowIndex, widget.columnIndex)
: null;
},
);
}
}
EDIT:
The solution was to swap Expanded with Focus in ReportInputTableRowState.
Maybe this one can help you:
Put the ListView inside Expanded and remove the Flexible which wraps the Row beneath the ListView.
child: Column(children: [
Text(
widget.title,
style: Theme.of(context).textTheme.bodyLarge,
),
ListView(
shrinkWrap: true,
children: children,
),
Flexible(
child: Row(
children: [
//PLUS BUTTON
Expanded(
child: IconButton(
onPressed: () {
...
You should also avoid changing widget variables, because they are considered to be immutable. If you want to mutate variables, put them inside the state.
Instead of widget.children.add(...) you should call children.add(...) with children being a state variable.
Actually you may not need Expanded or Flex, since you icons should have a defined size, you can wrap it using a SizedBox with defined height.
SizedBox(
height: 32, // It can be the height you desire, no need to define width
child: Row(
children: [
... // The icons that are children of the row go here
],
),
),

How to not load the children of a ExpansionTile

I have the follow structure: A list of Expansion tiles > clicking on it, opens another list of ExpansionTiles > Clicking in one of them, it should open some widgets according to a SQL query.
The problem is, when I tap in the first Expansion Tile it loads all the widgets from all the Expansion Tiles inside the first option making the query very slow. I want to only load the widgets when I tap in the second one (loading only the necessary ones)
Here is the code:
1st list:
class ListItemsScreen extends StatefulWidget {
#override
_ListItemsScreenState createState() => _ListItemsScreenState();
}
class _ListItemsScreenState extends State<ListItemsScreen> {
final Widget appBar = AppBar(
title: Text('ITEMS'),
actions: [
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.shopping_bag_outlined),
onPressed: () {
Navigator.of(context)
.pushNamed(ROUTE_CHART);
},
),
),
],
);
#override
Widget build(BuildContext context) {
final List items = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: appBar,
body: items == null || items.isEmpty ?
Center(child: Text("0 items here"),)
:
ListView(
children: [
...items.map<Widget>(
(item) {
return ExpansionTile(
leading: Image.asset(ASSET_IMAGE,
fit: BoxFit.cover
),
title: Text('${item.code} | ${item.description}'),
subtitle:
Text('${item.color}'),
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ProductWidget(item),
),
],
),
);
},
)
],
)
);
2nd list (ProductWidget):
class ProductWidget extends StatefulWidget {
final Product product;
ProductWidget(this.produto);
#override
_ProductWidgetState createState() => _ProductWidgetState();
}
class _ProdutoGradeWidgetState extends State<ProdutoGradeWidget> {
#override
Widget build(BuildContext context) {
CustomScrollView(
slivers: [
StreamBuilder(
stream: product.stream,
builder: (ctx, snapshot) {
return SliverList(
delegate: SliverChildBuilderDelegate((ctx, i) {
if (i == 0) {
return Column(
children: [
Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'I HAVE THIS PRODUCT IN THESE COLORS',
style: TextStyle(
fontSize: 20,
color:
Theme.of(context).textTheme.caption.color,
),
)
),
),
const SizedBox(height: 20.0),
ProductColorsWidget(color: snapshot.data[i]),
],
);
} else if (i == snapshot.data.length - 1) {
return Column(
children: [
ProductColorsWidget(color: snapshot.data[i]),
const SizedBox(height: 20.0),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Qtd',
style: TextStyle(
fontSize: 16,
color:
Theme.of(context).textTheme.caption.color,
),
),
),
),
const SizedBox(height: 20.0),
],
);
}
return ProductColorsWidget(color: snapshot.data[i]);
}, childCount: snapshot.data.length),
);
}
},
),
],
);
}
}
}
}
3rd part (Product Colors Widget where I list the second Expansion Tiles):
class ProductColorsWidget extends StatelessWidget {
final ColorProduct color;
ProdutoCorGradeWidget({this.color});
#override
Widget build(BuildContext context) {
return ExpansionTile(
maintainState: true,
tilePadding: EdgeInsets.all(15.0),
title: Text(
'${color.id} - ${color.description}',
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontWeight: FontWeight.w600),
),
childrenPadding: EdgeInsets.all(10.0),
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...color.sizes.map<Widget>(
(item) {
return Column(
children: [
Expanded(
child: Text(
item.description, textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
)
),
...item.prices.map<Widget>((size) {
return PricesWidget( //here it should show the widgets according to the second ExpansionTiles
color: color,
size: size
);
})
]
);
}
)
],
)
)
],
);
}
}
So, to be clear, what I want is: First It lists the products (with expansionTiles), expanding one it should show the second Tiles (with sizes) and after selecting one it should show the widgets.
..But what is happening now is: List the products and when I select one the app loads all the widget from all the second 'expansionTiles' making it slow to show the second list. What should I do?
I think the problem is with
ExpansionTile(
maintainState: true,
.....
)
I had a similar issue in which I had an ExpansionTile that was its own child and it caused a stack overflow because it was building them all. After setting maintainState to false the problem was solved.
You might have to adapt your state management according to that the children state may not be saved

Set State doesn't change the value just if hot reload the page

I have the following situation
Column(
children: [
Tabs(),
getPage(),
],
),
the getPage method
Widget getPage() {
if (tab1IsSelected == true) {
return Container(
child: Center(
child: Text('Tab1'),
),
);
}
if (tab1IsSelected == false) {
return Container(
child: Center(
child: Text('Tab2'),
),
);
}
}
and globally I have declared a variable
bool tab1IsSelected = true;
In the Tabs Class (statefull):
class Tabs extends StatefulWidget {
#override
_TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
tab1IsSelected = true;
});
},
child: Container(
decoration: BoxDecoration(
color: tab1IsSelected ? primary : second,
),
child: Padding(
padding:
EdgeInsets.only(top: 1.5 * SizeConfig.heightMultiplier),
child: Center(
child: Text(
'New Hunt',
style: Theme.of(context).textTheme.bodyText1,
),
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
tab1IsSelected = false;
});
},
child: Container(
decoration: BoxDecoration(
color: tab1IsSelected ? second : primary,
),
child: Padding(
padding:
EdgeInsets.only(top: 1.5 * SizeConfig.heightMultiplier),
child: Center(
child: Text(
'My Hunts',
style: Theme.of(context).textTheme.bodyText2,
),
),
),
),
),
),
],
);
}
}
I change the value of that bool, but only if I hot reload the page the content is changing. Why?
Can you guide me please?
I've tried to use ? : in that Column but the same result and if I declare that variable in the Main Class where the Column is, I can't access it in the Tabs class, so that's why I declared it globally, maybe that's the cause I have to hot reload, but how can I implement that to do what I want. Thank you in advance
setState is inside _TabsState so it will only affect/rebuilt that particular widget, not getPage(), you could try using ValueChanged<bool> to retrieve the new value and then using setState in the widget that wraps the getPage()
class Tabs extends StatefulWidget {
final ValueChanged<bool> onChanged;
Tabs({this.onChanged});
#override
_TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => widget.onChanged(true), //pass the value to the onChanged
child: Container(
decoration: BoxDecoration(
color: tab1IsSelected ? primary : second,
),
child: Padding(
padding:
EdgeInsets.only(top: 1.5 * SizeConfig.heightMultiplier),
child: Center(
child: Text(
'New Hunt',
style: Theme.of(context).textTheme.bodyText1,
),
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () => widget.onChanged(false), //pass the value to the onChanged
child: Container(
decoration: BoxDecoration(
color: tab1IsSelected ? second : primary,
),
child: Padding(
padding:
EdgeInsets.only(top: 1.5 * SizeConfig.heightMultiplier),
child: Center(
child: Text(
'My Hunts',
style: Theme.of(context).textTheme.bodyText2,
),
),
),
),
),
),
],
);
}
}
Now on the widget with the column (That should be a StatefulWidget for setState to work)
Column(
children: [
Tabs(
onChanged: (bool value) => setState(() => tab1IsSelected = value);
),
getPage(),
],
),
everytime you change the value of tab1IsSelected it will update getPage()
If you want to rebuild a widget when something in its state changes you need to call the setState() of the widget.
The variable is referenced to the State class and when you call setState() Flutter will rebuild the widget itself by calling the build() method of the State class.
If you want to have some variables outside the widgets I suggest you to use a state management approach listed here: https://flutter.dev/docs/development/data-and-backend/state-mgmt/options.
For example you could use Provider to store the active tab and reference the provider variable in both widgets.
You can try to handle the setstate in the parent class holding the Tab Widget then pass a the function to tab class and execute it in the gesture detector.

Flutter: Card widget with Dropdown

When I click on my card widget, I want it to be dropdown according to my upcoming data. I have text and icon on my card. I added Inkwell to be clickable. but as I click, I want my coming data to be added down. Is this possible?
child: Card(
child: InkWell(
onTap: () { });
},
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: Icon(
Icons.favorite,
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: Text(
snapshot.text,
style: TextStyle(fontSize: 16),
),
),
],
),
),
),
I have a list like this,
and when an item is clicked, I want related subtext as below. For example, I want 2 companies related to the country to come. Like the card open. (Sorry for the bad visuals. i did it quickly:))
It is indeed possible, the whole idea is based on this theory:
Your country card and it's associated company card should be a different StatefulWidget, which will be controlled by the boolean
Please note: I have given a very basic representation of the view, you can play with the UI and make it look like your own desired UI
This is your card view should look like:
class CardWidget extends StatefulWidget {
// right now it only accepts title, but you can add more
// arguments to be accepted by this widget
CardWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_CardWidgetState createState() => _CardWidgetState();
}
class _CardWidgetState extends State<CardWidget> {
// responsible for toggle
bool _showData = false;
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10.0),
// list card containing country name
GestureDetector(
onTap: (){
setState(() => _showData = !_showData);
},
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.grey, offset: Offset(0.0, 3.0))]
),
child: Padding(
padding: EdgeInsets.all(15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// add your other icon here
Text(widget.title)
]
)
)
)
),
// this is the company card which is toggling based upon the bool
_showData ? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: ['Kia','Samsung'].map((e){
// make changes in the UI here for your company card
return Card(child: Text(e));
}).toList()
) : SizedBox() // else blank
]
);
}
}
Here how I am calling the CardWidget in the main UI
// this is dummy data, you can play with the data and the UI
Column(
children: ['Korea', 'China', 'Japan', 'USA', 'India'].map((country){
// returning the CardWidget passing only title
return CardWidget(title: country);
}).toList()
)
Result

Expandable list view item button pressed changes value in all list view items

i want to increase the value of particular item after pressing the increment button which is inside expandable list view but it affects in all of list view items of sub category name value.
Here is some code I came up with. i dont know where i've made the mistake even though its in statefulwidget. suggestions are welcome. any idea..?
the screenshot of my current output
import 'package:flutter/material.dart';
import 'dart:convert';
class SubService extends StatefulWidget {
String strCatId;
SubService({Key key, #required this.strCatId}) : super(key: key);
#override
SubServiceState createState() => new SubServiceState(strCatId);
}
class SubServiceState extends State<SubService> {
List lessons;
List data;
SubService sModel;
RestDatasource api = new RestDatasource();
int _n = 0;
var subService = new List<SubServices>();
String strCatId;
void add() {
setState(() {
_n++;
});
}
void minus() {
setState(() {
if (_n != 0) _n--;
});
}
SubServiceState(String strCatId) {
strCatId = strCatId;
RestDatasource.getSubServices(strCatId).then((response) {
setState(() {
Iterable list = json.decode(response.body);
subService = list.map((model) => SubServices.fromJson(model)).toList();
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Sub services'),
backgroundColor: Colors.deepOrange,
),
backgroundColor: Colors.white,
body: new ListView.builder(
itemCount: subService == null ? 0 : subService.length,
itemBuilder: (context, i) {
return new ExpansionTile(
title: new Text(
subService[i].getsubcategoryName,
),
children: <Widget>[
new Column(
children: _buildExpandableContent(subService[i].getsubService),
// BottomAppBar(child: Text('Bottom bar'),),
),
],
);
},
),
);
}
_buildExpandableContent(List<Subservicename> sub) {
List<Widget> columnContent = [];
for (Subservicename content in sub)
columnContent.add(
new Container(
child: new Center(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
// mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child : Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 40),
child: Text(
content.getsubservicename,
style: new TextStyle(fontSize: 14.0),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 40),
child: SizedBox(
height: 25.0,
child: new FloatingActionButton(
onPressed: minus ,
child: new Icon(
const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.red,
size: 15.0,
),
backgroundColor: Colors.white,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 40),
child: new Text('$_n', style: new TextStyle(fontSize: 14.0)),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 40),
child: SizedBox(
height: 25.0,
child: new FloatingActionButton(
onPressed: add,
child: new Icon(
Icons.add,
color: Colors.red,
size: 15.0),
backgroundColor: Colors.white,
),
),
),
],
),
),
),
);
return columnContent;
}
}
Following Ejabu approach, I modified it to fit my needs. i need to add my counter in my Subservicename model and increase it in there.
to add product
_addProduct(int index) {
setState(() {
content.getcounter++;
});
to display the value
child : new Text('${content.getcounter}',
and the same for remove product.
onPressed: () => setState((){ if (content.getcounter != 0) content.getcounter--;}),
it happens because you actually only have one integer as reference that defined in SubServiceState
class SubServiceState extends State<SubService> {
List lessons;
...
int _n = 0; // you only have one _n
In spite of building columnContent programmatically, the app will always look for one memory address of variable _n.
for (Subservicename content in sub)
columnContent.add(
new Container(
child: new Center(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
...
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 40),
child: new Text('$_n', style: new TextStyle(fontSize: 14.0)),
),
Solution
You need to move all widget inside columnContent.add() to a new Stateful Widget. Then each of them has its own reference of _n property. or ..
Change int _n to another type, maybe List<Map> or Map<List> which corresponds to your payload structure